Alt Menu Search

Post your working scripts, libraries and tools for AHK v1.1 and older
lexikos
Posts: 9592
Joined: 30 Sep 2013, 04:07
Contact:

Alt Menu Search

07 Jun 2015, 22:52

Alt Menu Search

Press and release Alt and then type to search all of the active window's menus.

As you type, the list is narrowed down to rows containing what you've typed. There is one row for each menu item, and the row text includes the menu and sub-menu names. For example, typing "enco" with Notepad++ would show all items in the "Encoding" menu and its sub-menus.

Pressing the number indicated in the left column selects that item, and pressing Enter sends the appropriate command to the window.

If no item is selected, pressing Enter sends the command corresponding to item 1.

Limitations:
  • Only works with applications that use standard Windows menus.
  • It is currently impossible to type a number directly into the search box.
menu-search.png
menu-search.png (63.15 KiB) Viewed 4567 times

Code: Select all

; menu-search v1.0 -- http://ahkscript.org/boards/viewtopic.php?f=6&t=8085

SetBatchLines -1
OnMessage(0x100, "GuiKeyDown")
OnMessage(0x6, "GuiActivate")
return

WinHasMenu(WinTitle:="") {
    return !!DllCall("GetMenu", "ptr", WinExist(WinTitle), "ptr")
}
#If WinHasMenu("A") || WinActive("Searching menus of: ahk_class AutoHotkeyGUI")

Alt::
Gui +LastFoundExist
if WinActive()
    goto GuiEscape
Gui Destroy
Gui Font, s11
Gui Margin, 0, 0
Gui Add, Edit, x20 w500 vQuery gType
Gui Add, Text, x5 y+2 w15, 1`n2`n3`n4`n5`n6`n7`n8`n9
Gui Add, ListBox, x+0 yp-2 w500 r21 vCommand gSelect AltSubmit
Gui Add, StatusBar
Gui +ToolWindow +Resize +MinSize +MinSize200x +MaxSize +MaxSize%A_ScreenWidth%x
window := WinExist("A")
cmds := MenuGetAll(window)
gosub Type
WinGetTitle title, ahk_id %window%
title := RegExReplace(title, ".* - ")
Gui Show,, Searching menus of:  %title%
GuiControl Focus, Query
return

Type:
SetTimer Refresh, -10
return

Refresh:
GuiControlGet Query
r := cmds
if (Query != "")
{
    StringSplit q, Query, %A_Space%
    Loop % q0
        r := Filter(r, q%A_Index%, c)
}
rows := ""
row_id := []
Loop Parse, r, `n
{
    RegExMatch(A_LoopField, "(\d+)`t(.*)", m)
    row_id[A_Index] := m1
    rows .= "|"  m2
}
GuiControl,, Command, % rows ? rows : "|"
if (Query = "")
    c := row_id.MaxIndex()

Select:
GuiControlGet Command
if !Command
    Command := 1
Command := row_id[Command]
SB_SetText("Total " c " results`t`tID: " Command)
if (A_GuiEvent != "DoubleClick")
    return

Confirm:
if !GetKeyState("Shift")
{
    gosub GuiEscape
    WinActivate ahk_id %window%
}
DllCall("SendNotifyMessage", "ptr", window, "uint", 0x111, "ptr", Command, "ptr", 0)
return

GuiEscape:
Gui Destroy
cmds := r := ""
return

GuiSize:
GuiControl Move, Query, % "w" A_GuiWidth-20
GuiControl Move, Command, % "w" A_GuiWidth-20
return

GuiActivate(wParam)
{
    if (A_Gui && wParam = 0)
        SetTimer GuiEscape, -5
}

GuiKeyDown(wParam, lParam)
{
    if !A_Gui
        return
    if (wParam = GetKeyVK("Enter"))
    {
        gosub Confirm
        return 0
    }
    if (wParam = GetKeyVK(key := "Down")
     || wParam = GetKeyVK(key := "Up"))
    {
        GuiControlGet focus, FocusV
        if (focus != "Command")
        {
            GuiControl Focus, Command
            if (key = "Up")
                Send {End}
            else
                Send {Home}
            return 0
        }
        return
    }
    if (wParam >= 49 && wParam <= 57 && !GetKeyState("Shift"))
    {
        SendMessage 0x18E,,, ListBox1
        GuiControl Choose, Command, % wParam-48 + ErrorLevel
        GuiControl Focus, Command
        gosub Select
        return 0
    }
    if (wParam = GetKeyVK(key := "PgUp")
     || wParam = GetKeyVK(key := "PgDn"))
    {
        GuiControl Focus, Command
        Send {%key%}
        return
    }
}

Filter(s, q, ByRef count)
{
    if (q = "")
    {
        StringReplace s, s, `n, `n, UseErrorLevel
        count := ErrorLevel
        return s
    }
    i := 1
    match := ""
    result := ""
    count := 0
    while i := RegExMatch(s, "`ami)^.*\Q" q "\E.*$", match, i + StrLen(match))
    {
        result .= match "`n"
        count += 1
    }
    return SubStr(result, 1, -1)
}

MenuGetAll(hwnd)
{
    if !menu := DllCall("GetMenu", "ptr", hwnd, "ptr")
        return ""    
    MenuGetAll_sub(menu, "", cmds)
    return cmds
}

MenuGetAll_sub(menu, prefix, ByRef cmds)
{
    Loop % DllCall("GetMenuItemCount", "ptr", menu)
    {
        VarSetCapacity(itemString, 2000)
        if !DllCall("GetMenuString", "ptr", menu, "int", A_Index-1, "str", itemString, "int", 1000, "uint", 0x400)
            continue
        itemString := StrReplace(itemString, "&")
        itemID := DllCall("GetMenuItemID", "ptr", menu, "int", A_Index-1)
        if (itemID = -1)
        if subMenu := DllCall("GetSubMenu", "ptr", menu, "int", A_Index-1, "ptr")
        {
            MenuGetAll_sub(subMenu, prefix itemString " > ", cmds)
            continue
        }
        cmds .= itemID "`t" prefix RegExReplace(itemString, "`t.*") "`n"
    }
}
PostMessage

The ID of the selected menu item is shown in the bottom right-hand corner of the window. This ID can be used with PostMessage to activate the menu item directly. For example, this activates Notepad++'s "Encode in UTF-8 without BOM" option shown in the screenshot above:

Code: Select all

PostMessage 0x111, 45008,,, ahk_class Notepad++
tmplinshi
Posts: 1604
Joined: 01 Oct 2013, 14:57

Re: Alt Menu Search

08 Jun 2015, 00:38

Thank you! :D This is really useful. I have this idea in mind after using the Sublime Text's "Command Palette", but can't write it myself.
haichenatwork

Re: Alt Menu Search

08 Jun 2015, 01:33

Wow, very very useful. Can this also be done for trayicon/traybar-menus? Especially for getting the messagenumbers?
lexikos
Posts: 9592
Joined: 30 Sep 2013, 04:07
Contact:

Re: Alt Menu Search

08 Jun 2015, 02:17

No, because tray icons don't have menus. An application can show a popup menu in response to a mouse event on the icon, but there's nothing connecting that menu to the tray icon. The same is generally true for any context/popup menu.

This script works because GetMenu can be used to retrieve the (standard) menu of a window.
haichenatwork

Re: Alt Menu Search

08 Jun 2015, 02:49

Thanks for clarification. If i understand this correct i also cant send a message to a tray icon "menu" because there is no message connected to the "menu"? I only can send Mouseclicks like in trayicon.ahk?
lexikos
Posts: 9592
Joined: 30 Sep 2013, 04:07
Contact:

Re: Alt Menu Search

08 Jun 2015, 03:10

You can't send messages to a menu, in the sense of GetMenu. Menus are not windows.
You can send messages to a menu window (the thing you see on screen), but only while it is visible (i.e. it exists).
You can send messages to the window which owns the menu, which is what this script does.

I've seen a version of TrayIcon.ahk which returned the HWND and message number of each tray icon, but this is only useful for duplicating the messages that the tray itself sends (i.e. mouse click/move). When a program displays a popup menu, it can set whatever window it wants as the owner. For instance, when you use the Menu command, AutoHotkey generally sets its own main window (A_ScriptHwnd) as the owner.

As another example, the program JoyToKey has a main window which it presents to the user, and four hidden unnamed utility windows. One of these utility windows owns the tray icon, so that's where the tray sends messages when you click the icon. It's possible to retrieve the HWND of this window from the tray itself. When you click the icon, it shows a popup menu, but the menu is owned by a different utility window. Posting a WM_COMMAND message to the wrong window has no effect. If you find the right window, you can send it menu messages directly.

However, TrackPopupMenuEx, which is used to display popup menus, has two modes (which are not mutually exclusive):
  • Post a WM_COMMAND message to the owner window. This you can emulate.
  • Return the ID or position of the menu item (as in: function return value).
If the program uses the latter method, posting messages isn't going to do anything even if you find the right window.
User avatar
rommmcek
Posts: 1475
Joined: 15 Aug 2014, 15:18

Re: Alt Menu Search

26 Jun 2015, 11:07

Hi, to everyone & specially to lexikos

Old idea packed in a new bloody conducive app!!!
(Seek by Phi https://www.autohotkey.com/docs/scripts ... tMenu).htm & Search by HotKeyIt http://ahkscript.org/boards/viewtopic.p ... rch#p47315 - I guess he had this already on the shelf)

I made some changes. I know they might be redundant, but I missed them!

Press 0 to select row 10.
Press Shift+Number to select row 11 to 20.
And I enabled vertical resize. It should be row quantized - maybe some other time.

Code: Select all

; menu-search v1.0 -- http://ahkscript.org/boards/viewtopic.php?f=6&t=8085

SetBatchLines -1
OnMessage(0x100, "GuiKeyDown")
OnMessage(0x6, "GuiActivate")
return

WinHasMenu(WinTitle:="") {
    return !!DllCall("GetMenu", "ptr", WinExist(WinTitle), "ptr")
}
#If WinHasMenu("A") || WinActive("Searching menus of: ahk_class AutoHotkeyGUI")

Alt::
Gui +LastFoundExist
if WinActive()
    goto GuiEscape
Gui Destroy
Gui Font, s11 cBlue                                                                             ; <----- modified by RRR
Gui Margin, 0, 0
Gui Add, Edit, x20 w500 vQuery gType
Gui Add, Text, x5 y+2 w15, 1`n2`n3`n4`n5`n6`n7`n8`n9`n0                                         ; <----- modified by RRR
Gui, Font, cRed                                                                                 ; <----- added by RRR
Gui Add, Text, x5 y+0 w15, 1`n2`n3`n4`n5`n6`n7`n8`n9`n0                                         ; <----- added by RRR
Gui Font, cBlack                                                                                ; <----- added by RRR
Gui Add, ListBox, x+0 y25 w500 r21 vCommand gSelect AltSubmit
Gui Add, StatusBar
Gui +ToolWindow +Resize +MinSize +MinSize200 +MaxSize +MaxSize%A_ScreenWidth%x%A_ScreenHeight%  ; <----- modified by RRR
window := WinExist("A")
cmds := MenuGetAll(window)
gosub Type
WinGetTitle title, ahk_id %window%
title := RegExReplace(title, ".* - ")
Gui Show,, Searching menus of:  %title%
GuiControl Focus, Query
return

Type:
SetTimer Refresh, -10
return

Refresh:
GuiControlGet Query
r := cmds
if (Query != "")
{
    StringSplit q, Query, %A_Space%
    Loop % q0
        r := Filter(r, q%A_Index%, c)
}
rows := ""
row_id := []
Loop Parse, r, `n
{
    RegExMatch(A_LoopField, "(\d+)`t(.*)", m)
    row_id[A_Index] := m1
    rows .= "|"  m2
}
GuiControl,, Command, % rows ? rows : "|"
if (Query = "")
    c := row_id.MaxIndex()

Select:
GuiControlGet Command
if !Command
    Command := 1
Command := row_id[Command]
SB_SetText("Total " c " results`t`tID: " Command)
if (A_GuiEvent != "DoubleClick")
    return

Confirm:
if !GetKeyState("Shift")
{
    gosub GuiEscape
    WinActivate ahk_id %window%
}
DllCall("SendNotifyMessage", "ptr", window, "uint", 0x111, "ptr", Command, "ptr", 0)
return

GuiEscape:
Gui Destroy
cmds := r := ""
ToolTip,,,,1
ToolTip,,,,2
return

GuiSize:
GuiControl Move, Query, % "w" A_GuiWidth-20
GuiControl Move, Command, % "w" A_GuiWidth-20 "h" A_GuiHeight-35
return

GuiActivate(wParam)
{
    if (A_Gui && wParam = 0)
        SetTimer GuiEscape, -5
}

GuiKeyDown(wParam, lParam)
{
    if !A_Gui
        return
    if (wParam = GetKeyVK("Enter"))
    {
        gosub Confirm
        return 0
    }
    if (wParam = GetKeyVK(key := "Down")
     || wParam = GetKeyVK(key := "Up"))
    {
        GuiControlGet focus, FocusV
        if (focus != "Command")
        {
            GuiControl Focus, Command
            if (key = "Up")
                Send {End}
            else
                Send {Home}
            return 0
        }
        return
    }
    if (wParam >= 48 && wParam <= 57) ; && !GetKeyState("Shift"))       ; <----- modified by RRR
    {        
        if GetKeyState("Shift","P")                                     ; <----- added by RRR
        wParam += 10                                                    ; <----- added by RRR
        if mod(wParam-48, 10) = 0                                       ; <----- added by RRR
            wParam += 10                                                ; <----- added by RRR
        SendMessage 0x18E,,, ListBox1
        GuiControl Choose, Command, % wParam-48 + ErrorLevel
        GuiControl Focus, Command
        Twetnts := 0
        gosub Select
        return 0
    }
    
    
    if (wParam = GetKeyVK(key := "PgUp")
     || wParam = GetKeyVK(key := "PgDn"))
    {
        GuiControl Focus, Command
        Send {%key%}
        return
    }
}

Filter(s, q, ByRef count)
{
    if (q = "")
    {
        StringReplace s, s, `n, `n, UseErrorLevel
        count := ErrorLevel
        return s
    }
    i := 1
    match := ""
    result := ""
    count := 0
    while i := RegExMatch(s, "`ami)^.*\Q" q "\E.*$", match, i + StrLen(match))
    {
        result .= match "`n"
        count += 1
    }
    return SubStr(result, 1, -1)
}

MenuGetAll(hwnd)
{
    if !menu := DllCall("GetMenu", "ptr", hwnd, "ptr")
        return ""    
    MenuGetAll_sub(menu, "", cmds)
    return cmds
}

MenuGetAll_sub(menu, prefix, ByRef cmds)
{
    Loop % DllCall("GetMenuItemCount", "ptr", menu)
    {
        VarSetCapacity(itemString, 2000)
        if !DllCall("GetMenuString", "ptr", menu, "int", A_Index-1, "str", itemString, "int", 1000, "uint", 0x400)
            continue
        itemString := StrReplace(itemString, "&")
        itemID := DllCall("GetMenuItemID", "ptr", menu, "int", A_Index-1)
        if (itemID = -1)
        if subMenu := DllCall("GetSubMenu", "ptr", menu, "int", A_Index-1, "ptr")
        {
            MenuGetAll_sub(subMenu, prefix itemString " > ", cmds)
            continue
        }
        cmds .= itemID "`t" prefix RegExReplace(itemString, "`t.*") "`n"
    }
}
bye!
User avatar
jNizM
Posts: 3183
Joined: 30 Sep 2013, 01:33
Contact:

Re: Alt Menu Search

22 Jun 2017, 07:41

Thx for your script lexikos. Got my script now working (but just if the menu exist with this name once). Open a specific menu entry with a hotkey. (rewritten it a bit)

Code: Select all

#Numpad0::
    hWindow := WinExist("A")
    MenuArr := MenuGet(hWindow)
    for i, v in MenuArr
        ;MsgBox % "MenuID:`t`t" i "`nMenuString:`t" v
        if (InStr(v, "Settings"))
            DllCall("SendNotifyMessage", "ptr", hWindow, "uint", 0x111, "ptr", i, "ptr", 0)
return


MenuGet(hWindow)
{
    if !(hMenu := DllCall("user32\GetMenu", "ptr", hWindow, "ptr"))
        return ""
    MenuArray := []
    MenuGetAll(hMenu, MenuArray)
    return MenuArray
}

MenuGetAll(hMenu, ByRef MenuArray)
{
    static MF_BYPOSITION := 0x00000400

    loop % DllCall("GetMenuItemCount", "ptr", hMenu)
    {
        size := VarSetCapacity(buf, (DllCall("GetMenuString", "ptr", hMenu, "uint", A_Index - 1, "ptr", 0, "int", 0, "uint", MF_BYPOSITION) << 1) + 1, 0)
        if !(DllCall("GetMenuString", "ptr", hMenu, "uint", A_Index - 1, "str", buf, "int", size, "uint", MF_BYPOSITION))
            continue
        MenuString := StrReplace(buf, "&")
        MenuItemID := DllCall("GetMenuItemID", "ptr", hMenu, "int", A_Index - 1)
        if (MenuItemID = -1) {
            if (hSubMenu := DllCall("GetSubMenu", "ptr", hMenu, "int", A_Index - 1, "ptr")) {
                MenuGetAll(hSubMenu, MenuArray)
                continue
            }
        }
        MenuArray.InsertAt(MenuItemID, MenuString)
    }
}
[AHK] v2.0.5 | [WIN] 11 Pro (Version 22H2) | [GitHub] Profile
lexikos
Posts: 9592
Joined: 30 Sep 2013, 04:07
Contact:

Re: Alt Menu Search

23 Jun 2017, 03:04

jNizM, was there a reason you didn't use WinMenuSelectItem?
User avatar
jNizM
Posts: 3183
Joined: 30 Sep 2013, 01:33
Contact:

Re: Alt Menu Search

23 Jun 2017, 03:09

Huuu.... Looks like I totally missed this command :eh:
Thanks for the hint. Makes everything shorter and less confused than the multiple dll calls.
[AHK] v2.0.5 | [WIN] 11 Pro (Version 22H2) | [GitHub] Profile

Return to “Scripts and Functions (v1)”

Who is online

Users browsing this forum: No registered users and 139 guests