 |
AutoHotkey Community Let's help each other out
|
| View previous topic :: View next topic |
| Author |
Message |
Leef_me
Joined: 08 Apr 2009 Posts: 5335 Location: San Diego, California
|
Posted: Sat Jan 09, 2010 1:34 am Post subject: Can AHK detect external program menu selections |
|
|
We have mousegetpos, winget and a host of other commands.
mousegetpos can tell the window, x,y, control
Winget can tell the active window and a list of windows & controls
with these I can even detect the press of a button on n external program.
Is there a command that allows detecting when a menu item is selected?
We can create a menu in a gui, we can select a menu item, etc
But can we capture the selection, or must that be coded by hand ?
What I am really looking for is script recorder that captures menu and application button clicks .
Any ideas ? Thanks in advance
Leef_me |
|
| Back to top |
|
 |
Peter
Joined: 30 Dec 2005 Posts: 448
|
Posted: Sat Jan 09, 2010 6:43 am Post subject: |
|
|
There's a SetWinEventHook API. Have a look at [tool] WinEventHook Messages.
It doesn't seem to detect a selection though. I'm quite sure there will be a message sent with a selection, but to the program that owns the menu. |
|
| Back to top |
|
 |
Leef_me
Joined: 08 Apr 2009 Posts: 5335 Location: San Diego, California
|
Posted: Mon Jan 11, 2010 8:32 pm Post subject: |
|
|
Peter's answer doesn't address my question.
Perhaps a rewording of my question would help:
Does someone have an example that allows detecting when a menu item is selected, in an external program? |
|
| Back to top |
|
 |
svi
Joined: 09 Oct 2006 Posts: 236 Location: Finland
|
Posted: Mon Jan 11, 2010 10:16 pm Post subject: |
|
|
| Leef_me wrote: | | ...in an external program? |
I modified Micha's Get Info from Context Menu to do the job, but as Micha stated it works on... | Quote: | | standard windows context menus only!!! |
| Code: | ;constants
MFS_HILITE = 0x80
#NoEnv ; Recommended for performance and compatibility with future AutoHotkey releases.
SendMode Input ; Recommended for new scripts due to its superior speed and reliability.
#Persistent
Return
#IfWinExist ahk_class #32768
LButton::
Enter::
NumPadEnter::
WinGet, hwnd, ID, A
WinGet, hMenu, ID, ahk_class #32768
;Get count of menu items
ContextMenCnt := GetContextMenuCount(hMenu)
If ContextMenCnt < 1
{
Tooltip
Return
}
item =
Loop %ContextMenCnt%
{
IsEnabled := GetContextMenuState(hMenu, A_Index-1)
If (IsEnabled & MFS_HILITE)
item := A_Index
}
If item
Tooltip, % GetContextMenuText(hMenu, item - 1)
Else
ToolTip
If A_ThisHotkey = LButton
Click Down
Else
ControlSend, , {%A_ThisHotkey%}, ahk_id %hwnd%
Return
~LButton Up:: Return
/***************************************************************
* returns the count of menu items
***************************************************************
*/
GetContextMenuCount(hMenu)
{
WinGetClass, WindowClass, ahk_id %hMenu%
;All popups should have the window class #32768
if WindowClass <> #32768
{
return 0
}
;Retrieve menu handle from window
SendMessage, 0x01E1, , , , ahk_id %hMenu%
;Errorlevel is set by SendMessage. It contains the handle to the menu
hMenu := errorlevel
menuitemcount:=DllCall("GetMenuItemCount",UInt,hMenu)
Return, menuitemcount
}
/***************************************************************
* returns the state of a menu entry
***************************************************************
*/
GetContextMenuState(hMenu, Position)
{
WinGetClass, WindowClass, ahk_id %hMenu%
if WindowClass <> #32768
{
return -1
}
SendMessage, 0x01E1, , , , ahk_id %hMenu%
;Errorlevel is set by SendMessage. It contains the handle to the menu
hMenu := errorlevel
;We need to allocate a struct
VarSetCapacity(MenuItemInfo, 60, 0)
;Set Size of Struct to the first member
NumPut(48, MenuItemInfo, 0, 4)
;Get only Flags from dllcall GetMenuItemInfo MIIM_TYPE = 1
NumPut(1, MenuItemInfo, 4, 4)
;GetMenuItemInfo: Handle to Menu, Index of Position, 0=Menu identifier / 1=Index
InfoRes := DllCall("user32.dll\GetMenuItemInfo",UInt,hMenu, Uint, Position, uint, 1, "int", &MenuItemInfo)
InfoResError := errorlevel
LastErrorRes := DllCall("GetLastError")
if InfoResError <> 0
return -1
if LastErrorRes != 0
return -1
;Get Flag from struct
; GetMenuItemInfoRes := ExtractInteger(MenuItemInfo, 12, false, 4)
; GetMenuItemInfoRes0 := ExtractInteger(MenuItemInfo, 12, false, 4)
GetMenuItemInfoRes := NumGet(MenuItemInfo, 12)
GetMenuItemInfoRes0a := NumGet(MenuItemInfo, 12)
/*
IsEnabled = 1
if GetMenuItemInfoRes > 0
IsEnabled = 0
return IsEnabled
*/
return GetMenuItemInfoRes
}
/***************************************************************
* returns the text of a menu entry (standard windows context menus only!!!)
***************************************************************
*/
GetContextMenuText(hMenu, Position)
{
Global GetMenuItemInfoRes0
Global GetMenuItemInfoRes0a
Global GetMenuItemInfoRes1
Global GetMenuItemInfoRes1a
WinGetClass, WindowClass, ahk_id %hMenu%
if WindowClass <> #32768
{
return -1
}
SendMessage, 0x01E1, , , , ahk_id %hMenu%
;Errorlevel is set by SendMessage. It contains the handle to the menu
hMenu := errorlevel
;We need to allocate a struct
VarSetCapacity(MenuItemInfo, 200, 0)
;Set Size of Struct (48) to the first member
NumPut(48, MenuItemInfo, 0, 4)
;Retrieve string MIIM_STRING = 0x40 = 64 (/ MIIM_TYPE = 0x10 = 16)
NumPut(64, MenuItemInfo, 4, 4)
;Set type - Get only size of string we need to allocate
;NumPut(0, MenuItemInfo, 8, 4)
;GetMenuItemInfo: Handle to Menu, Index of Position, 0=Menu identifier / 1=Index
InfoRes := DllCall("user32.dll\GetMenuItemInfo",UInt,hMenu, Uint, Position, uint, 1, "int", &MenuItemInfo)
if InfoRes = 0
return -1
InfoResError := errorlevel
LastErrorRes := DllCall("GetLastError")
if InfoResError <> 0
return -1
if LastErrorRes <> 0
return -1
;Get size of string from struct
; GetMenuItemInfoRes := ExtractInteger(MenuItemInfo, 40, false, 4)
; GetMenuItemInfoRes1 := ExtractInteger(MenuItemInfo, 40, false, 4)
GetMenuItemInfoRes := NumGet(MenuItemInfo, 40)
GetMenuItemInfoRes1a := NumGet(MenuItemInfo, 40)
;If menu is empty return
If GetMenuItemInfoRes = 0
return "{Empty String}"
;+1 should be enough, we'll use 2
GetMenuItemInfoRes += 2
;Set capacity of string that will be filled by windows
VarSetCapacity(PopupText, GetMenuItemInfoRes, 0)
;Set Size plus 0 terminator + security ;-)
NumPut(GetMenuItemInfoRes, MenuItemInfo, 40, 4)
NumPut(&PopupText, MenuItemInfo, 36, 4)
InfoRes := DllCall("user32.dll\GetMenuItemInfo",UInt,hMenu, Uint, Position, uint, 1, "int", &MenuItemInfo)
if InfoRes = 0
return -1
InfoResError := errorlevel
LastErrorRes := DllCall("GetLastError")
if InfoResError <> 0
return -1
if LastErrorRes <> 0
return -1
return PopupText
}
PrintScreen::reload |
Also, it doesn't work on Explorer's / IE's favorites (it's really not a menu), and some submenus (like Explorer's File -> Send) show "{Empty String}".
It's not perfect: it works if the item is selected by a mouse or Enter key, but not when it's selected by a key, which is underlined in the menu (preceded by an ampersand in the tooltip). _________________ Pekka Vartto
Last edited by svi on Tue Jan 12, 2010 10:19 am; edited 1 time in total |
|
| Back to top |
|
 |
Leef_me
Joined: 08 Apr 2009 Posts: 5335 Location: San Diego, California
|
Posted: Tue Jan 12, 2010 3:06 am Post subject: |
|
|
Thank you svi.
I'll have to study this for a while. |
|
| Back to top |
|
 |
Peter
Joined: 30 Dec 2005 Posts: 448
|
Posted: Tue Jan 12, 2010 7:14 am Post subject: |
|
|
| Leef_me wrote: | | Does someone have an example that allows detecting when a menu item is selected, in an external program? | External programs is not the problem. Perhaps a rewording of my answer .
It detects when a standard menu opens and closes on any program, but not when/which a selection is made. But the latter can maybe solved with some hotkeys waiting after the menu opened?! |
|
| Back to top |
|
 |
svi
Joined: 09 Oct 2006 Posts: 236 Location: Finland
|
Posted: Tue Jan 12, 2010 5:37 pm Post subject: |
|
|
It can be done in much simplier way.
You can use the following script to test which menu item selections [EDIT: actually highlightings] are detected (beep if not) by pressing q (I use that as a hotkey on my test scripts, because it's not used in the Finnish language).
Esc to exit.
| Code: | q::
SendMessage, 0x1E1, 0, 0, , ahk_class #32768
hMenu := ErrorLevel
Loop, % DllCall("GetMenuItemCount", "UInt", hMenu)
{
idx := A_Index - 1
nSize := DllCall("GetMenuString", "UInt", hMenu, "Int", idx, "UInt", 0, "Int", 0, "UInt", 0x400)
VarSetCapacity(sString, nSize)
DllCall("GetMenuString", "Uint", hMenu, "Int", idx, "Str", sString, "Int", nSize, "UInt", 0x400)
VarSetCapacity(mii, 48, 0)
NumPut(48, mii, 0)
NumPut(1, mii, 4)
DllCall("GetMenuItemInfo", "UInt", hMenu, "UInt", idx, "UInt", 1, "UInt", &mii)
fState := NumGet(mii, 12)
If (fState & 0x80 And sString)
{
ToolTip % sString
Return
}
Else
ToolTip
}
SoundBeep
Return
Esc:: ExitApp |
Detecting mnemonic key usage seems too complicated  _________________ Pekka Vartto |
|
| Back to top |
|
 |
TLM
Joined: 21 Aug 2006 Posts: 2926 Location: The Shell
|
Posted: Thu May 13, 2010 8:33 pm Post subject: |
|
|
Pekka, very nice way to simplify
Errorlevel returns fail from the message sent.
Working on another approach to conjure the menu.
Iam I to assume that errorlevel should be 32768's handle?
Again, great work. _________________
paradigm.shift:=(•_•)┌П┐RTFM||^.*∞ |
|
| Back to top |
|
 |
svi
Joined: 09 Oct 2006 Posts: 236 Location: Finland
|
Posted: Sun May 16, 2010 10:31 pm Post subject: |
|
|
| TLM wrote: | Pekka, very nice way to simplify  |
Unfortunately I simplified a little too much: the script doesn't show the last character of the item!
Maybe it's line 160 in my first script (originally Micha's): | Code: | 159 ;+1 should be enough, we'll use 2
160 GetMenuItemInfoRes += 2 | , which should have been left
Increasing nSize with 1 seems to work: | Code: | q::
SendMessage, 0x1E1, 0, 0, , ahk_class #32768
hMenu := ErrorLevel
Loop, % DllCall("GetMenuItemCount", "UInt", hMenu)
{
idx := A_Index - 1
nSize := DllCall("GetMenuString", "UInt", hMenu, "Int", idx, "UInt", 0, "Int", 0, "UInt", 0x400) + 1
VarSetCapacity(sString, nSize)
DllCall("GetMenuString", "Uint", hMenu, "Int", idx, "Str", sString, "Int", nSize, "UInt", 0x400)
VarSetCapacity(mii, 48, 0)
NumPut(48, mii, 0)
NumPut(1, mii, 4)
DllCall("GetMenuItemInfo", "UInt", hMenu, "UInt", idx, "UInt", 1, "UInt", &mii)
fState := NumGet(mii, 12)
If (fState & 0x80 And sString)
{
ToolTip % sString
Return
}
Else
ToolTip
}
SoundBeep
Return
Esc:: ExitApp |
... but I should really update the script to use GetMenuItemInfo:
| MSDN wrote: | | [The GetMenuString function has been superseded. Use the GetMenuItemInfo function to retrieve the menu item text.] |
I (and hopefully some others, too) tested my script (as all scripts I've posted), but not well enough.
Thanks to SifJar for reporting. _________________ Pekka Vartto |
|
| Back to top |
|
 |
TLM
Joined: 21 Aug 2006 Posts: 2926 Location: The Shell
|
Posted: Tue May 18, 2010 4:30 pm Post subject: |
|
|
I have yet to get the initial message to not return false.
I think it has something to do with the class name.
For some reason the system I work on does not see #327 series classes.
I had to use GetDesktopWindow to get the upper most handle here.
| Code: | setformat, integerFast, H
msgbox % dllCall("GetDesktopWindow")+0 | Rather than just being able to call it from the classname 32769
I'm assuming this is why the message is not working.. _________________
paradigm.shift:=(•_•)┌П┐RTFM||^.*∞ |
|
| Back to top |
|
 |
SifJar
Joined: 13 Feb 2010 Posts: 170
|
Posted: Tue May 18, 2010 5:55 pm Post subject: |
|
|
svi: Does this line:
| Code: | | SendMessage, 0x1E1, 0, 0, , ahk_class #32768 |
set error level to the current menu item's handle? I'm trying to understand your code...
EDIT: If you could also explain this line, it'd be handy:
| Quote: |
If (fState & 0x80 And sString) |
I think I understand that it checks if fState and sString exist, but why the " & 0x80 "?
One last query: Why do you need to get the MenuInfo, if it seems it is not used? Why not just GetMenuString, then use it?
Last edited by SifJar on Tue May 18, 2010 6:14 pm; edited 1 time in total |
|
| Back to top |
|
 |
TLM
Joined: 21 Aug 2006 Posts: 2926 Location: The Shell
|
Posted: Tue May 18, 2010 6:12 pm Post subject: |
|
|
| SifJar wrote: | svi: Does this line:
| Code: | | SendMessage, 0x1E1, 0, 0, , ahk_class #32768 |
set error level to the current menu item's handle? I'm trying to understand your code... |
Yes it does if all goes well. It returns the handle for the class name.
If not it returns fail. _________________
paradigm.shift:=(•_•)┌П┐RTFM||^.*∞ |
|
| Back to top |
|
 |
SifJar
Joined: 13 Feb 2010 Posts: 170
|
Posted: Tue May 18, 2010 6:18 pm Post subject: |
|
|
| TLM wrote: | | SifJar wrote: | svi: Does this line:
| Code: | | SendMessage, 0x1E1, 0, 0, , ahk_class #32768 |
set error level to the current menu item's handle? I'm trying to understand your code... |
Yes it does if all goes well. It returns the handle for the class name.
If not it returns fail. |
i.e. returns the handle for a menu, generally, not a specific item? OK. Not 100% sure what a handle is, but I think I get the idea. If you happen to know the answers to my other questions I added to my post, it'd be great if you could answer...Thanks for answering that one btw.
EDIT: Also, am I correct in thinking any variable in a struct like this one is 4 bytes long? I'm trying to understand DLL calls here too... |
|
| Back to top |
|
 |
TLM
Joined: 21 Aug 2006 Posts: 2926 Location: The Shell
|
Posted: Tue May 18, 2010 6:43 pm Post subject: |
|
|
| SifJar wrote: | | Not 100% sure what a handle is, but I think I get the idea. | A handle is a HEX ID for a window, menu etc in memory.
| SifJar wrote: | | EDIT: Also, am I correct in thinking any variable in a struct like this one is 4 bytes long? I'm trying to understand DLL calls here too... |
To understand structs you should 1st understand pointers (text from C++ ebook).
To your question of adding variables to a struct, yes each member can be added per 4bytes like this:
| Code: | ; Rect struct
VarSetCapacity( R, 16, 0 )
NumPut( x1, &R+0 )
NumPut( y1, &R+4 )
NumPut( x2, &R+8 )
NumPut( y2, &R+12) |
Just remember to call the pointer from memory with a preceding &. _________________
paradigm.shift:=(•_•)┌П┐RTFM||^.*∞ |
|
| Back to top |
|
 |
SifJar
Joined: 13 Feb 2010 Posts: 170
|
Posted: Tue May 18, 2010 6:56 pm Post subject: |
|
|
| TLM wrote: |
To your question of adding variables to a struct, yes each member can be added per 4bytes like this:
| Code: | ; Rect struct
VarSetCapacity( R, 16, 0 )
NumPut( x1, &R+0 )
NumPut( y1, &R+4 )
NumPut( x2, &R+8 )
NumPut( y2, &R+12) |
Just remember to call the pointer from memory with a preceding &. |
Instead of writing say could you also use | Code: | | NumPut( x1, &R, 0 ) |
This seems to be what svi did in his code, when adding members to "mii" (MenuItemInfo), are they pretty much the same thing, or is there an important difference?
EDIT: Also, he didn't have the preceding &, he just had the variable name, and according the to help file, that works better...
EDIT: I 'm fairly sure I understand pointers now, do you know of a good reference for structs?
EDIT: I found some stuff, I think I kinda get the idea.A struct contains a set of members and each member is a pointer to a variable, am I right? |
|
| Back to top |
|
 |
|
|
You can post new topics in this forum You can reply to topics in this forum
|
Powered by phpBB © 2001, 2005 phpBB Group
|