.netvb.netcolorsdialogcolordialog

Get color property while ColorDialog still open, before confirming the dialog?


What I want to do is while a ColorDialog is opened, get the temporal selected color in the "full open" mode of the Colordialog.

For example in this image I have a white colored picturebox and a ColorDialog is opened with the "Red" color selected, what I want is to change the picturebox backcolor to the selected color (Red) without pressing the "ok" button to confirm the ColorDialog and to get the Color result...

enter image description here

This is possibly to do with the default ColorDialog?

This is what I've tried:

Public Class Form1

    Dim WithEvents PicBox As New PictureBox
    Dim ColorDlg As New ColorDialog

    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        PicBox.BackColor = Color.Blue
        ColorDlg.FullOpen = True
        Me.Controls.Add(PicBox)
    End Sub

    Private Sub PicBox_Click(sender As Object, e As EventArgs) Handles PicBox.Click
        ColorDlg.Color = sender.backcolor
        While Not ColorDlg.ShowDialog() = Windows.Forms.DialogResult.OK
            sender.BackColor = ColorDlg.Color
        End While
        ' sender.BackColor = ColorDlg.Color
    End Sub

End Class

Solution

  • Disclaimer: Just because we can, doesn't mean we should!

    This example is specifically designed to work with the ColorDialog in the FullOpen mode. It relies on the fact that the "Red:", "Green:" and "Blue:" Edit controls in the dialog are updated in real-time as the color is changed by the user, and the fact that a WM_CTLCOLOREDIT is sent each time those controls are updated (this also applies to when those values are manually typed in). We have to create a new instance of MyColorDialog each time we want to use it so that the window handles to those controls are correct.

    The WindowsEnumerator class below is based on Bob's (TheLearnedOne) work here.

    *I'm Idle_Mind over at Experts-Exchange as well.

    This was tested on Win8 Pro in VS2010 Premium. I don't if any changes would need to be made for the ColorDialog in other versions of Windows:

    Public Class Form1
    
        Private WithEvents PicBox As New PictureBox
        Private WithEvents ColorDlg As MyColorDialog = Nothing
    
        Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
            PicBox.BackColor = Color.Blue
            Me.Controls.Add(PicBox)
        End Sub
    
        Private Sub PicBox_Click(sender As Object, e As EventArgs) Handles PicBox.Click
            ColorDlg = New MyColorDialog
            ColorDlg.Color = sender.backcolor
            If ColorDlg.ShowDialog() = Windows.Forms.DialogResult.OK Then
                sender.BackColor = ColorDlg.Color
            End If
            ColorDlg = Nothing
        End Sub
    
        Private Sub ColorDlg_CurrentColor(c As System.Drawing.Color) Handles ColorDlg.CurrentColor
            PicBox.BackColor = c
        End Sub
    
    End Class
    
    Public Class MyColorDialog
        Inherits ColorDialog
    
        Public Event CurrentColor(ByVal c As Color)
    
        Private Const GA_ROOT As Integer = 2
        Private Const WM_CTLCOLOREDIT As Integer = &H133
    
        Public Declare Function GetAncestor Lib "user32.dll" _
            (ByVal hWnd As IntPtr, ByVal gaFlags As Integer) As IntPtr
    
        Private EditWindows As List(Of ApiWindow) = Nothing
    
        Public Sub New()
            Me.FullOpen = True
        End Sub
    
        <System.Security.Permissions.PermissionSetAttribute(System.Security.Permissions.SecurityAction.Demand, Name:="FullTrust")> _
        Protected Overrides Function HookProc(ByVal hWnd As IntPtr, ByVal msg As Integer, ByVal wParam As IntPtr, ByVal lParam As IntPtr) As IntPtr
            Select Case msg
                Case WM_CTLCOLOREDIT
                    If IsNothing(EditWindows) Then
                        Dim mainWindow As IntPtr = GetAncestor(hWnd, GA_ROOT)
                        If Not mainWindow.Equals(IntPtr.Zero) Then
                            EditWindows = New List(Of ApiWindow)((New WindowsEnumerator).GetChildWindows(mainWindow, "Edit"))
                        End If
                    End If
    
                    If Not IsNothing(EditWindows) AndAlso EditWindows.Count = 6 Then
                        Dim strRed As String = WindowsEnumerator.WindowText(EditWindows(3).hWnd)
                        Dim strGreen As String = WindowsEnumerator.WindowText(EditWindows(4).hWnd)
                        Dim strBlue As String = WindowsEnumerator.WindowText(EditWindows(5).hWnd)
    
                        Dim Red, Green, Blue As Integer
                        If Integer.TryParse(strRed, Red) Then
                            If Integer.TryParse(strGreen, Green) Then
                                If Integer.TryParse(strBlue, Blue) Then
                                    RaiseEvent CurrentColor(Color.FromArgb(Red, Green, Blue))
                                End If
                            End If
                        End If
                    End If
            End Select
    
            Return MyBase.HookProc(hWnd, msg, wParam, lParam)
        End Function
    
    End Class
    
    Public Class ApiWindow
        Public hWnd As IntPtr
        Public ClassName As String
        Public MainWindowTitle As String
    End Class
    
    Public Class WindowsEnumerator
    
        Private Delegate Function EnumCallBackDelegate(ByVal hwnd As IntPtr, ByVal lParam As Integer) As Integer
    
        Private Declare Function EnumWindows Lib "user32" _
            (ByVal lpEnumFunc As EnumCallBackDelegate, ByVal lParam As Integer) As Integer
    
        Private Declare Function EnumChildWindows Lib "user32" _
            (ByVal hWndParent As IntPtr, ByVal lpEnumFunc As EnumCallBackDelegate, ByVal lParam As Integer) As Integer
    
        Private Declare Function GetClassName Lib "user32" Alias "GetClassNameA" _
            (ByVal hwnd As IntPtr, ByVal lpClassName As System.Text.StringBuilder, ByVal nMaxCount As Integer) As Integer
    
        Private Declare Function IsWindowVisible Lib "user32" (ByVal hwnd As IntPtr) As Integer
    
        Private Declare Function GetParent Lib "user32" (ByVal hwnd As IntPtr) As Integer
    
        Private Declare Function SendMessage Lib "user32" Alias "SendMessageA" _
            (ByVal hwnd As IntPtr, ByVal wMsg As Integer, ByVal wParam As Integer, ByVal lParam As Integer) As Integer
    
        Private Declare Function SendMessage Lib "user32" Alias "SendMessageA" _
            (ByVal hwnd As IntPtr, ByVal wMsg As Integer, ByVal wParam As Integer, ByVal lParam As System.Text.StringBuilder) As Integer
    
        Private _listChildren As New List(Of ApiWindow)
        Private _listTopLevel As New List(Of ApiWindow)
    
        Private _topLevelClass As String = String.Empty
        Private _childClass As String = String.Empty
    
        Public Overloads Function GetTopLevelWindows() As ApiWindow()
            EnumWindows(AddressOf EnumWindowProc, &H0)
            Return _listTopLevel.ToArray
        End Function
    
        Public Overloads Function GetTopLevelWindows(ByVal className As String) As ApiWindow()
            _topLevelClass = className
            Return Me.GetTopLevelWindows()
        End Function
    
        Public Overloads Function GetChildWindows(ByVal hwnd As Int32) As ApiWindow()
            _listChildren.Clear()
            EnumChildWindows(hwnd, AddressOf EnumChildWindowProc, &H0)
            Return _listChildren.ToArray
        End Function
    
        Public Overloads Function GetChildWindows(ByVal hwnd As Int32, ByVal childClass As String) As ApiWindow()
            _childClass = childClass
            Return Me.GetChildWindows(hwnd)
        End Function
    
        Private Function EnumWindowProc(ByVal hwnd As Int32, ByVal lParam As Int32) As Int32
            If GetParent(hwnd) = 0 AndAlso IsWindowVisible(hwnd) Then
                Dim window As ApiWindow = GetWindowIdentification(hwnd)
                If _topLevelClass.Length = 0 OrElse window.ClassName.ToLower() = _topLevelClass.ToLower() Then
                    _listTopLevel.Add(window)
                End If
            End If
            Return 1
        End Function
    
        Private Function EnumChildWindowProc(ByVal hwnd As Int32, ByVal lParam As Int32) As Int32
            Dim window As ApiWindow = GetWindowIdentification(hwnd)
            If _childClass.Length = 0 OrElse window.ClassName.ToLower() = _childClass.ToLower() Then
                _listChildren.Add(window)
            End If
            Return 1
        End Function
    
        Private Function GetWindowIdentification(ByVal hwnd As Integer) As ApiWindow
            Dim classBuilder As New System.Text.StringBuilder(64)
            GetClassName(hwnd, classBuilder, 64)
    
            Dim window As New ApiWindow
            window.ClassName = classBuilder.ToString()
            window.MainWindowTitle = WindowText(hwnd)
            window.hWnd = hwnd
            Return window
        End Function
    
        Public Shared Function WindowText(ByVal hwnd As IntPtr) As String
            Const W_GETTEXT As Integer = &HD
            Const W_GETTEXTLENGTH As Integer = &HE
    
            Dim SB As New System.Text.StringBuilder
            Dim length As Integer = SendMessage(hwnd, W_GETTEXTLENGTH, 0, 0)
            If length > 0 Then
                SB = New System.Text.StringBuilder(length + 1)
                SendMessage(hwnd, W_GETTEXT, SB.Capacity, SB)
            End If
            Return SB.ToString
        End Function
    
    End Class