Jump to content

Is there any way to obtain CtrlID from UDF-created menu?


Go to solution Solved by Nine,

Recommended Posts

Posted

I am needing to obtain the CtrlID for menus created with _GUICtrlMenu_CreateMenu() but not having any luck.

_WinAPI_GetDlgCtrlID() does not seem to be able to get CtrlID's from menus at all. Whether it's from _GUICtrlMenu_CreateMenu() or GUICtrlCreateMenu(). I have found other ways to obtain the CtrlID for menus created with GUICtrlCreateMenu() but I am still stuck on _GUICtrlMenu_CreateMenu().

I even created a loop to loop through 10,000 CtrlID's to find a match but they don't seem to exist.

Thanks for your time. :)

Posted (edited)

Yep, so with menus you have a HMENU handle, its not a window handle - this is why _WinAPI_GetDlgCtrlID wont work. I realise GUICtrlCreateMenu gives you a "control id" but I think this is just an internal autoit thing.

You can retrieve the IDM of items if you have the parent HMENU and its position. This is normally used if you're handling WM_MENUCOMMAND (menus created with the $MNS_NOTIFYBYPOS style). Otherwise you'd just go by the IDM and handle notifications via WM_COMMAND.

IDMs aren't necessarily unique either - you can reuse the ID in multiple menus. (Maybe a command appears in a context menu as well as the main menu for eg.)

#include <guimenu.au3>
#include <guiconstants.au3>
#include <windowsconstants.au3>

Global $hGUI = GUICreate("test", 400, 200)
GUIRegisterMsg($WM_COMMAND, "WM_COMMAND")
GUIRegisterMsg($WM_MENUCOMMAND, "WM_MENUCOMMAND")
GUISetState()

Global Enum $idmNew = 1000, $idmOpen, $idmSave, $idmExit

;1st Menu
Global $hFile = _GUICtrlMenu_CreateMenu()

_GUICtrlMenu_InsertMenuItem($hFile, 0, "New", $idmNew)
_GUICtrlMenu_InsertMenuItem($hFile, 1, "Open", $idmOpen)
_GUICtrlMenu_InsertMenuItem($hFile, 2, "Save", $idmSave)
_GUICtrlMenu_InsertMenuItem($hFile, 3, "", 0)
_GUICtrlMenu_InsertMenuItem($hFile, 4, "Exit", $idmExit)

;2nd Menu
Global $hFile2 = _GUICtrlMenu_CreateMenu()
_GUICtrlMenu_InsertMenuItem($hFile2, 0, "New", $idmNew)
_GUICtrlMenu_InsertMenuItem($hFile2, 1, "Open", $idmOpen)
_GUICtrlMenu_InsertMenuItem($hFile2, 2, "Save", $idmSave)
_GUICtrlMenu_InsertMenuItem($hFile2, 3, "", 0)
_GUICtrlMenu_InsertMenuItem($hFile2, 4, "Exit", $idmExit)

;Main
Global $hMain = _GUICtrlMenu_CreateMenu(BitOR($MNS_NOTIFYBYPOS, $MNS_CHECKORBMP)) ;Use this one for WM_MENUCOMMAND
;~ Global $hMain = _GUICtrlMenu_CreateMenu() ;Use this one instead for WM_COMMAND
_GUICtrlMenu_InsertMenuItem($hMain, 0, "File 1", 0, $hFile)
_GUICtrlMenu_InsertMenuItem($hMain, 1, "File 2", 0, $hFile2)
_GUICtrlMenu_SetMenu($hGUI, $hMain)

do
until GUIGetMsg() = $GUI_EVENT_CLOSE


Func WM_COMMAND($hWnd, $iMsg, $wParam, $lParam)
    #forceref $hWnd, $iMsg, $lParam

    Local $iItemID, $iNotifCode, $iCtrlID, $hCtrl

    Switch _WinAPI_HiWord($wParam)
        Case 0 ;menu
            $iItemID = _WinAPI_LoWord($wParam)
            ConsoleWrite("WM_COMMAND: " & $iItemID & @CRLF)

        Case 1 ;accelerator
            $iItemID = _WinAPI_LoWord($wParam)
            ConsoleWrite("WM_COMMAND: " & $iItemID & @CRLF)

        Case Else ;control-defined notif code
            $iNotifCode = _WinAPI_HiWord($wParam) ;e.g BN_CLICKED BN_DBLCLK for button
            $iCtrlID = _WinAPI_LoWord($wParam)
            $hCtrl = $lParam
            
    EndSwitch

    Return $GUI_RUNDEFMSG
EndFunc   ;==>WM_COMMAND

Func WM_MENUCOMMAND($hWnd, $iMsg, $wParam, $lParam)
    #forceref $hWnd, $iMsg, $lParam
    Local $sText = _GUICtrlMenu_GetItemText($lParam, $wParam)
    Local $iItemID = _GUICtrlMenu_GetItemID($lParam, $wParam)
    ConsoleWrite("WM_MENUCOMMAND: " & $iItemID & " " & $sText & @CRLF)
    
    Return $GUI_RUNDEFMSG
EndFunc   ;==>WM_COMMAND
Edited by MattyD
Posted

The real question is why you need control ID of a menu.  I believe it is related to your Dark Mode UDF but if we cannot understand the fundamental of your research, you may pass way abord of a nicer solution.  It is like your solution for distinguish between menu type, are you sure about it ?  I doubt, but since we do not have a snippet of the code, all can really guess...

Posted
13 hours ago, Nine said:

The real question is why you need control ID of a menu.  I believe it is related to your Dark Mode UDF but if we cannot understand the fundamental of your research, you may pass way abord of a nicer solution.  It is like your solution for distinguish between menu type, are you sure about it ?  I doubt, but since we do not have a snippet of the code, all can really guess...

My apologies. You are right, I wasn't as descriptive with my goal and intentions as I usually am and that does not assist anyone trying to help me. Same with no code snippet.

So yes, it is related to the Dark Mode UDF. My goal is to make it easier for anyone who uses the UDF. I want to detect all controls automatically and I want to detect the menubar items as well. This way, the user only has to add one line to enable everything in the UDF and the UDF should then do it all.

I put together the best from ModernMenuRaw UDF combined with the best from UEZ's recent dark menubar subclassing since both sources have certain parts that are better when combined together. The problem is that both solutions rely on subclassing the menubar with the CtrlID returned by GUICtrlCreateMenu.

Here is the relevant code snippet which automatically gets the CtrlID's and text for the menubar:

Func _GUITopMenuTheme($hWnd)
    Local $sItemText
    Local $z = 0

    $hGUI = $hWnd

    ;Local $aMenuInfo[0][3]
    ; get top menu handle
    Local $hMenu = _GUICtrlMenu_GetMenu($hWnd)
    If Not $hMenu Then Return False
    GUIRegisterMsg($WM_NCPAINT, "WM_NCPAINT")
    GUIRegisterMsg($WM_ACTIVATE, "WM_ACTIVATE_Handler")
    Local $iMenuCount = _GUICtrlMenu_GetItemCount($hMenu)
    Local $aMenuInfo[$iMenuCount][3]
    Global $g_aMenuText[$iMenuCount]
    Global $g_aMenuPos[$iMenuCount]
    For $i = 0 To UBound($g_aMenuText) - 1
        $g_aMenuText[$i] = ""
        $g_aMenuPos[$i] = ""
    Next
    _ArrayColInsert($g_aMenuText, 0)

    ; run through the first 100 CtrlID's to determine which are valid menus
    For $i = 1 To 100
        If _GUICtrlMenu_IsMenu(GUICtrlGetHandle($i)) Then
            $sItemText = _GUICtrlMenu_GetItemText($hMenu, $i, False) ; obtain text
            ; if it returns blank for text, discard because that would be context menu or other
            If $sItemText Then
                $g_aMenuText[$z][0] = $i            ; nMenu (CtrlId)
                $g_aMenuText[$z][1] = $sItemText    ; menu text
                $g_aMenuPos[$z] = $i
                $aMenuInfo[$z][0] = GUICtrlGetHandle($i)    ; submenu handle
                $aMenuInfo[$z][1] = $i                      ; nMenu (CtrlId)
                $aMenuInfo[$z][2] = $sItemText              ; menu text
                $z += 1
            EndIf
        EndIf
    Next

    For $i = 0 To UBound($g_aMenuText) - 1
        Local $nIdx = _GetNewItemIndex()
        If $nIdx = 0 Then Return 0
        
        $MENULASTITEM = $aMenuInfo[$i][1]

        $arMenuItems[$nIdx][0] = $g_aMenuText[$i][0]
        $arMenuItems[$nIdx][1] = $g_aMenuText[$i][1]
        $arMenuItems[$nIdx][2] = -1
        $arMenuItems[$nIdx][3] = "TOP"
        $arMenuItems[$nIdx][4] = 0
        $arMenuItems[$nIdx][5] = FALSE
        $arMenuItems[$nIdx][6] = -1
        $arMenuItems[$nIdx][7] = TRUE

        _GUICtrlMenu_SetItemType($hMenu, $i, $MFT_OWNERDRAW, True)
        MenuBarBKColor($hMenu, 0x202020)
    Next
EndFunc

I only run through the first 100 CtrlID's to check for menus. Possibly that could be a higher number, but generally they would be lower.

If you want to see and test the full working code, GUIDarkTheme-0.4 includes it now.

Posted
14 hours ago, MattyD said:

Yep, so with menus you have a HMENU handle, its not a window handle - this is why _WinAPI_GetDlgCtrlID wont work. I realise GUICtrlCreateMenu gives you a "control id" but I think this is just an internal autoit thing.

First of all, thank you for taking time to share that code. It is really helpful and I'm going to mess around with your example some more today. I don't understand all of it so I'm going to spend some time learning from it as well.

I don't know much about whether it is a legitimate CtrlID or not, but that return from GUICtrlCreateMenu is what ModernMenuRaw and UEZ's dark menubar subclassing use for adding dark mode theme to the menubar. The text and menu position, from my understanding, are derived from that CtrlID in those implementations.

14 hours ago, MattyD said:

You can retrieve the IDM of items if you have the parent HMENU and its position. This is normally used if you're handling WM_MENUCOMMAND (menus created with the $MNS_NOTIFYBYPOS style). Otherwise you'd just go by the IDM and handle notifications via WM_COMMAND.

IDMs aren't necessarily unique either - you can reuse the ID in multiple menus. (Maybe a command appears in a context menu as well as the main menu for eg.)

I definitely need to learn more about HMENU in general.

What does IDM stand for?

The only problem that I have is that I would need the ItemID/CtrlID (or whichever is right) prior to any user interaction. I apologize because I wasn't very descriptive in my first post as to what my goal was. Basically, I have made a UDF for dark theme and my goal is to auto-detect all controls, including menubar items. I am detecting most controls already and I am now (as of GUIDarkTheme-0.4) able to automatically detect text/CtrlID/Position for menubar created with GUICtrlCreateMenu and switching to ownerdraw and completing the theme-related stuff.

My goal is essentially that the anyone with a GUI script can add the #include line for the UDF and only have to add the single line _ApplyDarkTheme($hGUI) right before GUISetState to send the GUI handle on to the UDF which will then auto-detect and subclass anything that needs it. So far it is working remarkably well.

But if I can also get it to work with scripts that have a menubar created with _GUICtrlMenu_CreateMenu(), that would be a bonus. So far any of my attempts have failed.

  • Solution
Posted
4 hours ago, WildByDesign said:

Here is the relevant code snippet

Thank you.  I now better understand what you are up to.  As @MattyD said, there is not Control ID for those _GUICtrlMenu_* function.  So you should consider switching to handles since all controls (whatsoever) has a windows handle.  To extend Matty example :

; From Nine
#include <GUIConstantsEx.au3>
#include <GuiMenu.au3>
#include <WinAPI.au3>
#include <Array.au3>

Opt("MustDeclareVars", True)

Global Enum $e_idOpen = 1000, $e_idSave, $e_idInfo, $e_idTest
Global $hMenu

Example()

Func Example()
  Local $hGUI = GUICreate("Menu", 400, 300)
  GUISetState(@SW_SHOW)

  ; Context menu

  $hMenu = _GUICtrlMenu_CreatePopup()
  _GUICtrlMenu_InsertMenuItem($hMenu, 0, "Open", $e_idOpen)
  _GUICtrlMenu_InsertMenuItem($hMenu, 1, "Save", $e_idSave)
  _GUICtrlMenu_InsertMenuItem($hMenu, 3, "", 0)
  _GUICtrlMenu_InsertMenuItem($hMenu, 4, "Info", $e_idInfo)

  #cs
    Local $idContextmenu = GUICtrlCreateContextMenu()
    Local $idOpen = GUICtrlCreateMenuItem("Open", $idContextmenu)
    Local $idSave = GUICtrlCreateMenuItem("Save", $idContextmenu)
    GUICtrlCreateMenuItem("", $idContextmenu)
    Local $idInfo = GUICtrlCreateMenuItem("Info", $idContextmenu)
    Local $hMenu = GUICtrlGetHandle($idContextmenu)
  #ce

  ; GUI menu

  Local $hFile = _GUICtrlMenu_CreateMenu()
  _GUICtrlMenu_InsertMenuItem($hFile, 0, "&Open", $e_idOpen)
  Local $hRecent = _GUICtrlMenu_CreateMenu()
  _GUICtrlMenu_InsertMenuItem($hRecent, 0, "Test")
  _GUICtrlMenu_InsertMenuItem($hFile, 1, "&Recent", 0, $hRecent)
  Local $hMain = _GUICtrlMenu_CreateMenu()
  _GUICtrlMenu_InsertMenuItem($hMain, 0, "&File", 0, $hFile)
  _GUICtrlMenu_InsertMenuItem($hMain, 1, "Test", $e_idTest)
  _GUICtrlMenu_SetMenu($hGUI, $hMain)

  #cs
    Local $idFile = GUICtrlCreateMenu("&File")
    Local $idOpen = GUICtrlCreateMenuItem("&Open", $idFile)
    Local $idRecent = GUICtrlCreateMenu("&Recent", $idFile)
    GUICtrlCreateMenuItem("Test", $idRecent)
    Local $hFile = GUICtrlGetHandle($idFile)
    Local $hRecent = GUICtrlGetHandle($idRecent)
    GUICtrlCreateMenuItem("Test", -1)
  #ce

  ConsoleWrite("Main " & IsInMainMenu($hGUI, $hMain) & @CRLF)
  ConsoleWrite("Context " & IsInMainMenu($hGUI, $hMenu) & @CRLF)
  ConsoleWrite("File " & IsInMainMenu($hGUI, $hFile) & @CRLF)
  ConsoleWrite("Recent " & IsInMainMenu($hGUI, $hRecent) & @CRLF)

  Local $aItem = GetTopLevelMenuItems($hGUI)
  _ArrayDisplay($aItem)

  GUIRegisterMsg($WM_CONTEXTMENU, WM_CONTEXTMENU)

  Do
  Until GUIGetMsg() = $GUI_EVENT_CLOSE

EndFunc   ;==>Example

Func WM_CONTEXTMENU($hWnd, $iMsg, $wParam, $lParam)
  _GUICtrlMenu_TrackPopupMenu($hMenu, $wParam)
EndFunc   ;==>WM_CONTEXTMENU

Func IsInMainMenu($hGUI, $hMenu)
  Local $hMain = _GUICtrlMenu_GetMenu($hGUI)
  If $hMain = $hMenu Then Return 0
  Return CheckSub($hMain, $hMenu, 1)
EndFunc   ;==>IsInMainMenu

Func CheckSub($hMenu, $hCheck, $iLvl)
  Local $hSub
  For $i = 0 To _GUICtrlMenu_GetItemCount($hMenu) - 1
    $hSub = _GUICtrlMenu_GetItemSubMenu($hMenu, $i)
    If $hSub Then
      If $hSub = $hCheck Then Return $iLvl
      Return CheckSub($hSub, $hCheck, $iLvl + 1)
    EndIf
  Next
  Return -1
EndFunc   ;==>CheckSub

Func GetTopLevelMenuItems($hWnd)
  Local $hMenu = _GUICtrlMenu_GetMenu($hWnd)
  Local $nItem = _GUICtrlMenu_GetItemCount($hMenu)
  Local $aList[$nItem][3], $tInfo
  For $i = 0 To $nItem - 1
    $tInfo = _GUICtrlMenu_GetItemInfo($hMenu, $i)
    $aList[$i][0] = $tInfo.SubMenu
    $aList[$i][1] = $tInfo.ID
    $aList[$i][2] = _GUICtrlMenu_GetItemText($hMenu, $i)
  Next
  Return $aList
EndFunc   ;==>GetTopLevelMenuItems

 

Posted
1 hour ago, Nine said:

So you should consider switching to handles since all controls (whatsoever) has a windows handle.

I agree and I like the idea of using handles in general for that exact reason. But there are some things that I don't fully understand.

For example, many of the _GUICtrlMenu_* functions don't work with handles. Such as _GUICtrlMenu_SetItemType for setting ownerdraw. I'm not entirely sure how I would do that in this case.

Another concern that I have is not all of the top menus in the _ArrayDisplay show a handle. The File menu shows a handle in the array but Test does not. I added a few more just to mess around with it and they didn't show a handle either.

I'm sure that there are likely other ways to get the handles so I'm not too worried about that. I'll have to look into whether or not the subclassing part can be done with handles since both examples that I use to reference only do it with the CtrlIDs.

Thank you for the extended example. I'm going to mess around with it some more later tonight.

Posted
11 minutes ago, WildByDesign said:

Test does not

Yes I voluntary created a non menu item at top level.  Although it is not frequent, it is possible.

17 minutes ago, WildByDesign said:

I added a few more just to mess around with it and they didn't show a handle either.

You probably added non menu also.

Tested adding another menu, and it works as expected :

Local $hOther = _GUICtrlMenu_CreateMenu()
  _GUICtrlMenu_InsertMenuItem($hOther, 0, "Other")
  Local $hFile = _GUICtrlMenu_CreateMenu()
  _GUICtrlMenu_InsertMenuItem($hFile, 0, "&Open", $e_idOpen)
  Local $hRecent = _GUICtrlMenu_CreateMenu()
  _GUICtrlMenu_InsertMenuItem($hRecent, 0, "Test")
  _GUICtrlMenu_InsertMenuItem($hFile, 1, "&Recent", 0, $hRecent)
  Local $hMain = _GUICtrlMenu_CreateMenu()
  _GUICtrlMenu_InsertMenuItem($hMain, 0, "&File", 0, $hFile)
  _GUICtrlMenu_InsertMenuItem($hMain, 1, "Test", $e_idTest)
  _GUICtrlMenu_InsertMenuItem($hMain, 2, "Other", 0, $hOther)
  _GUICtrlMenu_SetMenu($hGUI, $hMain)

image.png.b05ff09856b09045670f0e5f0c6e9c50.png

Posted

I have an idea and wondering if I can get any opinions on it.

All menus created with _GUICtrlMenu_CreateMenu seem to have an ItemID of 0 by default. I am wondering about detecting these as top level menus and if they don't have an ItemID, assign one with _GUICtrlMenu_SetItemID. I seem to be able to pick that up in the subclassing code. I would have to also take note of its position in the menu and probably store both in an array to work with.

Do you think this would be suitable?

If yes, what would be an appropriate number to start assigning as ItemID?

Posted (edited)

@Nine Can you please check and see if this makes sense and would be correct?

Basically, if there is already an ItemID, good. If not, assign one. If everything is correct then I should have everything I need in a single array. :)

EDIT: I am thinking that this should work for both GUICtrlCreateMenu and _GUICtrlMenu_CreateMenu.

Func GetTopLevelMenuItems($hWnd)
  Local $iItemID = 10000
  Local $hMenu = _GUICtrlMenu_GetMenu($hWnd)
  Local $nItem = _GUICtrlMenu_GetItemCount($hMenu)
  Local $aList[$nItem][3], $tInfo
  For $i = 0 To $nItem - 1
    $tInfo = _GUICtrlMenu_GetItemInfo($hMenu, $i)
    $aList[$i][0] = $tInfo.SubMenu
    If Not $tInfo.ID Then
      _GUICtrlMenu_SetItemID($hMenu, $i, $iItemID)
      $aList[$i][1] = _GUICtrlMenu_GetItemID($hMenu, $i)
      $iItemID += 1
    Else
      $aList[$i][1] = $tInfo.ID
    EndIf
    $aList[$i][2] = _GUICtrlMenu_GetItemText($hMenu, $i)
  Next
  Return $aList
EndFunc   ;==>GetTopLevelMenuItems

 

Edited by WildByDesign
Posted

I am happy to report partial success on this so far. It's working with both GUICtrlCreateMenu and _GUICtrlMenu_CreateMenu. The only negative aspect so far is that the measurements for the custom drawing are off for _GUICtrlMenu_CreateMenu. But one step at a time.

partial.png

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