Jump to content

Recommended Posts

Posted (edited)

I wasn't able to find a ChooseColor hook example in the forum, so I went ahead and made one. It's dark mode, but realistically you could color it however you want.

I do need some help, however. Please. :)

While everything seems to work quite well, color returns and so on, I need some help to ensure that I have done things properly. So here is a working example of a custom hook for the ChooseColor dialog box (in dark mode). If anyone is able to help improve it, please feel free. It is for the community, as always. And if the community wants to help improve upon it, I am always open to that.

#include <GUIConstantsEx.au3>
#include <APIGdiConstants.au3>
#include <SendMessage.au3>
#include <WinAPISysWin.au3>
#include <WinAPITheme.au3>
#include <WindowsStylesConstants.au3>
#include <WinAPIGdiInternals.au3>
#include <WinAPIGdiDC.au3>
#include <AutoItConstants.au3>
#include <WindowsNotifsConstants.au3>
#include <WinAPIGdi.au3>
#include <Misc.au3>

; Initialize System DPI awareness
DllCall("user32.dll", "bool", "SetProcessDpiAwarenessContext", @AutoItX64 ? "int64" : "int", -2)

Global Const $CC_ENABLEHOOK = 0x00000010
Global Const $COLOROKSTRING = "commdlg_ColorOK"

Global $g_hColorProc = 0

_WinAPI_RegisterWindowMessage($COLOROKSTRING)

ConsoleWrite(_ChooseColorHooked(2, 0x0080C0, 2) & @CRLF)


Func _ChooseColorHooked($vReturnType = 0, $iColorRef = 0, $iRefType = 0, $hWndOwnder = 0)
    Local $tagCustcolors = "dword[16]"

    Local $tChoose = DllStructCreate($tagCHOOSECOLOR)

    Local Static $tCc = DllStructCreate($tagCustcolors) ; keep the colors
    Local $iReturnType, $vReturn

    If $vReturnType = -9 Or $vReturnType = -10 Then
        ; get $tagCustcolors as array
        Local $a_tCc = __ChooseColor_StructToArray($tCc)
        $a_tCc[0] = -1
        If $vReturnType = -10 Then $tCc = DllStructCreate($tagCustcolors)
        Return $a_tCc
    ElseIf $vReturnType > 9 Then
        ; re-init./clear $tCc, and continue on a clean slate, just "10 + ReturnType"
        $tCc = DllStructCreate($tagCustcolors) ; ( maybe unnecessary but the user may want to )
        $iReturnType = $vReturnType - 10
    ElseIf IsArray($vReturnType) Then
        ; user declared Custcolors array
        If UBound($vReturnType, $UBOUND_ROWS) = 17 And UBound($vReturnType, $UBOUND_DIMENSIONS) = 1 Then
            For $n = 1 To 16
                DllStructSetData($tCc, 1, $vReturnType[$n], $n)
            Next
            If $vReturnType[0] = -9 Then
                ; ..consistent with "-9 = return $tagCustcolors as array"
                $vReturnType[0] = 0 ; set back to default "ReturnType"
                Return $vReturnType
            EndIf
            If $vReturnType[0] > 9 Then $vReturnType[0] -= 10 ; just in case the user mistakenly think it needs to
            $iReturnType = $vReturnType[0]
        Else
            ; unexpected array format
            Return SetError(-5, 0, -1)
        EndIf
    Else
        $iReturnType = $vReturnType
    EndIf

    If $iReturnType < 0 Or $iReturnType > 2 Then
        ; unexpected ReturnType
        If IsArray($vReturnType) Then
            $vReturnType[0] = -1
            Return SetError(-4, 0, $vReturnType)
        EndIf
        Return SetError(-4, 0, -1)
    EndIf

    If $iRefType = 1 Then
        ; BGR hex color to colorref
        $iColorRef = Int($iColorRef)
    ElseIf $iRefType = 2 Then
        ; RGB hex color to colorref
        $iColorRef = Hex(String($iColorRef), 6)
        $iColorRef = '0x' & StringMid($iColorRef, 5, 2) & StringMid($iColorRef, 3, 2) & StringMid($iColorRef, 1, 2)
    EndIf

    DllStructSetData($tChoose, "Size", DllStructGetSize($tChoose))
    DllStructSetData($tChoose, "hWndOwnder", $hWndOwnder)
    DllStructSetData($tChoose, "rgbResult", $iColorRef)
    DllStructSetData($tChoose, "CustColors", DllStructGetPtr($tCc))
    $g_hColorProc = DllCallbackRegister(_ColorChooseProc, "int", "hwnd;uint;wparam;lparam")
    DllStructSetData($tChoose, 'lpfnHook', DllCallbackGetPtr($g_hColorProc))
    DllStructSetData($tChoose, "Flags", BitOR($__MISCCONSTANT_CC_ANYCOLOR, $__MISCCONSTANT_CC_FULLOPEN, $__MISCCONSTANT_CC_RGBINIT, $CC_ENABLEHOOK))

    Local $aResult = DllCall("comdlg32.dll", "bool", "ChooseColor", "struct*", $tChoose)
    DllCallbackFree($g_hColorProc)
    Local $iError = @error
    If @error Then
        Local $iExtended = @extended
        If IsArray($vReturnType) Then
            $vReturnType[0] = -1
            Return SetError($iError, $iExtended, $vReturnType)
        EndIf
        Return SetError($iError, $iExtended, -1)
    EndIf

    If $aResult[0] = 0 Then
        ; user selected cancel or struct settings incorrect
        If IsArray($vReturnType) Then
            $vReturnType[0] = -1
            Return SetError(-3, 0, $vReturnType)
        EndIf
        Return SetError(-3, 0, -1)
    EndIf

    Local $sColor_picked = DllStructGetData($tChoose, "rgbResult")

    If $iReturnType = 1 Then
        ; return Hex BGR Color
        $vReturn = '0x' & Hex(String($sColor_picked), 6)
    ElseIf $iReturnType = 2 Then
        ; return Hex RGB Color
        $sColor_picked = Hex(String($sColor_picked), 6)
        $vReturn = '0x' & StringMid($sColor_picked, 5, 2) & StringMid($sColor_picked, 3, 2) & StringMid($sColor_picked, 1, 2)
    Else
        ; return RGB COLORREF
        $vReturn = $sColor_picked
    EndIf
    If IsArray($vReturnType) Then
        $vReturnType = __ChooseColor_StructToArray($tCc)
        $vReturnType[0] = $vReturn
        Return $vReturnType
    EndIf
    Return $vReturn
EndFunc   ;==>_ChooseColorHooked

Func _ColorChooseProc($hWnd, $iMsg, $wParam, $lParam)
    #forceref $hWnd, $iMsg, $wParam, $lParam
    Local Static $hBrush

    Switch $iMsg
        Case $WM_INITDIALOG
            _WinAPI_DwmSetWindowAttribute($hWnd, $DWMWA_USE_IMMERSIVE_DARK_MODE, True)
            $hBrush = _WinAPI_CreateSolidBrush(0x202020)

            ; Find all controls to apply dark theme
            Local $aCtrls = _WinAPI_EnumChildWindows($hWnd, False)
            If @error = 0 Then
                For $i = 1 To $aCtrls[0][0]
                    If $aCtrls[$i][1] = "Button" Then
                        _WinAPI_SetWindowTheme($aCtrls[$i][0], "DarkMode_Explorer")
                        _SendMessage($aCtrls[$i][0], $WM_THEMECHANGED, 0, 0)
                    EndIf
                    If $aCtrls[$i][1] = "Edit" Then
                        _WinAPI_SetWindowTheme($aCtrls[$i][0], "DarkMode_Explorer")
                        _SendMessage($aCtrls[$i][0], $WM_THEMECHANGED, 0, 0)
                        _WinAPI_SetWindowLong($aCtrls[$i][0], $GWL_EXSTYLE, BitAND(_WinAPI_GetWindowLong($aCtrls[$i][0], $GWL_EXSTYLE), BitNOT($WS_EX_CLIENTEDGE)))
                        _WinAPI_SetWindowLong($aCtrls[$i][0], $GWL_EXSTYLE, BitOR(_WinAPI_GetWindowLong($aCtrls[$i][0], $GWL_EXSTYLE), $WS_EX_STATICEDGE))
                        _WinAPI_SetWindowPos($aCtrls[$i][0], 0, 0, 0, 0, 0, BitOR($SWP_NOMOVE, $SWP_NOSIZE, $SWP_NOZORDER, $SWP_FRAMECHANGED))
                    EndIf
                Next
            EndIf

        Case $WM_CTLCOLORSTATIC,  $WM_CTLCOLORDLG
            _WinAPI_SetBkMode($wParam, $TRANSPARENT)
            _WinAPI_SetTextColor($wParam, 0xFFFFFF)
            Return $hBrush

        Case $WM_CTLCOLOREDIT
            _WinAPI_SetBkMode($wParam, $TRANSPARENT)
            _WinAPI_SetTextColor($wParam, 0xFFFFFF)
            Return $hBrush

        Case $WM_CTLCOLORBTN
            Return $hBrush

        Case $WM_DESTROY
            _WinAPI_DeleteObject($hBrush)

    EndSwitch
EndFunc

 

Edited by WildByDesign
Posted

I am following the MSDN info for: To enable a hook procedure for the Color dialog box

I still have to figure out the RegisterWindowMessage stuff:

Quote

The dialog box sends the COLOROKSTRING registered message to the hook procedure when the user clicks the OK button. The hook procedure can reject the selected color and force the dialog box to remain open by returning zero when it receives this message. The hook procedure can force the dialog box to select a particular color by sending the SETRGBSTRING registered message to the dialog box. To use these registered messages, you must pass the COLOROKSTRING and SETRGBSTRING constants to the RegisterWindowMessage function to get a message identifier. You can then use the identifier to detect and process messages sent from the dialog box, or to send messages to the dialog box.

 

Posted
1 hour ago, WildByDesign said:

I still have to figure out the RegisterWindowMessage stuff:

its just to create your own unique WM_ value if you need to use one across apps. 
So you'll see every time you call $WM_MYCODE = _WinAPI_RegisterWindowMessage("myUnique_WM_Code") it'll return the same value - even after the the original process ends.

This registered value will stick around until the end of your windows session.

If you're not working across processes, you'd normally just declare a code in the WM_APP range (0x8000 - 0xBFFF). ie.

Global Const $WM_MYCODE = $WM_APP+ 10

Just be aware AutoIt might be using some of these for it's own purposes!

And to complete the set, there is also the WM_USER range (0x0400 - 0x7FFF) if you need a specify a code for a particular class.  eg. toolbar messages are defined as below.  The messages mean something to the toolbar control, but something else to another control.

#define TB_ENABLEBUTTON         (WM_USER + 1)
#define TB_CHECKBUTTON          (WM_USER + 2)
#define TB_PRESSBUTTON          (WM_USER + 3)
...

 

Posted (edited)

so basically at the top:

Global Const $COLOROKSTRING = _WinAPI_RegisterWindowMessage("commdlg_ColorOK")

then in _ColorChooseProc you can  theoretically

Switch $iMsg
        Case $WM_INITDIALOG
        
        .... etc.
        Case $COLOROKSTRING
            Local $tChooseColor = DllStructCreate($tagCHOOSECOLOR, $lParam)
            ... etc.
            
EndSwitch

Edit:

Without delving too deeply into it, this documentation seems to be wrong...

The dialog box sends the COLOROKSTRING registered message to the hook procedure when the user clicks the OK button.
The hook procedure can reject the selected color and force the dialog box to remain open by returning zero when it receives this message.

Returning 1 keeps the dialog open, 0 closes it and accepts the colour..

Edited by MattyD
Posted
8 hours ago, MattyD said:

If you're not working across processes, you'd normally just declare a code in the WM_APP range (0x8000 - 0xBFFF). ie.

Global Const $WM_MYCODE = $WM_APP+ 10

Thanks Matty. This is all very helpful information. I've never really looked into custom messages before for any of my apps but I can see how that could have been helpful for one of my apps. Now that I am aware of it, I will probably implement it in the one app. Very handy.

7 hours ago, MattyD said:

then in _ColorChooseProc you can  theoretically

This also was very helpful. I've got it working now thanks to your post. 

7 hours ago, MattyD said:

Returning 1 keeps the dialog open, 0 closes it and accepts the colour..

Indeed, yes. You're right in the fact that it seems MS has got their docs mixed up on the return value.

Posted

@MattyD By the way, I was hoping to use your CBTHook technique (from the Dark MsgBox stuff) in $HCBT_CREATEWND to intercept the ChooseColor dialog. Technically, I was intercepting it and I was able to apply the nice dark theme to the ChooseColor dialog window. But the problem was that I could not get any return color that way.

This is why I had to go this route by separately doing:

DllStructSetData($tChoose, 'lpfnHook', DllCallbackGetPtr($g_hColorProc))
DllStructSetData($tChoose, "Flags", BitOR($__MISCCONSTANT_CC_ANYCOLOR, $__MISCCONSTANT_CC_FULLOPEN, $__MISCCONSTANT_CC_RGBINIT, $CC_ENABLEHOOK))

In particular, the $CC_ENABLEHOOK flag and the lpfnHook did the trick for getting a custom procedure going that also gives return values.

The only problem that I have now is that I cannot intercept the users' own usage of the _ChooseColor() function dialog which is what I was hoping for. It seems that my only choice (I assume as of right now) would be to create a separate function such as _ChooseColorDark() unless I can figure out how to get a return from the standard _ChooseColor() function.

From the perspective of a UDF (GUIDarkTheme), I would rather not make users go through their GUI scripts and rename all of their _ChooseColor() functions. I try to make everything as automatic as possible.

Posted
4 minutes ago, ahmet said:

I think FunSkin is the topic.

Thanks, that helped me to find the topic. I'm surprised that the topic did not get much attention back then because it is very neat. It looks like it can change a lot of things. I'm checking it out right now. :)

Posted
1 hour ago, WildByDesign said:

I was hoping to use your CBTHook technique ..... But the problem was that I could not get any return color that way.

It seems to work OK if you don't EndDialog on WM_COMMAND. Just let it run through to the DefSubclassProc(). 

But the trick will be differentiating between a dialog types so we can tailor the subclass depending on what we're looking at.  Msgboxes, fileOpen dialogs, colorpickers etc all share the same classname...

Posted
9 minutes ago, MattyD said:

It seems to work OK if you don't EndDialog on WM_COMMAND. Just let it run through to the DefSubclassProc(). 

You are right again. :)

From MSDN:

Quote

Do not call the EndDialog function from the hook procedure. Instead, the hook procedure can call the PostMessage function to post a WM_COMMAND message with the IDABORT value to the dialog box procedure. Posting IDABORT closes the dialog box and causes the dialog box function to return FALSE. If you need to know why the hook procedure closed the dialog box, you must provide your own communication mechanism between the hook procedure and your application.

 

10 minutes ago, MattyD said:

But the trick will be differentiating between a dialog types so we can tailor the subclass depending on what we're looking at.  Msgboxes, fileOpen dialogs, colorpickers etc all share the same classname...

This part I have already made some code to differentiate between MsgBox, ChooseColor and ChooseFont. I haven't looked into the other dialog types yet.

Posted

It looks like I can also properly center the pesky ChooseColor dialog in $WM_SHOWWINDOW which is nice because that dialog always opens in the top left corner of the screen for me.

49 minutes ago, MattyD said:

It seems to work OK if you don't EndDialog on WM_COMMAND.

I am also successfully getting the proper color returns now from ChooseColor dialog. This is fantastic! :)

This way I can use my original subclass procs that are already working for other purposes.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
×
×
  • Create New...