WildByDesign Posted April 13 Posted April 13 This is more of a question for anyone with more familiarity with subclassing. So I don't necessarily need code examples in return, unless of course that helps to explain things more. I have subclasses for some individual handles for some specific window classes. However, for this question, I am referring specifically to the main GUI window. For example, instead of: GUIRegisterMsg($WM_ACTIVATE, __GUIDarkMenu_WM_ACTIVATE) GUIRegisterMsg($WM_WINDOWPOSCHANGED, __GUIDarkMenu_WM_WINDOWPOSCHANGED) GUIRegisterMsg($WM_MEASUREITEM, __GUIDarkMenu_WM_MEASUREITEM) GUIRegisterMsg($WM_DRAWITEM, __GUIDarkMenu_WM_DRAWITEM) I have successfully switched to the following: ;... $g_pMenuWndProc = DllCallbackRegister("__GUIDarkTheme_MenuProc", "lresult", "hwnd;uint;wparam;lparam;uint_ptr;dword_ptr") _WinAPI_SetWindowSubclass($g_hGui, DllCallbackGetPtr($g_pMenuWndProc), 1) ;... Func __GUIDarkTheme_MenuProc($hWnd, $iMsg, $wParam, $lParam, $iID, $pData) #forceref $iID, $pData Local $sContinue = $GUI_RUNDEFMSG Switch $iMsg Case $WM_ACTIVATE $sContinue = __GUIDarkMenu_WM_ACTIVATE($hWnd, $iMsg, $wParam, $lParam) Case $WM_DRAWITEM $sContinue = __GUIDarkMenu_WM_DRAWITEM($hWnd, $iMsg, $wParam, $lParam) Case $WM_MEASUREITEM $sContinue = __GUIDarkMenu_WM_MEASUREITEM($hWnd, $iMsg, $wParam, $lParam) Case $WM_WINDOWPOSCHANGED $sContinue = __GUIDarkMenu_WM_WINDOWPOSCHANGED($hWnd, $iMsg, $wParam, $lParam) EndSwitch If $sContinue = $GUI_RUNDEFMSG Then Return __WinAPI_DefSubclassProc($hWnd, $iMsg, $wParam, $lParam) EndFunc ;==>__GUIDarkTheme_MenuProc This is works perfectly well. But I am wondering now about expanding. Those all cover the functionality needed for subclassing the GUI menubar. I am wondering if I can (or even should?) use another window procedure for the other GUI handle related window messages. I have GUIRegisterMsg for $WM_NOTIFY, $WM_CTLCOLORLISTBOX, $WM_CTLCOLOREDIT, and $WM_SIZE. And since they are not related to GUI menubar, this I why I am curious about another procedure. And so this leads to my question from the title: Can you have two or more subclasses for the same GUI handle? For the record, I have never had success subclassing $WM_SIZE for the main GUI handle. I have only had success with GUIRegisterMsg and $WM_SIZE. Thank you for your time.
Solution MattyD Posted April 14 Solution Posted April 14 Yeah its certainly possible, but its not what you'd normally see. There is also a risk of one of one subclass consuming messages which you might need up the chain. expandcollapse popup#include <WinAPI.au3> #include <WindowsConstants.au3> #include <GUIConstants.au3> Global Const $tagMINMAXINFO = "struct;long ptReserved[2];long ptMaxSize[2];long ptMaxPosition[2];long ptMinTrackSize[2];long ptMaxTrackSize[2];endstruct" Global $__g_hWndProc = DllCallbackRegister("WndProc", "lresult", "hwnd;uint;wparam;lparam;uint_ptr;dword_ptr") Global $__g_pWndProc = DllCallbackGetPtr($__g_hWndProc) Global $__g_hWndProc2 = DllCallbackRegister("WndProc2", "lresult", "hwnd;uint;wparam;lparam;uint_ptr;dword_ptr") Global $__g_pWndProc2 = DllCallbackGetPtr($__g_hWndProc2) Global $__g_hGui = GUICreate("Test", 400, 200, -1, -1, $WS_OVERLAPPEDWINDOW) _WinAPI_SetWindowSubclass($__g_hGui, $__g_pWndProc, 1) _WinAPI_SetWindowSubclass($__g_hGui, $__g_pWndProc2, 2) GUISetState() Do Until GUIGetMsg() = $GUI_EVENT_CLOSE GUIDelete($__g_hGui) DllCallbackFree($__g_hWndProc) DllCallbackFree($__g_hWndProc2) Func WndProc($hWnd, $iMsg, $wParam, $lParam, $iID, $pData) Switch $iMsg Case $WM_ERASEBKGND _WinAPI_FillRect($wParam, _WinAPI_GetClientRect($hWnd), _WinAPI_GetStockObject($BLACK_BRUSH)) Return True Case $WM_DESTROY _WinAPI_RemoveWindowSubclass($hWnd, $__g_pWndProc, $iID) Return _WinAPI_DefSubclassProc($hWnd, $iMsg, $wParam, $lParam) Case Else Return _WinAPI_DefSubclassProc($hWnd, $iMsg, $wParam, $lParam) EndSwitch EndFunc Func WndProc2($hWnd, $iMsg, $wParam, $lParam, $iID, $pData) Switch $iMsg Case $WM_GETMINMAXINFO ;Limit resizing Local $tMINMAXINFO = DllStructCreate($tagMINMAXINFO, $lParam) $tMINMAXINFO.ptMinTrackSize(1) = 200 $tMINMAXINFO.ptMinTrackSize(2) = 100 $tMINMAXINFO.ptMaxTrackSize(1) = 500 $tMINMAXINFO.ptMaxTrackSize(2) = 300 Return 0 Case $WM_DESTROY _WinAPI_RemoveWindowSubclass($hWnd, $__g_pWndProc2, $iID) Return _WinAPI_DefSubclassProc($hWnd, $iMsg, $wParam, $lParam) Case Else Return _WinAPI_DefSubclassProc($hWnd, $iMsg, $wParam, $lParam) EndSwitch EndFunc WildByDesign 1
WildByDesign Posted April 15 Author Posted April 15 Thank you, Matty. That answered my question perfectly and the example is very helpful. You always have an excellent way of making complex things look so much easier. And this example is the best for me to follow and learn from. I really appreciate your help. MattyD 1
argumentum Posted April 15 Posted April 15 (edited) _WinAPI_SetWindowSubclass($__g_hGui, $__g_pWndProc2, 2) A latent question would be: that UINT_PTR , how can I make it unique for my UDF, as to not interfere with other scripts ? can I pass TimerInit() ? 🤯 Edit: I guess something higher than 10000 as a Const, like 54437 or whatever, to be known as reserved. Edited April 15 by argumentum answered Follow the link to my code contribution ( and other things too ). FAQ - Please Read Before Posting
pixelsearch Posted April 15 Posted April 15 58 minutes ago, argumentum said: A latent question would be: that UINT_PTR , how can I make it unique for my UDF, as to not interfere with other scripts ? Hello. If not mistaken, the subclass ID doesn't have to be unique, for example this should work : _WinAPI_SetWindowSubclass($__g_hGui, $__g_pWndProc, 1) _WinAPI_SetWindowSubclass($__g_hGui, $__g_pWndProc2, 1) And this should work too : _WinAPI_SetWindowSubclass($__g_hGui, $__g_pWndProc, 1) _WinAPI_SetWindowSubclass($__g_hGui, $__g_pWndProc, 2) It's the combination of the subclass procedure + subclass ID that matters, so it shouldn't matter if the user already uses the same subclass ID in his own subclassing functions, @MattyD what's your opinion, because I may be totally wrong on this. Thanks argumentum 1 "I think you are searching a bug where there is no bug... don't listen to bad advice."
MattyD Posted April 15 Posted April 15 yep, you are correct. In fact, its the triplet which is used as an identifier when registering the subclass. It's a combo of window handle + Subclass proc ptr + subclass id. Apparently you may wish to subclass a window multiple times, but using the same subclass proc. You could set the SubClassID to associated hwnds or object ptrs or something. But this is an edge case. I've used CtrlIds for the SubclassID before when subclassing things like buttons, but there's no hard and fast rule. If we're not using the subclassid param for anything meaningful then we should probably just use 0. (In hindsight I should have done that in the example!) argumentum and pixelsearch 2
WildByDesign Posted 2 hours ago Author Posted 2 hours ago On 4/14/2026 at 1:49 PM, MattyD said: Case $WM_DESTROY _WinAPI_RemoveWindowSubclass($hWnd, $__g_pWndProc, $iID) Return _WinAPI_DefSubclassProc($hWnd, $iMsg, $wParam, $lParam) Hey Matty. I was just thinking about something. Is it possible to send the $__g_pWndProc in the initial $pData during SetWindowSubclass? I'm trying to simplify the WM_DESTROY return. Something like: Case $WM_DESTROY _WinAPI_RemoveWindowSubclass($hWnd, $pData, $iID) Return _WinAPI_DefSubclassProc($hWnd, $iMsg, $wParam, $lParam) I just don't understand pData well enough as to whether or not that would count as a DWORD and so on.
WildByDesign Posted 2 hours ago Author Posted 2 hours ago From what I can tell after some testing, it looks like I need to send the DLL handle (not the pointer) as pData: _WinAPI_SetWindowSubclass($__g_hGui, $__g_pWndProc, 1, $__g_hWndProc) And then in WM_DESTROY: Case $WM_DESTROY _WinAPI_RemoveWindowSubclass($hWnd, DllCallbackGetPtr($pData), $iID) Return _WinAPI_DefSubclassProc($hWnd, $iMsg, $wParam, $lParam) This seems to be working correctly. The reason why I need this (for GUIDarkTheme UDF) is mostly just to simply it for me. I created a custom user message ($__DM_WM_CHANGETHEME) as you had suggested in another thread, and when a theme change occurs, I can send that message to the main top-level GUI window and all child windows. And the relevant subclasses process that message to remove the subclass if needed. And I'm doing the same with WM_DESTROY as well.
MattyD Posted 1 hour ago Posted 1 hour ago yeah absolutely - its just arbitrary data. So either of these should work. ; Example 1 _WinAPI_SetWindowSubclass($__g_hGui, $__g_pWndProc, 0, $__g_hWndProc) ... _WinAPI_RemoveWindowSubclass($hWnd, DllCallbackGetPtr($pData), $iID) ; Example 2 _WinAPI_SetWindowSubclass($__g_hGui, $__g_pWndProc, 0, $__g_pWndProc) ... _WinAPI_RemoveWindowSubclass($hWnd, $pData, $iID) Just be mindful that you only get one pData, so it might be useful to allocate memory somewhere and set pData as a pointer to that. This way we can attach more data down the track by chucking things in a struct. The doco says $pData (dwRefData) is: The reference data provided to the SetWindowSubclass function. This can be used to associate the subclass instance with a "this" pointer. Which I guess was the original intent. So that sounds like a wrapper to me - basically a "user" object to ingest window messages and keep track of its own properties etc. But I don't see much point in concocting something like that here.
MattyD Posted 1 hour ago Posted 1 hour ago 33 minutes ago, WildByDesign said: $__DM_WM_CHANGETHEME I'm probably misremembering, but doesn't WM_THEMECHANGED get sent everywhere?? Not that it really matters!
WildByDesign Posted 49 minutes ago Author Posted 49 minutes ago (edited) 39 minutes ago, MattyD said: I'm probably misremembering, but doesn't WM_THEMECHANGED get sent everywhere?? Not that it really matters! The problem with WM_THEMECHANGED is that it does not get sent when switching back and forth between Dark and Light modes which is really unfortunate. It only triggers when switching full themes like Aero to some custom msstyles or custom msstyles back to Aero, for example. Edit: But switching between Dark and Light modes stays within the Aero theme which is why it does not trigger. For detecting the switch between Dark and Light modes, you have to handle the WM_SETTINGCHANGE message and watch for the "ImmersiveColorSet" value from lParam like below: Func __GUIDarkTheme_WM_SETTINGCHANGE($hWnd, $iMsg, $wParam, $lParam) #forceref $hWnd, $iMsg, $wParam ConsoleWrite("__GUIDarkTheme_WM_SETTINGCHANGE" & @CRLF) ; lParam is a pointer to a string indicating what changed Local $tString = DllStructCreate("wchar[256]", $lParam) Local $sParam = DllStructGetData($tString, 1) Local Static $iModePrev ; "ImmersiveColorSet" indicates a Light/Dark mode change If $sParam = "ImmersiveColorSet" Then Local $iMode = RegRead("HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize", "AppsUseLightTheme") If $iModePrev <> $iMode Then $iModePrev = $iMode _GUIDarkTheme_SwitchTheme($hWnd) Else Return $GUI_RUNDEFMSG EndIf EndIf Return $GUI_RUNDEFMSG EndFunc ;==>__GUIDarkTheme_WM_SETTINGCHANGE Another problem is that individual window subclasses can't get that message. So the top level window can listen for it and then send out my custom $__DM_WM_CHANGETHEME message to all of the child windows. Edited 48 minutes ago by WildByDesign MattyD 1
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