WildByDesign Posted Monday at 11:10 PM Posted Monday at 11:10 PM (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. expandcollapse popup#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 Tuesday at 12:23 AM by WildByDesign
WildByDesign Posted Monday at 11:24 PM Author Posted Monday at 11:24 PM 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.
MattyD Posted Tuesday at 01:24 AM Posted Tuesday at 01:24 AM 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) ... WildByDesign 1
MattyD Posted Tuesday at 02:29 AM Posted Tuesday at 02:29 AM (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 Tuesday at 02:54 AM by MattyD WildByDesign 1
WildByDesign Posted Tuesday at 10:16 AM Author Posted Tuesday at 10:16 AM 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. MattyD 1
WildByDesign Posted Tuesday at 10:25 AM Author Posted Tuesday at 10:25 AM @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.
ahmet Posted Tuesday at 11:08 AM Posted Tuesday at 11:08 AM There is example by funkey for changing MsgBox and FileOpenDialog border color if I am not mistaken. Somebody updated those to work with some newer OS. WildByDesign 1
ahmet Posted Tuesday at 11:30 AM Posted Tuesday at 11:30 AM I think FunSkin is the topic. WildByDesign 1
WildByDesign Posted Tuesday at 11:35 AM Author Posted Tuesday at 11:35 AM 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.
MattyD Posted Tuesday at 12:16 PM Posted Tuesday at 12:16 PM 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... WildByDesign 1
WildByDesign Posted Tuesday at 12:27 PM Author Posted Tuesday at 12:27 PM 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. MattyD 1
WildByDesign Posted Tuesday at 01:07 PM Author Posted Tuesday at 01:07 PM 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.
Recommended Posts
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 accountSign in
Already have an account? Sign in here.
Sign In Now