 |
AutoHotkey Community Let's help each other out
|
| View previous topic :: View next topic |
| Author |
Message |
animatorgeek
Joined: 28 Oct 2009 Posts: 3
|
Posted: Wed Oct 28, 2009 11:32 pm Post subject: Selecting a menu item by name in a non-standard app menu |
|
|
I've got a program in which I'd like to automate some stuff, but it uses a non-standard app menu. (I'm talking about Adobe Flash CS4 here, which seems to use an Adobe version of OWL for its GUI.) I can open the menu by sending alt-C (the menu's hotkey) but the only way I've found to activate an entry in the menu is via PostMessage. That won't work for me, since this menu is built dynamically and the entries may change from run to run (and even while running). Thus, I'd like to call the menu items by name.
I've seen other posts that deal with finding the name, hotkey and command ID of a menu, but I'm not sure how to apply it to this situation. I think I'd first need the hWnd for the popup menu but I'm not even sure how to find that.
Can anyone give me any suggestions? |
|
| Back to top |
|
 |
txquestor
Joined: 22 Aug 2009 Posts: 294
|
Posted: Thu Oct 29, 2009 1:59 am Post subject: |
|
|
Try this:
| Code: |
; Adobe Flash CS4 Menuitem
; TESTED
#InstallKeybdHook
^!a::
ifWinExist, Adobe Flash CS4 ; WinTitle OR use Ahk_Class Abobe Flash ClassNN
WinActivate
sleep 200
SendInput {Alt} ; Activates "File" Menu
sleep 300
SendInput {Enter 2} ; Activates "Open" menuitem
return
|
_________________
"Man's quest for knowledge is an expanding series whose limit is infinity" |
|
| Back to top |
|
 |
mstorey20
Joined: 21 Sep 2009 Posts: 10
|
Posted: Thu Oct 29, 2009 2:02 am Post subject: |
|
|
I may be off and too simple but you've gone to the Window Spy - right click on the ahk tray icon and looked at the info you can get there:
| Code: |
>>>>>>>>>>( Window Title & Class )<<<<<<<<<<<
ahk_class Shell_TrayWnd
>>>>>>>>>>>>( Mouse Position )<<<<<<<<<<<<<
On Screen: 57, 146 (less often used)
In Active Window: 57, -874
>>>>>>>>>( Now Under Mouse Cursor )<<<<<<<<
Color: 0xCEF0F0 (Blue=CE Green=F0 Red=F0)
>>>>>>>>>>( Active Window Position )<<<<<<<<<<
left: 0 top: 1020 width: 1680 height: 30
>>>>>>>>>>>( Status Bar Text )<<<<<<<<<<
>>>>>>>>>>>( Visible Window Text )<<<<<<<<<<<
9:00 PM
Notification Area
System Control Area
Quick Launch
Running Applications
Running Applications
>>>>>>>>>>>( Hidden Window Text )<<<<<<<<<<<
>>>>( TitleMatchMode=slow Visible Text )<<<<
>>>>( TitleMatchMode=slow Hidden Text )<<<<
|
I think the answer would be the Now Under Mouse Cursor when you move your cursor around the gui.
That said, I have an issue where I'm trying to work in a Citrix environment and it doesn't show me any real usable data. If that doesn't work, then maybe your solution will help me too. _________________ StoreyQuickNotes - An AutoHotkey Project for Radiation Oncology |
|
| Back to top |
|
 |
animatorgeek
Joined: 28 Oct 2009 Posts: 3
|
Posted: Thu Oct 29, 2009 2:13 am Post subject: |
|
|
I guess I didn't state my problem clearly. I can activate a popup menu by sending alt-C (it looks like a standard app menu but doesn't respond to WinMenuSelectItem, so I don't think it's a standard app menu). I then need to select a menu item BY NAME, not by its position in the popup menu. I can't rely on the position to stay the same between runs.
So I think I need to: get a handle to the currently focused popup menu and select an item on that menu based solely on the text of the item. If there's some simpler way to go about this I welcome any suggestions. |
|
| Back to top |
|
 |
txquestor
Joined: 22 Aug 2009 Posts: 294
|
Posted: Thu Oct 29, 2009 8:03 pm Post subject: |
|
|
Selecting a menu item by name in a non-standard app menu
Your description is confusing.
The script I provided doesn't need the Name or ID of the menuitem as
long as it doesn't change position.
1st you state you are working with an application, Adobe Flash CS4
& you want to activate a popup menuitem by NAME because
WinSelectMenuitem & Postmeessage doesn't work or does work? Not clear
You stated it changes dynamically. Does the menuitem change position or the Names change?
If the Names change you can use the script. You just need to figure out
where to move in the menu.
If the position changes, the script will not work.
But you could replace it with a Loop to move until you find the Name.
You can get the ClassNN & ID of a Window or Control in some situations.
But you need to do some tests to determine if this is the case.
Use this code to see if you can get the ClassNN & ID.
| Code: |
; Gets all Controls in a selected Window by their ID & Class
#Persistent
SetTitleMatchMode, 2
DetectHiddenWindows=On
; CTRL+Win+c to activate script
^#c::
SetTimer, WatchMouse, 200
return
WatchMouse:
ChildHWND := GetChildHWND(MouseWin, MouseControl)
WinGet, WinActive_id, ID, A
WinGetActiveTitle, WinTitle
MouseGetPos,,, MouseWin, MouseControl
; Get the list of controls for active window
; Figure out how to get it's title or text from it's ID
WinGet, ControlList, ControlList, A
Loop, Parse, ControlList, `n
{
MsgBox, 4,, Control #%a_index% is "%A_LoopField%". Continue?
IfMsgBox, No
break
}
; All Commented lines work & get various values of window & controls by uncommenting
;
;ToolTip % GetChildHWND(MouseWin, MouseControl) ; VERIFIED
; Move Mouse Over Control to gets it's HWND - displays after last Control is listed
; ToolTip %Directions% WinActive_id = %WinActive_id% WinTitle = %WinTitle% `n ParentHWND = %MouseWin% `nControlHWND = %ChildHWND% ; VERIFIED
Clipboard = WinActive_id = %WinActive_id% `nWinTitle = %WinTitle% `nParentHWND = %MouseWin% `nControlHWND = %ChildHWND% ; VERIFIED
WinCtlInfo = %Clipboard%
;MsgBox,, Window+Control Info, %WinCtlInfo% ; VERIFIED
ToolTip Window+Control Info `n-------------------------- `n%WinCtlInfo% ; VERIFIED
;MsgBox % GetChildHWND(MouseWin, MouseControl) ; VERIFIED
;MsgBox Window ID= %ChildHWND% ; VERIFIED
return
; The above just serve to demonstrate how to call the following function and display the results
GetChildHWND(ParentWindowID, ChildClassNN)
{
; Returns a blank value if parent or child doesn't exist.
; Otherwise, the HWND is returned.
WinGetPos, ParentX, ParentY,,, ahk_id %ParentWindowID%
if ParentX =
return ; Parent window not found (possibly due to DetectHiddenWindows).
ControlGetPos, ChildX, ChildY,,, %ChildClassNN%, ahk_id %ParentWindowID%
if ChildX =
return ; Child window not found.
; Convert child coordinates -- which are relative to its parent's upper left
; corner -- to absolute/screen coordinates for use with WindowFromPoint().
; The following INTENTIONALLY passes too many args to the function because
; each arg is 32-bit, which allows the function to automatically combine
; them into one 64-bit arg (the POINT struct):
return DllCall("WindowFromPoint", "int", ChildX + ParentX, "int", ChildY + ParentY)
}
return
|
You can get the HWND of a Window or Control in some situations.
But you need to do some tests to determine if this is the case.
Use this code to see if you can get the HWND by Mouseover control.
| Code: |
; Gets all Controls in a selected Window by their HWND by Mouseover control
#Persistent
SetTitleMatchMode, 2
DetectHiddenWindows=On
; CTRL+Win+h to activate script
^#h::
SetTimer, WatchMouse, 200
return
WatchMouse:
ChildHWND := GetChildHWND(MouseWin, MouseControl)
WinGet, WinActive_id, ID, A
WinGetActiveTitle, WinTitle
MouseGetPos,,, MouseWin, MouseControl
; All Commented lines work & get various values of window & controls by uncommenting
;
;ToolTip % GetChildHWND(MouseWin, MouseControl) ; VERIFIED
; Move Mouse Over Control to gets it's HWND
ToolTip Move Mouse Over Control to gets it's HWND`nWinActive_id = %WinActive_id% WinTitle = %WinTitle% `n ParentHWND = %MouseWin% `nControlHWND = %ChildHWND% ; VERIFIED
Clipboard = WinActive_id = %WinActive_id% `nWinTitle = %WinTitle% `nParentHWND = %MouseWin% `nControlHWND = %ChildHWND% ; VERIFIED
WinCtlInfo = %Clipboard%
;MsgBox,, Window+Control Info, %WinCtlInfo% ; VERIFIED
ToolTip Window+Control Info `n-------------------------- `n%WinCtlInfo% ; VERIFIED
;MsgBox % GetChildHWND(MouseWin, MouseControl) ; VERIFIED
;MsgBox Window ID= %ChildHWND% ; VERIFIED
return
; The above just serve to demonstrate how to call the following function and display the results
GetChildHWND(ParentWindowID, ChildClassNN)
{
; Returns a blank value if parent or child doesn't exist.
; Otherwise, the HWND is returned.
WinGetPos, ParentX, ParentY,,, ahk_id %ParentWindowID%
if ParentX =
return ; Parent window not found (possibly due to DetectHiddenWindows).
ControlGetPos, ChildX, ChildY,,, %ChildClassNN%, ahk_id %ParentWindowID%
if ChildX =
return ; Child window not found.
; Convert child coordinates -- which are relative to its parent's upper left
; corner -- to absolute/screen coordinates for use with WindowFromPoint().
; The following INTENTIONALLY passes too many args to the function because
; each arg is 32-bit, which allows the function to automatically combine
; them into one 64-bit arg (the POINT struct):
return DllCall("WindowFromPoint", "int", ChildX + ParentX, "int", ChildY + ParentY)
}
return
|
This script gets the HWND of a ContextMenu:
| Code: |
IfWinNotExist ahk_class #32768
{
ToolTip
Return
}
WinGet,ControlHwnd, ID,ahk_class #32768
|
Here is a script that works on Contestmenu's (popup).
Not sure if it works put worth a try.
http://www.autohotkey.com/forum/topic21451.html
Follow the directions in the post & use whatever script applies.
But then you state in a 2nd post, you are running in a Citrix environment.
You need to give more details.
You also said you thought I got the ClassNN by going to the system tray
and running Window Spy. I didn't say that.
I told you to use whatever the ClassNN or it's WinTitle of the popup of the app you running.
Here is what you need to determine in order to get the correct help:
1. What is name of the application which contains the popup menu you are trying to control.
2. Are you running Windows? What version? in a Citrix environment?
If When you state you running in a Citrix environment.
Is this a Citrix Window or a Windows Gui?
What does that mean? Explain in detail.
3. If you use Window Spy on the main app window,
What is the Name & ClassNN of it's window
What is the Name & ClassNN of the popup menuitem
4. You stated WinMenuSelectItem doesn't work.
What happens?
Post your code to test WinMenuSelectItem.
5. Did you try to use Postmessage?
What happen?
Post your code.
How did you get the ID of the Menuitem to use PM?
Did you use WinInspector? or other method.
If Postmessage will work on your app, then there is a way to get the menuitem NAME & ID.
If postmessage FAILS in all situations. Then the method I have in mind will not work.
6. Until you answer these questions, I can't help you.
Good Luck,  _________________
"Man's quest for knowledge is an expanding series whose limit is infinity" |
|
| Back to top |
|
 |
animatorgeek
Joined: 28 Oct 2009 Posts: 3
|
Posted: Fri Oct 30, 2009 5:00 pm Post subject: |
|
|
There's some confusion going on here. I only posted the messages that say they're from Animatorgeek. Mstorey20 made the comment that mentions Citrix (which I've never heard of before today).
I'll clarify some more. It's a standard popup menu control. I know this because I had already tried your tool that gives tooltips for the class and ID and stuff. I also was able to detect the WM_COMMAND message that these menu entries emitted when selected. So, theoretically, I could use PostMessage.
The problem is that the order of the items in the menu might change. That is, they're always alphabetical, but entries might be added or taken away. This menu basically lists all the files of a particular type in a particular directory.
Using Winspector I discovered that each entry on the menu sends a WM_COMMAND message with an ID of 50000 + n, where n is the position of the item on the menu. If a new item gets added then all the ones below it will now have a different ID.
WinMenuSelectItem appeared to do nothing when I tried to use it. I don't have the original source code from when I tried that, though I'm confident I used it correctly.
Using Window Spy I see that the Flash CS4 menu bar has a ClassNN of OWL.MenuBar1. The popup menu is AfxFrameOrView80u1. |
|
| Back to top |
|
 |
txquestor
Joined: 22 Aug 2009 Posts: 294
|
Posted: Fri Oct 30, 2009 7:01 pm Post subject: |
|
|
The only way I know to do this is to get all the Names & IDs of the popup menu items with this script.
How to use;
Open the app with the Popup Menu of choice.
Run the script.
Activate the Popup Menu and select any menuitem.
It displays the list on the upper left side of the screen.
| Code: |
; Micha wrote this script for me when I needed something similar.
#NoEnv ; Recommended for performance and compatibility with future AutoHotkey releases.
SendMode Input ; Recommended for new scripts due to its superior speed and reliability.
#Persistent
SetTimer, Demo, 500
return
Demo:
; Constants
MFS_HILITE = 0x80
WinGet hWnd, ID, A
hMenu := DllCall("GetMenu", "UInt", hWnd) ; VERIFIED for TextText
MouseGetPos, MouseScreenX, MouseScreenY, MouseWindowUID, MouseControlID
WinGet,ControlHwnd, ID,ahk_id %MouseWindowUID%
;Get count of menu items
ContextMenCnt := GetContextMenuCount(ControlHwnd)
if ContextMenCnt < 1
{
Tooltip,
}
;Read info for each menu item on IsEnabled & Highlight
loop, %ContextMenCnt%
{
; Get the Menuitem State & send Message to Menuitem to retrieve the "GetMenuItemID" value
IsEnabled := GetContextMenuState(ControlHwnd, a_index-1)
SendMessage, 0x01E1, , , , ahk_id %ControlHwnd%
; Errorlevel is set by SendMessage. It contains the ID handle to the menuitem not the same as wID in MIIM_ID from GetMenuItemInfo Function ()
;MenuitemID := DllCall( "GetMenuItemID", "UInt", ErrorLevel, "Int", a_index-1 ) ; ONLY Shows Name & Hotkey
{
TextText =
if (IsEnabled & MFS_HILITE) ; If Menuitem is Enabled & Highlighted get the Values for it's ID, Name & Hotkey
;MenuitemID := DllCall( "GetMenuItemID", "UInt", ErrorLevel, "Int", a_index-1 ) ; ONLY MenuitemID - Name & Hotkey only shows last menu item in menu selected
StrSize := GetContextMenuText(ControlHwnd, a_index-1) ; Gets the Name & Hotkey for a Menuitem
TextText = %TextText%%a_index%:%StrSize%`n
StringTrimLeft, TextText, TextText, 2
StringReplace, TextText, TextText, `:, , All
;TextText =%MenuitemID% %TextText% ; Used to Append MenuitemID to TextText
}
}
CoordMode, Tooltip, Screen
Tooltip, % TextText ? "ID" . MenuitemID " : " . TextText : "", ; Displays Menuitem's Name & Hotkey ID does NOT change
;Tooltip, %TextText% ; Used when MenuitemID is appended to TextText above
return
ESC::ExitApp
/***************************************************************
* Micha - Returns the TEXT of a menu entry (standard, context, popup & ahk menus only!!!)
***************************************************************
*/
GetContextMenuText(hWnd, Position)
{
WinGetClass, WindowClass, ahk_id %hWnd%
if WindowClass <> #32768 ; Get Class of Menu/Menuitem
{
return -1
}
SendMessage, 0x01E1, , , , ahk_id %hWnd%
;Errorlevel is set by SendMessage. It contains the handle to the menu
hMenu := errorlevel
;Allocate a struct for MenuItemInfo. It contains all the data of a Menu/Menuitem
VarSetCapacity(MenuItemInfo, 200, 0)
;Set Size of Struct [48] to the first member
InsertInteger(48, MenuItemInfo, 0, 4)
;Retrieve string MIIM_STRING = 0x40 = 64 (/ MIIM_TYPE = 0x10 = 16)
InsertInteger(64, 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)
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)
;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 ;-)
InsertInteger(GetMenuItemInfoRes, MenuItemInfo, 40, 4)
InsertInteger(&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
}
/***************************************************************
* Micha - returns the count of menu items
***************************************************************
*/
GetContextMenuCount(hWnd)
{
WinGetClass, WindowClass, ahk_id %hWnd%
;All popups should have the window class #32768
if WindowClass <> #32768
{
return 0
}
;Retrieve menu handle from window
SendMessage, 0x01E1, , , , ahk_id %hWnd%
;Errorlevel is set by SendMessage. It contains the handle to the menu
hMenu := errorlevel
menuitemcount:=DllCall("GetMenuItemCount",UInt,hMenu)
Return, menuitemcount
}
/***************************************************************
* Micha - returns the state of a menu entry
***************************************************************
*/
GetContextMenuState(hWnd, Position)
{
WinGetClass, WindowClass, ahk_id %hWnd%
if WindowClass <> #32768
{
return -1
}
SendMessage, 0x01E1, , , , ahk_id %hWnd%
;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
InsertInteger(48, MenuItemInfo, 0, 4)
;Get only Flags from dllcall GetMenuItemInfo MIIM_TYPE = 1
InsertInteger(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)
return GetMenuItemInfoRes
}
;***************************************************************
; Majkinetor's - Get Menu's MenuitemInfo
;***************************************************************
GetMenu(hMenu)
{
Loop, % DllCall("GetMenuItemCount", "Uint", hMenu)
{
idx := A_Index - 1
idn := DllCall("GetMenuItemID", "Uint", hMenu, "int", idx)
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) ;MF_BYPOSITION
If !sString
sString := "---------------------------------------"
sContents .= idx . " : " . idn . A_Tab . A_Tab . sString . "`n"
;msgbox idn=%idn%
If (idn = -1) && (hSubMenu := DllCall("GetSubMenu", "Uint", hMenu, "int", idx))
sContents .= GetMenu(hSubMenu)
}
Return sContents
}
;/*
; *********************************
; Original versions of ExtractInteger and InsertInteger provided by Chris
; - from the AutoHotkey help file - Version 1.0.37.04
; *********************************
; *********************************
ExtractInteger(ByRef pSource, pOffset = 0, pIsSigned = false, pSize = 4)
; pSource is a string (buffer) whose memory area contains a raw/binary integer at pOffset.
; The caller should pass true for pSigned to interpret the result as signed vs. unsigned.
; pSize is the size of PSource's integer in bytes (e.g. 4 bytes for a DWORD or Int).
; pSource must be ByRef to avoid corruption during the formal-to-actual copying process
; (since pSource might contain valid data beyond its first binary zero).
{
SourceAddress := &pSource + pOffset ; Get address and apply the caller's offset.
result := 0 ; Init prior to accumulation in the loop.
Loop %pSize% ; For each byte in the integer:
{
result := result | (*SourceAddress << 8 * (A_Index - 1)) ; Build the integer from its bytes.
SourceAddress += 1 ; Move on to the next byte.
}
if (!pIsSigned OR pSize > 4 OR result < 0x80000000)
return result ; Signed vs. unsigned doesn't matter in these cases.
; Otherwise, convert the value (now known to be 32-bit) to its signed counterpart:
return -(0xFFFFFFFF - result + 1)
}
InsertInteger(pInteger, ByRef pDest, pOffset = 0, pSize = 4)
; To preserve any existing contents in pDest, only pSize number of bytes starting at pOffset
; are altered in it. The caller must ensure that pDest has sufficient capacity.
{
mask := 0xFF ; This serves to isolate each byte, one by one.
Loop %pSize% ; Copy each byte in the integer into the structure as raw binary data.
{
DllCall("RtlFillMemory", UInt, &pDest + pOffset + A_Index - 1, UInt, 1 ; Write one byte.
, UChar, (pInteger & mask) >> 8 * (A_Index - 1)) ; This line is auto-merged with above at load-time.
mask := mask << 8 ; Set it up for isolation of the next byte.
}
}
|
Then use it to get the Name/ID of the menuitem of choice.
Since you are saying you want it by NAME, does this mean you know which one you want?
If, so then you can either manually or with an ahk script take your menuitem choice and
save it to a VAR, then write a Postmessage routine that uses VARs instead of LITERALs to activate the menuitem.
You can add it to this script since it gets the Popup Menu's entire list of menuitems NAMES & IDs.
Go to the line in the script that displays the list.
| Code: | | Tooltip, % TextText ? "ID" . MenuitemID " : " . TextText : "", | it contains the VAR's you need. _________________
"Man's quest for knowledge is an expanding series whose limit is infinity" |
|
| 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
|