Jump to content

Sky Slate Blueberry Blackcurrant Watermelon Strawberry Orange Banana Apple Emerald Chocolate
Photo

Extracting Menus


  • Please log in to reply
8 replies to this topic
skrommel
  • Members
  • 193 posts
  • Last active: Jun 07 2010 08:30 AM
  • Joined: 30 Jul 2004
I was looking for how to handle menus, but I couldn't find it anywhere, so here's some code for getting the menus from Calculator.

Anyone know why the menu ids in some programs change for every run?

Skrommel


#SingleInstance,Force
SetBatchLInes,-1
AutoTrim,Off 

WinGet,hWnd,ID,Calculator

wholemenu=
space=
hMenu:=DllCall("user32\GetMenu","UInt",hWnd)
GETMENU(hMenu)
MsgBox,%wholemenu%
Return

GETMENU(hMenu)
{
  nMaxCount=100
  uFlag:=0x0400  ;MF_BYPOSITION
  global wholemenu
  global space

  menuitemcount:=DllCall("GetMenuItemCount",UInt,hMenu)
  Loop,%menuitemcount%
  {
    nPos:=A_Index-1
    VarSetCapacity(lpString,100,0) ; [color=red]line inserted on 2008-02-18 -- Hope you dont mind Skrommel! - Moderator! [/color]
    length:=DllCall("user32\GetMenuString","UInt",hMenu,"UINT",nPos,"STR",lpString,"int",nMaxCount,"UINT",uFlag) 
    wholemenu=%wholemenu%%hMenu% - %nPos%%A_Tab%%space%%lpString%`n
    hSubMenu:=DllCall("user32\GetSubMenu","UInt",hMenu,"int",nPos) 
    If hSubMenu>-1
    {
      Loop,3
        space=%space%%A_Space%
      GETMENU(hSubMenu)
      StringTrimRight,space,space,3
    }
  }
  Return
}


Invalid User
  • Members
  • 447 posts
  • Last active: Mar 27 2012 01:04 PM
  • Joined: 14 Feb 2005
thanks, a nice solution. I was looking for this some time back.

PhiLho
  • Moderators
  • 6850 posts
  • Last active: Jan 02 2012 10:09 PM
  • Joined: 27 Dec 2005
I like that! It can be useful.
Note you don't display the menu ID but the menu handle, which isn't fixed.
I wanted to change the code to avoid the use of a global variable, but I discovered that ByRef parameters don't mix with recursive functions!
So I still use a global variable to store the result, sigh!
Here is my version, works with the active window:
WinGet hWnd, ID, A

hMenu := DllCall("GetMenu", "UInt", hWnd)

#wholeMenu =
GetMenu(hMenu)

Clipboard = %#wholeMenu%
MsgBox %#wholeMenu%
Return

GetMenu(_hMenu, _menuLevel=0)
{
	local menuItemCount, indent, nPos, length, lpString, id, hSubMenu

	menuItemCount := DllCall("GetMenuItemCount", "UInt", _hMenu)
	indent =
	If _menuLevel > 0
	{
		indent := "|   "
		Loop % _menuLevel - 1
		{
			indent := indent "|   "
		}
		indent := indent "+-- "
	}
	Loop %menuItemCount%
	{
		nPos := A_Index - 1
		length := DllCall("GetMenuString"
			, "UInt", _hMenu
			, "UInt", nPos
			, "UInt", 0	; NULL
			, "Int", 0	; Get length
			, "UInt", 0x0400)	; MF_BYPOSITION
		VarSetCapacity(lpString, length + 1)	; I don't check the result...
		length := DllCall("GetMenuString"
			, "UInt", _hMenu
			, "UInt", nPos
			, "Str", lpString
			, "Int", length + 1
			, "UInt", 0x0400)
		id := DllCall("GetMenuItemID", "UInt", _hMenu, "Int", nPos)
		If length > 0	; 0 if not of MFT_STRING type or error
		{
			If id < 0
			{
				If _menuLevel = 0
					id = _____
				Else
					id = 00000
			}
			Else
			{
				id = 00000%id%
			}
			StringRight id, id, 5
			#wholeMenu = %#wholeMenu%%id% - %indent%%lpString%`n
		}
		hSubMenu := DllCall("GetSubMenu", "UInt", _hMenu, "Int", nPos)
		If hSubMenu != 0
		{
			GetMenu(hSubMenu, _menuLevel + 1)
		}
	}
}
I tried to slightly improve the indentation display, too.

Update: Chris corrected the crash bug (in v1.0.42.07), so I can give the version without global variables:
WinGet hWnd, ID, A

hMenu := DllCall("GetMenu", "UInt", hWnd)

wholeMenu =
GetMenu(hMenu, wholeMenu)

Clipboard = %wholeMenu%
MsgBox %wholeMenu%
Return

GetMenu(_hMenu, ByRef @wholeMenu, _menuLevel=0)
{
	local menuItemCount, indent, nPos, length, lpString, id, hSubMenu

	menuItemCount := DllCall("GetMenuItemCount", "UInt", _hMenu)
	indent =
	If _menuLevel > 0
	{
		indent := "|   "
		Loop % _menuLevel - 1
		{
			indent := indent "|   "
		}
		indent := indent "+-- "
	}
	Loop %menuItemCount%
	{
		nPos := A_Index - 1
		length := DllCall("GetMenuString"
			, "UInt", _hMenu
			, "UInt", nPos
			, "UInt", 0	; NULL
			, "Int", 0	; Get length
			, "UInt", 0x0400)	; MF_BYPOSITION
		VarSetCapacity(lpString, length + 1)	; I don't check the result...
		length := DllCall("GetMenuString"
			, "UInt", _hMenu
			, "UInt", nPos
			, "Str", lpString
			, "Int", length + 1
			, "UInt", 0x0400)
		id := DllCall("GetMenuItemID", "UInt", _hMenu, "Int", nPos)
		If length > 0	; 0 if not of MFT_STRING type or error
		{
			If id < 0
			{
				If _menuLevel = 0
					id = _____
				Else
					id = ––––0
			}
			Else
			{
				id = 00000%id%
			}
			StringRight id, id, 5
			@wholeMenu = %@wholeMenu%%id% - %indent%%lpString%`n
		}
		hSubMenu := DllCall("GetSubMenu", "UInt", _hMenu, "Int", nPos)
		If hSubMenu != 0
		{
			GetMenu(hSubMenu, @wholeMenu, _menuLevel + 1)
		}
	}
}
I left the first version as some people may want something working for an older version of AHK.
_____ - &File
65400 - |   +-- &Reload Script	Ctrl+R
65401 - |   +-- &Edit Script	Ctrl+E
65402 - |   +-- &Window Spy
65403 - |   +-- &Pause Script	Pause
65404 - |   +-- &Suspend Hotkeys
65405 - |   +-- E&xit (Terminate Script)
_____ - &View
65406 - |   +-- &Lines most recently executed	Ctrl+L
65407 - |   +-- &Variables and their contents	Ctrl+V
65408 - |   +-- &Hotkeys and their methods	Ctrl+H
65409 - |   +-- &Key history and script info	Ctrl+K
65410 - |   +-- &Refresh	F5
_____ - &Help
65411 - |   +-- &User Manual	F1
65412 - |   +-- &Web Site
The function fails on Firefox and Windows Explorer.
_____ - &File
57600 - |   +-- &New	Ctrl+N
57601 - |   +-- &Open...	Ctrl+O
57603 - |   +-- &Save	Ctrl+S
57604 - |   +-- Save &As...
57616 - |   +-- Recent File
40071 - |   +-- Password...
32786 - |   +-- &User Preferences...
40064 - |   +-- Filters...
40065 - |   +-- Apply filters	F7
40066 - |   +-- Friends
57665 - |   +-- E&xit
_____ - &Edit
57642 - |   +-- Select A&ll	Ctrl+A
32791 - |   +-- Ne&w Mailbox...	Ins
32790 - |   +-- &Delete	Del
32783 - |   +-- P&roperties...	Alt+Enter
40061 - |   +-- Mark all as read
_____ - &View
59393 - |   +-- &Status Bar
59392 - |   +-- &Tool Bar
––––0 - |   +-- Language
40005 - |   |   +-- Default English
32773 - |   +-- Lar&ge Icons
59408 - |   +-- S&mall Icon
59410 - |   +-- &List
32776 - |   +-- &Details
32792 - |   +-- &Refresh	F5
32821 - |   +-- Sto&p	Esc
_____ - Quick&Tools
32799 - |   +-- Quick &View
32798 - |   +-- Quick R&eply
32797 - |   +-- Quick &Send
32800 - |   +-- Quick &Delete	Shift+Del
40001 - |   +-- &Load
40002 - |   +-- View as &text
40004 - |   +-- Preview
––––0 - |   +-- Filters
40067 - |   |   +-- Add to friends
40068 - |   |   +-- Domain to friends
32792 - |   +-- &Check Now!	F5
32822 - |   +-- S&uspend
32821 - |   +-- Sto&p	Esc
_____ - &Help
57670 - |   +-- &Help	F1
40583 - |   +-- Test RegExp
57664 - |   +-- &About the program...


HuBa
  • Members
  • 175 posts
  • Last active: Feb 13 2012 09:51 AM
  • Joined: 24 Feb 2007
Good job!

But how can I get the hwnd of an AHK menu (like Tray menu)?

This doesn't works:
ControlGet, hMenu, Hwnd, , Tray, A


BTW, a little optimization can be made here in the script abowe:
Else
{
  id = 00000%id%
  StringRight id, id, 5
}         
@wholeMenu = %@wholeMenu%%id% - %indent%%lpString%`n


jsmain
  • Members
  • 124 posts
  • Last active: Dec 15 2014 08:31 PM
  • Joined: 11 Jul 2005
This is great stuff right here!
Too bad it doesn't also show the check states of the menu items....

HuBa
  • Members
  • 175 posts
  • Last active: Feb 13 2012 09:51 AM
  • Joined: 24 Feb 2007

This is great stuff right here!
Too bad it doesn't also show the check states of the menu items....

It can be easily achieved with a little coding. You can query the check state via WinAPI.

SoLong&Thx4AllTheFish
  • Members
  • 4999 posts
  • Last active:
  • Joined: 27 May 2007
I was wondering if you could use this to mimick something from
Quicksilver: Application Menus, you can watch a video here:
http://www.themerlin... ... tion-menus

Basically:
- you press a hotkey
- you get a list of the entire menu from the current application
- you can do a incremental search (like iswitch)
- execute selected menu

Edit: to answer my own question, yes it is possible, at least
with some applications. I've tested the script briefly below with
Total Commander and Notepad++ and it seems to work.

- Run the script
- Press capslock
- Type to search menu item
- Hit enter
- and if all goes well it the chosen menu item should be executed

(Doesn't seem to work with UltraEdit, MS Word...)

Warning: The code below has been merged from two scripts just to
make it work, so it can be improved a lot I think. Would be nice if
MS Word and other applications could be supported...

; ---------------------------------------------------------------------
; merged two scripts:
; 1)
; Name: Incremental Listbox        
; http://www.autohotkey.com/forum/viewtopic.php?t=2534
; 2)
; name: extracting menus
; http://www.autohotkey.com/forum/viewtopic.php?t=8492
; ---------------------------------------------------------------------
; -- Configuration: ---------------------------------------------------
; ---------------------------------------------------------------------
;Your favourite hotkey:
CapsLock::                  ;Hotkey CapsLock to start 

ControlGetFocus, control, A

;The name of the textfile containing the contents of the listbox:
GetMenuListTexts()

; ---------------------------------------------------------------------
; -- Autoexecute ------------------------------------------------------
; ---------------------------------------------------------------------

Gui, Add, ListBox, vChoice gListBoxClick w400 h250 vscroll 
Gui, Add, Text, x10 y264 w50 h20, Search`:
Gui, Add, Edit, x66 y261 w343 h20

Gosub RefreshListBox

search =   

;The input-command in this loop processes keys pressed by the user 
;or send by the "send"-command
Loop
{
    Input, input, L1, {enter}{esc}{backspace}{up}{down}{pgup}{pgdn}{tab}{left}{right}
       if ErrorLevel = EndKey:escape
      {
         Gui, cancel
         Gosub GuiClose
      }
       if ErrorLevel = EndKey:enter
      {
         GoSub, WordRetrieve
         continue
      }
       if ErrorLevel = EndKey:backspace
      {
         GoSub, DeleteSearchChar
         continue
      }
       if ErrorLevel = EndKey:up
      {
         Send, {up}
         continue
      }
       if ErrorLevel = EndKey:down
      {
         Send, {down}
         continue
      }
      if ErrorLevel = EndKey:pgup
      {
         Send, {pgup}
         continue
      }
      if ErrorLevel = EndKey:pgdn
      {
         Send, {pgdn}
         continue
      }
   
    search = %search%%input%               ;Assembles the search string
    GuiControl,, Edit1, %search%            ;Displays the search string in the edit control
    StringLen,SearchLength,Search
    Gosub RefreshListBox 
    continue
}

return


; ---------------------------------------------------------------------
; -- Subroutines ------------------------------------------------------
; ---------------------------------------------------------------------

;Assigns the chosen item to the variable "Choice". 
WordRetrieve:
Gui, submit, noHide
GuiControlGet, Choice ; Retrieve the ListBox's current selection.
Gui, Destroy
Loop, parse, MenuListTexts, |
{
 If (Choice = A_LoopField) 
    	{
    	SendMenuItem:=command%A_Index%-1
    	ControlGetFocus, control, HWND
	PostMessage, 0x111, %SendMenuItem%, %Control%,,A
	;MsgBox %Choice%, %SendMenuItem%
	ExitApp
	}
      continue
}

Return
; ---------------------------------------------------------------------

;Exits the script on closing the GUI
GuiClose:
GuiEscape:
   ExitApp
; ---------------------------------------------------------------------

;Refreshes the listbox according to the search criteria:
RefreshListBox:
Wordlist=

Loop, parse, MenuListTexts, |
{
   IfInString, A_LoopField,%Search%      
      Wordlist=%Wordlist%|%A_LoopField%   
   Else
      continue
}

Gui, Show,
GuiControl,, ListBox1, %wordlist%
GuiControl, Choose, ListBox1, 1
return
;-------------------------------------------------------------------------

;Delete the last character and update Listbox:
DeleteSearchChar:

if search =
    return

StringTrimRight, search, search, 1
GuiControl,, Edit1, %search%
GoSub, RefreshListBox

return
;-------------------------------------------------------------------------

; Handle mouse click events on the list box:
ListBoxClick:

if A_GuiControlEvent = DoubleClick
    send, {enter}

return

; --------------

GetMenuListTexts()
{
global

WinGet hWnd, ID, A

hMenu := DllCall("GetMenu", "UInt", hWnd)

wholeMenu =
GetMenu(hMenu, wholeMenu)

Clipboard = %wholeMenu%
StringReplace, clipboard, clipboard, `&, , All
StringReplace, clipboard, clipboard, %A_Tab%, %A_Space%-%A_Space%, All
StringReplace, clipboard, clipboard, +, -, All
;MsgBox %clipboard%
test=0
command=0
MenuText=0
Loop, parse, clipboard, `n
	{
	StringLeft, command%A_Index%,A_LoopField, 5
	StringTrimLeft, MenuText%A_Index%, A_LoopField, 6
	tmp:=MenuText%A_Index%
	MenuListTexts=%MenuListTexts%|%tmp%
	}
}

GetMenu(_hMenu, ByRef @wholeMenu, _menuLevel=0)
{
   local menuItemCount, indent, nPos, length, lpString, id, hSubMenu
   menuItemCount := DllCall("GetMenuItemCount", "UInt", _hMenu)
   indent =
   Loop %menuItemCount%
   {
      nPos := A_Index - 1
      length := DllCall("GetMenuString"
         , "UInt", _hMenu
         , "UInt", nPos
         , "UInt", 0   ; NULL
         , "Int", 0   ; Get length
         , "UInt", 0x0400)   ; MF_BYPOSITION
      VarSetCapacity(lpString, length + 1)   ; I don't check the result...
      length := DllCall("GetMenuString"
         , "UInt", _hMenu
         , "UInt", nPos
         , "Str", lpString
         , "Int", length + 1
         , "UInt", 0x0400)
      id := DllCall("GetMenuItemID", "UInt", _hMenu, "Int", nPos)
      If length > 0   ; 0 if not of MFT_STRING type or error
      {
         If id < 0
         {
            If _menuLevel = 0
               id = _____
            Else
               id = ––––0
         }
	Else
	{
	  id = 00000%id%
	  StringRight id, id, 5
	}         
	@wholeMenu = %@wholeMenu%%id%|%lpString%`n
      }
      hSubMenu := DllCall("GetSubMenu", "UInt", _hMenu, "Int", nPos)
      If hSubMenu != 0
      {
         GetMenu(hSubMenu, @wholeMenu, _menuLevel + 1)
      }
   }



SoLong&Thx4AllTheFish
  • Members
  • 4999 posts
  • Last active:
  • Joined: 27 May 2007
bump: see post above...

Superfraggle
  • Members
  • 1019 posts
  • Last active: Sep 25 2011 01:06 AM
  • Joined: 02 Nov 2004
I dont know about ultra edit, but MSWord uses non standard menus, the information might be extractable someway but not the standard way.


afaik anyway