Jump to content

Recommended Posts

Posted

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
Posted

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. 

#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
Posted

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. :)

Posted (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 by argumentum
answered

Follow the link to my code contribution ( and other things too ).
FAQ - Please Read Before Posting  image.gif.922e3a93535f431de08b31ee669cc446.gif
autoit_scripter_blue_userbar.png

Posted
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

"I think you are searching a bug where there is no bug... don't listen to bad advice."

Posted

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!)

  • 2 months later...
Posted
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.

Posted

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.

Posted

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.

Posted
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!

Posted (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 by WildByDesign

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...