How to get tooltip for a menu

Get help with using AutoHotkey (v1.1 and older) and its commands and hotkeys
User avatar
mviens
Posts: 43
Joined: 08 Jan 2014, 19:04

How to get tooltip for a menu

08 Sep 2015, 13:12

I can get a tooltip to display for a standard GUI. However, I have been unable to get a tooltip to work for a menu. I want to display a tooltip whenever the mouse hovers over a menu item. Even better would be if it hovered for a certain time before showing the tooltip. Can anyone offer some assistance? Thank you.

Code: Select all

#SingleInstance force
#Warn

OnMessage(0x200, "hoverHelp")

^#s::
    showMenu()
    return

showMenu() {
    Menu, myMenu, Add, First Item, doMenu

    Menu, mySubMenu, Add, Sub Item&1, doMenu
    Menu, mySubMenu, Add, Sub Item&2, doMenu
    Menu, mySubMenu, Add, Sub Item&3, doMenu

    Menu, myMenu, Add, SubMenu, :mySubMenu

    Menu, myMenu, Add
    Menu, myMenu, Add, &Cancel, doMenu
    Menu, myMenu, Show
    Menu, myMenu, Delete
    return

    doMenu:
        if (A_ThisMenuItem != "&Cancel") {
            MsgBox % "You clicked on: " . A_ThisMenuItem
        }
        return
}

hoverHelp(wParam, lParam, message, hwnd) {
    ToolTip, % "wParam :`t" . wParam . "`nlParam :`t`t" . lParam . "`nmessage :`t" . message . "`nhwnd :`t`t" . hwnd    
}
Mike V.
GEV
Posts: 1002
Joined: 25 Feb 2014, 00:50

Re: How to get tooltip for a menu

12 Sep 2015, 16:10

Only in combination with a second script that displays prescribed tooltips using a timer with MouseGetPos.
User avatar
mviens
Posts: 43
Joined: 08 Jan 2014, 19:04

Re: How to get tooltip for a menu

13 Sep 2015, 10:30

Thank you for the replies.
Mike V.
zcooler
Posts: 455
Joined: 11 Jan 2014, 04:59

Re: How to get tooltip for a menu

01 May 2017, 08:55

Since this seem to be a pretty sought after feature for AHK Im wondering if not a DLL call to the GetMenuItemRect function to get the Menu Item Rect (dont know exactly what the function returns cuz dll calls is mystery for me and i have no idea what BOOL is) and then use just me`s ToolTipEx to display the ToolTip within the menu x/y coordinates?
GetMenuItemRect function
Retrieves the bounding rectangle for the specified menu item.
Syntax

C++

BOOL WINAPI GetMenuItemRect(
_In_opt_ HWND hWnd,
_In_ HMENU hMenu,
_In_ UINT uItem,
_Out_ LPRECT lprcItem
);

Parameters

hWnd [in, optional]
Type: HWND
A handle to the window containing the menu.
If this value is NULL and the hMenu parameter represents a popup menu, the function will find the menu window.
hMenu [in]
Type: HMENU
A handle to a menu.
uItem [in]
Type: UINT
The zero-based position of the menu item.
lprcItem [out]
Type: LPRECT
A pointer to a RECT structure that receives the bounding rectangle of the specified menu item expressed in screen coordinates.
Return value

Type: BOOL
If the function succeeds, the return value is nonzero.
If the function fails, the return value is zero. To get extended error information, use the GetLastError function.
I see lexikos has done a couple of other MenuFunctions API dll calls in close proximity:

Code: Select all

Menu MyMenu, Add, Item 1, no
Menu MyMenu, Add, Item 2, no
Menu MyMenu, Add, Item B, no

; Retrieve the number of items in a menu.
item_count := DllCall("GetMenuItemCount", "ptr", MenuGetHandle("MyMenu"))

; Retrieve the ID of the last item.
last_id := DllCall("GetMenuItemID", "ptr", MenuGetHandle("MyMenu"), "int", item_count-1)

MsgBox, MyMenu has %item_count% items, and its last item has ID %last_id%.

no:
return
Last edited by zcooler on 01 May 2017, 10:02, edited 1 time in total.
sancarn
Posts: 224
Joined: 01 Mar 2016, 14:52

Re: How to get tooltip for a menu

01 May 2017, 09:59

zcooler wrote:Since this seem to be a pretty sought after feature for AHK Im wondering if not a DLL call to the GetMenuItemRect function to get the Menu rect (dont know exactly what the function returns cuz dll calls is mystery for me and i have no idea what BOOL is) and then use just me`s ToolTipEx to display the ToolTip within the menu x/y coordinates?
BOOL stands for Boolean. I.e. True/False.
zcooler
Posts: 455
Joined: 11 Jan 2014, 04:59

Re: How to get tooltip for a menu

01 May 2017, 10:05

sancarn wrote:BOOL stands for Boolean. I.e. True/False.
Ok, so you are ready to give it a go fetching the Menu Item Rects, so we can test out if the Tooltip idea might work? ;)
sancarn
Posts: 224
Joined: 01 Mar 2016, 14:52

Re: How to get tooltip for a menu

01 May 2017, 10:09

I have never tested this code but, to get the rectangle I believe you should be able to do something like this:

Code: Select all

nItem := 0 ;first menu item?
item_count := DllCall("GetMenuItemRect", "ptr",0, "ptr", MenuGetHandle("MyMenu")), "int", nItem, "ptr", myRect)
x:=(myRect>>0 ) & 0xFF   ;bitshift left is unimportant here but keeps things neat.
y:=(myRect>>16) & 0xFF
w:=(myRect>>32) & 0xFF
h:=(myRect>>48) & 0xFF   ;'& 0xFF' is unimportant here but keeps things neat.
Also p.s. it's always good to link the documentation for the function: GetMenuItemRect

Edit:

Actually you might need to call

Code: Select all

"int64*", myRect
instead of

Code: Select all

"ptr", myRect
Last edited by sancarn on 01 May 2017, 11:02, edited 1 time in total.
zcooler
Posts: 455
Joined: 11 Jan 2014, 04:59

Re: How to get tooltip for a menu

01 May 2017, 10:28

Sorry for no linking and sorry it does not work. Thanks for trying though :)

Code: Select all

Menu MyMenu, Add, Item 1, no
Menu MyMenu, Add, Item 2, no
Menu MyMenu, Add, Item B, no

; Retrieve the number of items in a menu.
item_count := DllCall("GetMenuItemCount", "ptr", MenuGetHandle("MyMenu"))

; Retrieve the ID of the last item.
last_id := DllCall("GetMenuItemID", "ptr", MenuGetHandle("MyMenu"), "int", item_count-1)

nItem := 0 ;first menu item?
item_rect := DllCall("GetMenuItemRect", "ptr", nItem, "ptr", MenuGetHandle("MyMenu"), "int", 0, "ptr", myRect)
;item_rect := DllCall("GetMenuItemRect", "ptr", nItem, "ptr", MenuGetHandle("MyMenu"), "int", 0, "int64*", myRect)
x:=(myRect>>0 ) & 0xFF   ;bitshift left is unimportant here but keeps things neat.
y:=(myRect>>16) & 0xFF
w:=(myRect>>32) & 0xFF
h:=(myRect>>48) & 0xFF

MsgBox, % "MyMenu has " item_count " items`nand its last item has ID " last_id "`nWith the first items rect of`nx= " x "`ny= " y "`nw= " w "`nh= " h 

no:
return
sancarn
Posts: 224
Joined: 01 Mar 2016, 14:52

Re: How to get tooltip for a menu

01 May 2017, 11:11

zcooler wrote:Sorry for no linking and sorry it does not work. Thanks for trying though :)
Well that does make sense, because the menu isn't showing at that time... :P The only way to really do this would be to use AHK_H and have another thread running asynchronously to the main script.

Edit: Even if you do the following:

Code: Select all

Menu MyMenu, Add, Item 1, no
Menu MyMenu, Add, Item 2, no
Menu MyMenu, Add, Item B, no
SetTimer, getInfo, -1000
Menu MyMenu, Show
no:
return

getInfo:
; Retrieve the number of items in a menu.
item_count := DllCall("GetMenuItemCount", "ptr", MenuGetHandle("MyMenu"))

; Retrieve the ID of the last item.
last_id := DllCall("GetMenuItemID", "ptr", MenuGetHandle("MyMenu"), "int", item_count-1)

nItem := 0 ;first menu item?
item_rect := DllCall("GetMenuItemRect", "ptr", nItem, "ptr", MenuGetHandle("MyMenu"), "int", 0, "ptr", myRect)
;item_rect := DllCall("GetMenuItemRect", "ptr", nItem, "ptr", MenuGetHandle("MyMenu"), "int", 0, "int64*", myRect)
x:=(myRect>>0 ) & 0xFF   ;bitshift left is unimportant here but keeps things neat.
y:=(myRect>>16) & 0xFF
w:=(myRect>>32) & 0xFF
h:=(myRect>>48) & 0xFF

MsgBox, % "MyMenu has " item_count " items`nand its last item has ID " last_id "`nWith the first items rect of`nx= " x "`ny= " y "`nw= " w "`nh= " h 
return
Stuff doesn't work out because MyMenu appears to freeze all runtime, and thus getInfo is never executed until after the menu has closed...

Not saying my code is correct, but there is some logic to it not working as expected.

Edit 2:
It may be possible to spawn a child process and pass the child process the Menu Handle. It'd be slightly complicated but it's definitely possible... Or as said, use AHK_H
zcooler
Posts: 455
Joined: 11 Jan 2014, 04:59

Re: How to get tooltip for a menu

01 May 2017, 11:21

Ok, thanks, I think you proved Leef_me right...this is impossble with current AHK.
just me
Posts: 9423
Joined: 02 Oct 2013, 08:51
Location: Germany

Re: How to get tooltip for a menu

02 May 2017, 05:41

It's technically possible. I believe SKAN showed the basic technique in the old forum. But because it disables the default AHK menu handling, you'll use it at your own risk.

The following is a working example for simple menues without sub-menus:

Code: Select all

#NoEnv
SetBatchLines, -1

Menu, MyMenu, Add, Item1, MenuHandler
Menu, MyMenu, Add, Item2, MenuHandler
Menu, MyMenu, Add, Item3, MenuHandler
Menu, MyMenu, Add, Item4, MenuHandler
Menu, MyMenu, Add, Item5, MenuHandler
HMENU := MenuGetHandle("MyMenu") + 0

Gui, Show, w400 h400, Context Menu Gui
Return

GuiClose:
ExitApp

GuiContextMenu:
ShowMenu(HMENU)
Return

MenuHandler:
   MsgBox, 0, %A_ThisLabel%, %A_ThisMenu% - %A_ThisMenuItem%
Return

; ==================================================================================================================================
; Shows a user-defined menu without blocking the script.
; Parameters:
;     HMENU          -  name or handle of a user-defined menu. This menu must not be the 'Tray' menu.
;     X              -  x-position of the upper-left corner of the menu in screen coordinates.
;                       Default: -1 - current cursor position.
;     Y              -  y-position of the upper-left corner of the menu in screen coordinates.
;                       Default: -1 - current cursor position.
; Return value:
;     True if the menu could be shown, otherwise False.
; Remarks:
;     Parts of code adapted from script_menu.cpp -> UserMenu::Display
;     Because the function prevents the default AHK handling of menues there might be unwanted side-effects.
; ==================================================================================================================================
ShowMenu(HMENU, X := -1, Y := -1) {
   If !DllCall("IsMenu", "Ptr", HMENU) && !(HMENU := MenuGetHandle(HMENU))
      Return False
   If (MenuGetName(HMENU) = "Tray")
      Return False
   ; Determine the display position
   If (X = -1) || (Y = -1) {
      VarSetCapacity(PT, 8, 0)
      DllCall("GetCursorPos", "Ptr", &PT)
      If (X = -1)
         X := NumGet(PT, 0, "Int")
      If (Y = -1)
         Y := NumGet(PT, 4, "Int")
   }
   ; Set the AHK main window as the foreground window, if needed
   ThisTID := DllCall("GetCurrentThreadId", "UInt")
   ForeWin := DllCall("GetForegroundWindow", "UPtr")
   If (ChangeFore := !(ForeWin) || (DllCall("GetWindowThreadProcessId", "Ptr", ForeWin, "Ptr", 0, "UInt") <> ThisTID))
      While !DllCall("SetForegroundWindow", "Ptr", A_ScriptHwnd, "UInt")
         DllCall("Sleep", "UInt", 10)
   ; Activate the message handler
   MsgFunc := Func("ShowMenuEnterMenuLoop")
   OnMessage(0x0211, MsgFunc, -1) ; WM_ENTERMENULOOP
   ; Activate the timer
   TimerFunc := Func("ShowMenuCheckCursorPosition").Bind(HMENU)
   SetTimer, %TimerFunc%, 10
   Result := DllCall("TrackPopupMenuEx", "Ptr", HMENU, "UInt", 0, "Int", X, "Int", Y, "Ptr", A_ScriptHwnd, "Ptr", 0, "UInt")
   ; Remove the ToolTip, if any
   ToolTip
   ; Deactivate the message handler
   OnMessage(0x0211, MsgFunc, 0) ; WM_ENTERMENULOOP
   ; Deactivate the timer
   SetTimer, %TimerFunc%, Delete
   ; Reset the timer's static variables
   ShowMenuCheckCursorPosition(0)
   ; Restore the foreground window, if needed
   If (ChangeFore) && (ForeWin) && (DllCall("GetForegroundWindow", "UPtr") = A_ScriptHwnd)
      DllCall("SetForegroundWindow", "Ptr", ForeWin, "UInt")
   Return Result
}
; ==================================================================================================================================
ShowMenuEnterMenuLoop() {
   Return 0 ; prevents AHK menu processing
}
; ==================================================================================================================================
ShowMenuCheckCursorPosition(HMENU) {
   Static ThisMenu := 0, ThisItem := 0
   If (HMENU <> ThisMenu) {
      ThisMenu := HMENU
      ThisItem := 0
   }
   If (HMENU = 0)
      Return
   DllCall("GetCursorPos", "Int64P", PT)
   If (Item := DllCall("MenuItemFromPoint", "Ptr", 0, "Ptr", HMENU, "Int64", PT, "Int") + 1) {
      If (Item <> ThisItem)
         ToolTip, The mouse cursor is on item %Item%
   }
   Else
      ToolTip
   ThisItem := Item
}
zcooler
Posts: 455
Joined: 11 Jan 2014, 04:59

Re: How to get tooltip for a menu

02 May 2017, 08:25

One word...AMAZING :clap:
TooltipOnMenus.jpg
TooltipOnMenus.jpg (48.44 KiB) Viewed 5405 times
The only sinister sideeffect I did encounter was radio togglechecks not working. I use them on the menu to see which channel is currently selected. I assume it might be problematic to get those radio togglechecks working with this awesome script of yours, yes?Also one last question is if the AHK menu handling sideeffects can affect other current running AHK scripts with menus?

EDIT: Radio Togglechecks works just fine :) I messed up :oops:
So no sideeffects as far as the eye can see and next step do some styling with your ToolTipEx() :dance:
Much obliged, just me :wave:
just me
Posts: 9423
Joined: 02 Oct 2013, 08:51
Location: Germany

Re: How to get tooltip for a menu

02 May 2017, 08:56

zcooler wrote:EDIT: Radio Togglechecks works just fine ...
Because I already finished a revised version of the test script:

Code: Select all

#NoEnv
SetBatchLines, -1

Menu, MyMenu, Add, Item1, MenuHandler
Menu, MyMenu, Add, Item2, MenuHandler
Menu, MyMenu, Add, Item3, MenuHandler
Menu, MyMenu, Add, Item4, MenuHandler
Menu, MyMenu, Add, Item5, MenuHandler
HMENU := MenuGetHandle("MyMenu") + 0

Gui, Show, w400 h400, Context Menu Gui
Return

GuiClose:
ExitApp

GuiContextMenu:
ShowMenu(HMENU)
Return

MenuHandler:
   MsgBox, 0, %A_ThisLabel%, %A_ThisMenu% - %A_ThisMenuItem%
   Menu_CheckRadioGroup(HMENU, A_ThisMenuItemPos, 1)
Return

; ==================================================================================================================================
; Shows a user-defined menu without blocking the script.
; Parameters:
;     HMENU          -  name or handle of a user-defined menu. This menu must not be the 'Tray' menu.
;     X              -  x-position of the upper-left corner of the menu in screen coordinates.
;                       Default: -1 - current cursor position.
;     Y              -  y-position of the upper-left corner of the menu in screen coordinates.
;                       Default: -1 - current cursor position.
; Return value:
;     True if the menu could be shown, otherwise False.
; Remarks:
;     Parts of code adapted from script_menu.cpp -> UserMenu::Display
;     Because the function prevents the default AHK handling of menues there might be unwanted side-effects.
; ==================================================================================================================================
ShowMenu(HMENU, X := -1, Y := -1) {
   If !DllCall("IsMenu", "Ptr", HMENU) && !(HMENU := MenuGetHandle(HMENU))
      Return False
   If (MenuGetName(HMENU) = "Tray")
      Return False
   ; Determine the display position
   If (X = -1) || (Y = -1) {
      VarSetCapacity(PT, 8, 0)
      DllCall("GetCursorPos", "Ptr", &PT)
      If (X = -1)
         X := NumGet(PT, 0, "Int")
      If (Y = -1)
         Y := NumGet(PT, 4, "Int")
   }
   ; Set the AHK main window as the foreground window, if needed
   ThisTID := DllCall("GetCurrentThreadId", "UInt")
   ForeWin := DllCall("GetForegroundWindow", "UPtr")
   If (ChangeFore := !(ForeWin) || (DllCall("GetWindowThreadProcessId", "Ptr", ForeWin, "Ptr", 0, "UInt") <> ThisTID))
      While !DllCall("SetForegroundWindow", "Ptr", A_ScriptHwnd, "UInt")
         DllCall("Sleep", "UInt", 10)
   ; Activate the message handler
   MsgFunc := Func("ShowMenuEnterMenuLoop")
   OnMessage(0x0211, MsgFunc, -1) ; WM_ENTERMENULOOP
   ; Activate the timer
   TimerFunc := Func("ShowMenuCheckCursorPosition").Bind(HMENU)
   SetTimer, %TimerFunc%, 10
   Result := DllCall("TrackPopupMenuEx", "Ptr", HMENU, "UInt", 0, "Int", X, "Int", Y, "Ptr", A_ScriptHwnd, "Ptr", 0, "UInt")
   ; Remove the ToolTip, if any
   ToolTip
   ; Deactivate the message handler
   OnMessage(0x0211, MsgFunc, 0) ; WM_ENTERMENULOOP
   ; Deactivate the timer
   SetTimer, %TimerFunc%, Delete
   ; Reset the timer's static variables
   ShowMenuCheckCursorPosition(0)
   ; Restore the foreground window, if needed
   If (ChangeFore) && (ForeWin) && (DllCall("GetForegroundWindow", "UPtr") = A_ScriptHwnd)
      DllCall("SetForegroundWindow", "Ptr", ForeWin, "UInt")
   Return Result
}
; ==================================================================================================================================
ShowMenuEnterMenuLoop() {
   Return 0 ; prevents AHK menu processing
}
; ==================================================================================================================================
ShowMenuCheckCursorPosition(HMENU) {
   Static ThisMenu := 0, ThisItem := 0
   If (HMENU <> ThisMenu) {
      ThisMenu := HMENU
      ThisItem := 0
   }
   If (HMENU = 0)
      Return
   DllCall("GetCursorPos", "Int64P", PT)
   If (Item := DllCall("MenuItemFromPoint", "Ptr", 0, "Ptr", HMENU, "Int64", PT, "Int") + 1) {
      If (Item <> ThisItem)
         ToolTip, The mouse cursor is on item %Item%
   }
   Else
      ToolTip
   ThisItem := Item
}
; ==================================================================================================================================
; CheckRadioGroup Checks a specified menu item and makes it a radio item. At the same time, the function clears
;                 all other menu items in the associated group and clears the radio-item type flag for those items.
; Parameters:     First -  The 1-based position of the first menu item in the group.
;                          Default: 0 = no radio group.
;                 Last  -  The 1-based position of the last menu item in the group.
;                          Default: 0 = last menu item.
; Return values:  If the function succeeds, the return value is nonzero; otherwise it is zero (False).
; Note:           To uncheck all items in the group specify 0 for Pos.
;                 To treat all items in the menu as a group pass 1 for First and 0 for Last.
;                 To check/uncheck the item inidividually use the menu command.
;
; ==================================================================================================================================
Menu_CheckRadioGroup(HMENU, Pos, First := 0, Last := 0) {
   If (First < 1)
      First := Last := Pos
   If (Last < 1)
      Last := Menu_GetItemCount(HMENU)
   Return DllCall("CheckMenuRadioItem", "Ptr", HMENU, "UInt", First - 1, "UInt", Last - 1, "UInt", Pos - 1, "UInt", 0x0400, "UInt")
}
; ==================================================================================================================================
; GetItemCount    Determines the number of items in the specified menu.
; Return values:  If the function succeeds, the return value specifies the number of items in the menu.
;                 If the function fails, the return value is -1
; ==================================================================================================================================
Menu_GetItemCount(HMENU) {
   Return DllCall("GetMenuItemCount", "Ptr", HMENU, "Int")
}
zcooler wrote:So no sideeffects as far as the eye can see ...
Sideeffects can only occur in the script calling the ShowMenu() function.
zcooler
Posts: 455
Joined: 11 Jan 2014, 04:59

Re: How to get tooltip for a menu

02 May 2017, 09:19

just me wrote:
zcooler wrote:EDIT: Radio Togglechecks works just fine ...
Because I already finished a revised version of the test script:
Oh My...I messed up that too (Im too hasty as usual) :oops: I managed to get the CheckRadioGroup showing but the updating wasnt working. Never tested the latter before posting. Well, that doesnt matter since the Great just me saved the day with an addition to this masterpiece. BIG THANKs just me, the menus gets staggering and quality looking :thumbup:
lblb
Posts: 190
Joined: 30 Sep 2013, 11:31

Re: How to get tooltip for a menu

02 May 2017, 11:17

Thanks for the code just me, this is great!

In a previous life using AHK Basic, to add tooltips to menus I used the MMenu module from the great Majkinetor, which replaces standard menus and allows all kinds of customizations:
https://autohotkey.com/board/topic/1624 ... enu-10-b1/

It was useful because I had a program that had larger menus and more intense highlighting contrast for people with eye sight problems, and also I had combined it with a Speech library so that the menu items were read out loud when highlighted. Worked great! Unfortunately, it didn't work all that well with AHK_L at the time.
iPhilip
Posts: 796
Joined: 02 Oct 2013, 12:21

Re: How to get tooltip for a menu

21 Sep 2017, 12:36

Hi Folks,

Below is an extension of just me's solution as follows:
  • ShowMenu() - extended to accept an additional parameter (WhichToolTip) so that the tooltip number can be specified.
  • ShowMenuCheckCursorPosition() - extended to work with submenus. The tooltip is now placed to the right of and half-way down the menu item.
  • Menu_CheckRadioGroup() - extended to work with submenus and to clear a menu item if reselected.
Here is the code, including an example:

Code: Select all

#NoEnv
SetBatchLines, -1
CoordMode, ToolTip, Screen ; Required for ShowMenuCheckCursorPosition()

Menu, MyMenu, Add, Item 1, MenuHandler
Menu, MyMenu, Add, Item 2, MenuHandler
   Menu, MySubMenu3, Add, Item 3.1, MenuHandler
   Menu, MySubMenu3, Add, Item 3.2, MenuHandler
   Menu, MySubMenu3, Add, Item 3.3, MenuHandler
   Menu, MySubMenu3, Add, Item 3.4, MenuHandler
   Menu, MySubMenu3, Add, Item 3.5, MenuHandler
Menu, MyMenu, Add, Item 3, :MySubMenu3
Menu, MyMenu, Add, Item 4, MenuHandler
   Menu, MySubMenu5, Add, Item 5.1, MenuHandler
   Menu, MySubMenu5, Add, Item 5.2, MenuHandler
      Menu, MySubMenu5.3, Add, Item 5.3.1, MenuHandler
      Menu, MySubMenu5.3, Add, Item 5.3.2, MenuHandler
      Menu, MySubMenu5.3, Add, Item 5.3.3, MenuHandler
      Menu, MySubMenu5.3, Add, Item 5.3.4, MenuHandler
      Menu, MySubMenu5.3, Add, Item 5.3.5, MenuHandler
   Menu, MySubMenu5, Add, Item 5.3, :MySubMenu5.3
   Menu, MySubMenu5, Add, Item 5.4, MenuHandler
   Menu, MySubMenu5, Add, Item 5.5, MenuHandler
Menu, MyMenu, Add, Item 5, :MySubMenu5

Gui, Show, w400 h400, Context Menu Gui

global ToolTipText := {}
AssignToolTipTexts("MyMenu")
AssignToolTipTexts("MySubMenu3")
AssignToolTipTexts("MySubMenu5")
AssignToolTipTexts("MySubMenu5.3")
Return

AssignToolTipTexts(MenuName) {
   HMENU := MenuGetHandle(MenuName)
   ToolTipText[HMENU] := []
   Loop, % Menu_GetItemCount(HMENU)
      ToolTipText[HMENU][A_Index] := MenuName " - Item " A_Index
}

GuiClose:
ExitApp

GuiContextMenu:
ShowMenu(MenuGetHandle("MyMenu"), , , 3)
Return

MenuHandler:
   MsgBox, 0, %A_ThisLabel%, %A_ThisMenu% - %A_ThisMenuItem%
   Menu_CheckRadioGroup(MenuGetHandle(A_ThisMenu), A_ThisMenuItemPos, 1)
Return

; ==================================================================================================================================
; Shows a user-defined menu without blocking the script.
; Parameters:
;     HMENU          -  name or handle of a user-defined menu. This menu must not be the 'Tray' menu.
;     X              -  x-position of the upper-left corner of the menu in screen coordinates.
;                       Default: -1 - current cursor position.
;     Y              -  y-position of the upper-left corner of the menu in screen coordinates.
;                       Default: -1 - current cursor position.
;     WhichToolTip   -  Tooltip window to operate upon (1-20)
;                       Default: 1
; Return value:
;     True if the menu could be shown, otherwise False.
; Remarks:
;     Parts of code adapted from script_menu.cpp -> UserMenu::Display
;     Because the function prevents the default AHK handling of menues there might be unwanted side-effects.
; ==================================================================================================================================
ShowMenu(HMENU, X := -1, Y := -1, WhichToolTip := 1) {
   If !DllCall("IsMenu", "Ptr", HMENU) && !(HMENU := MenuGetHandle(HMENU))
      Return False
   If (MenuGetName(HMENU) = "Tray")
      Return False
   ; Determine the display position
   If (X = -1) || (Y = -1) {
      VarSetCapacity(PT, 8, 0)
      DllCall("GetCursorPos", "Ptr", &PT)
      If (X = -1)
         X := NumGet(PT, 0, "Int")
      If (Y = -1)
         Y := NumGet(PT, 4, "Int")
   }
   ; Set the AHK main window as the foreground window, if needed
   ThisTID := DllCall("GetCurrentThreadId", "UInt")
   ForeWin := DllCall("GetForegroundWindow", "UPtr")
   If (ChangeFore := !(ForeWin) || (DllCall("GetWindowThreadProcessId", "Ptr", ForeWin, "Ptr", 0, "UInt") <> ThisTID))
      While !DllCall("SetForegroundWindow", "Ptr", A_ScriptHwnd, "UInt")
         DllCall("Sleep", "UInt", 10)
   ; Activate the message handler
   MsgFunc := Func("ShowMenuEnterMenuLoop")
   OnMessage(0x0211, MsgFunc, -1) ; WM_ENTERMENULOOP
   ; Activate the timer
   TimerFunc := Func("ShowMenuCheckCursorPosition").Bind(HMENU, WhichToolTip)
   SetTimer, %TimerFunc%, 10
   Result := DllCall("TrackPopupMenuEx", "Ptr", HMENU, "UInt", 0, "Int", X, "Int", Y, "Ptr", A_ScriptHwnd, "Ptr", 0, "UInt")
   ; Deactivate the timer
   SetTimer, %TimerFunc%, Delete
   ; Remove the ToolTip, if any
   ToolTip, , , , WhichToolTip
   ; Deactivate the message handler
   OnMessage(0x0211, MsgFunc, 0) ; WM_ENTERMENULOOP
   ; Reset the timer's static variables
   ShowMenuCheckCursorPosition(0, WhichToolTip)
   ; Restore the foreground window, if needed
   If (ChangeFore) && (ForeWin) && (DllCall("GetForegroundWindow", "UPtr") = A_ScriptHwnd)
      DllCall("SetForegroundWindow", "Ptr", ForeWin, "UInt")
   Return Result
}
; ==================================================================================================================================
ShowMenuEnterMenuLoop() {
   Return 0 ; prevents AHK menu processing
}
; ==================================================================================================================================
ShowMenuCheckCursorPosition(HMENU, WhichToolTip) {
   Static PrevItem := 0, ThisMenu := 0, Menus := []
   If (HMENU = 0) {
      PrevItem := 0, ThisMenu := 0, Menus := []
      Return
   }
   If !Menus.MaxIndex()
      Menus.Push(ThisMenu := HMENU)
   DllCall("GetCursorPos", "Int64P", PT)
   If (Item := DllCall("MenuItemFromPoint", "Ptr", 0, "Ptr", ThisMenu, "Int64", PT, "Int") + 1) {
      If (Item <> PrevItem) {
         Pos := Menu_GetItemRect(ThisMenu, Item)
         ToolTip, % ToolTipText[ThisMenu][Item], Pos.right, (Pos.top + Pos.bottom)//2, WhichToolTip ; Requires CoordMode, ToolTip, Screen
      }
   }
   Else If (HSUBMENU := DllCall("GetSubMenu", "Ptr", ThisMenu, "Int", PrevItem-1))
      Menus.Push(ThisMenu := HSUBMENU)
   Else {
      If (Menus.MaxIndex() > 1 && (DllCall("MenuItemFromPoint", "Ptr", 0, "Ptr", Menus[Menus.MaxIndex()-1], "Int64", PT, "Int") + 1)) {
         Menus.Pop()
         ThisMenu := Menus[Menus.MaxIndex()]
      }
      ToolTip, , , , WhichToolTip
   }
   PrevItem := Item
}
; ==================================================================================================================================
; GetItemRect     Retrieves the bounding rectangle for the specified menu item.
; Return values:  If the function succeeds, the return value is an array containing the coordinates of the bounding rectangle of the
;                 specified menu item expressed in screen coordinates.
;                 If the function fails, the return value is null.
; ==================================================================================================================================
Menu_GetItemRect(hMenu, Item) {
   VarSetCapacity(RECT, 16, 0)
   if DllCall("GetMenuItemRect", "Ptr", 0, "Ptr", hMenu, "UInt", Item-1, "Ptr", &RECT)
      Return {left:NumGet(RECT,0,"Int"),top:NumGet(RECT,4,"Int"),right:NumGet(RECT,8,"Int"),bottom:NumGet(RECT,12,"Int")}
}
; ==================================================================================================================================
; CheckRadioGroup Checks a specified menu item and makes it a radio item. At the same time, the function clears
;                 all other menu items in the associated group and clears the radio-item type flag for those items.
;                 If the specified menu item is the same as the previous one, the menu item is cleared.
; Parameters:     First -  The 1-based position of the first menu item in the group.
;                          Default: 0 = no radio group.
;                 Last  -  The 1-based position of the last menu item in the group.
;                          Default: 0 = last menu item.
; Return values:  If the function succeeds, the return value is nonzero; otherwise it is zero (False).
; Note:           To uncheck all items in the group specify 0 for Pos, 1 for First, and 0 for Last.
;                 To treat all items in the menu as a group pass 1 for First and 0 for Last.
;                 To check/uncheck the item inidividually use the menu command.
;
; ==================================================================================================================================
Menu_CheckRadioGroup(HMENU, Pos, First := 0, Last := 0) {
   Static PrevHMENU, PrevPos
   If (First < 1)
      First := Last := Pos
   If (Last < 1)
      Last := Menu_GetItemCount(HMENU)
   If (HMENU = PrevHMENU && Pos = PrevPos) ; If selecting the previous menu item, clear it and return
      Return DllCall("CheckMenuRadioItem", "Ptr", HMENU, "UInt", 0, "UInt", Menu_GetItemCount(HMENU) - 1, "UInt", -1, "UInt", 0x0400, "UInt") ; MF_BYPOSITION = 0x0400
   If (HMENU <> PrevHMENU) ; if the specified menu is different than the previous one, clear the all the items in the previous menu
      DllCall("CheckMenuRadioItem", "Ptr", PrevHMENU, "UInt", 0, "UInt", Menu_GetItemCount(PrevHMENU) - 1, "UInt", -1, "UInt", 0x0400, "UInt") ; MF_BYPOSITION = 0x0400
   PrevHMENU := HMENU, PrevPos := Pos
   Return DllCall("CheckMenuRadioItem", "Ptr", HMENU, "UInt", First - 1, "UInt", Last - 1, "UInt", Pos - 1, "UInt", 0x0400, "UInt") ; MF_BYPOSITION = 0x0400
}
; ==================================================================================================================================
; GetItemCount    Determines the number of items in the specified menu.
; Return values:  If the function succeeds, the return value specifies the number of items in the menu.
;                 If the function fails, the return value is -1
; ==================================================================================================================================
Menu_GetItemCount(HMENU) {
   Return DllCall("GetMenuItemCount", "Ptr", HMENU, "Int")
}
Cheers!

EDIT: Corrected the Menu_GetItemRect function so that it returns correct values for AutoHotkey U64.
Last edited by iPhilip on 31 May 2022, 17:39, edited 1 time in total.
Windows 10 Pro (64 bit) - AutoHotkey v2.0+ (Unicode 64-bit)
robodesign
Posts: 934
Joined: 30 Sep 2017, 03:59
Location: Romania
Contact:

Re: How to get tooltip for a menu

31 May 2022, 14:27

Apologies for bumping this thread, but....

I have been working on menus, and I was not able to get working GetMenuItemRect(). And searching the web, I found this thread.

And, the latest script posted by @iPhilip , does not yield correct results either. GetMenuItemRect seems to return the wrong coords. The tooltips are always at x=0. I am on Windows 10 .

Any ideas why?

Best regards, Marius.
-------------------------
KeyPress OSD v4: GitHub or forum. (presentation video)
Quick Picto Viewer: GitHub or forum.
AHK GDI+ expanded / compilation library (on GitHub)
My home page.
iPhilip
Posts: 796
Joined: 02 Oct 2013, 12:21

Re: How to get tooltip for a menu

31 May 2022, 16:21

robodesign wrote:
31 May 2022, 14:27
...And, the latest script posted by @iPhilip , does not yield correct results either.
Can you be more specific? I just tried it again and it works fine for me.
robodesign wrote:
31 May 2022, 14:27
GetMenuItemRect seems to return the wrong coords.
GetMenuItemRect returns screen coordinates. If you add the following line after line 126 in my post you will see those coordinates.

Code: Select all

ToolTip, % Pos.left " " Pos.right "`n" Pos.top " " Pos.bottom, 0, 0, 10
robodesign wrote:
31 May 2022, 14:27
The tooltips are always at x=0.
I don't know what x=0 means. Did you see the comment: ; Requires CoordMode, ToolTip, Screen?
Windows 10 Pro (64 bit) - AutoHotkey v2.0+ (Unicode 64-bit)
robodesign
Posts: 934
Joined: 30 Sep 2017, 03:59
Location: Romania
Contact:

Re: How to get tooltip for a menu

31 May 2022, 16:58

iPhilip wrote:
31 May 2022, 16:21
robodesign wrote:
31 May 2022, 14:27
...And, the latest script posted by @iPhilip , does not yield correct results either.
Can you be more specific? I just tried it again and it works fine for me.
robodesign wrote:
31 May 2022, 14:27
GetMenuItemRect seems to return the wrong coords.
GetMenuItemRect returns screen coordinates. If you add the following line after line 126 in my post you will see those coordinates.

Code: Select all

ToolTip, % Pos.left " " Pos.right "`n" Pos.top " " Pos.bottom, 0, 0, 10
robodesign wrote:
31 May 2022, 14:27
The tooltips are always at x=0.
I don't know what x=0 means. Did you see the comment: ; Requires CoordMode, ToolTip, Screen?
Thank you for the reply.

I copy /pasted your script into an ahk file and ran it. By x=0 I meant, the tooltip for the menus are at (x, y), where x=0. In other words, the tooltip is always located on the left side of the screen, glued to the left edge of the screen. The Y coordinates somehow seems to match the menus, to some extent, but it's still off by a certain amount.

Your script begins with coordmode, tooltip, screen, i checked that, because I knew that the function returns the values in screen coordinates.

In your script, if I add a tooltip to display the actual pos values, they are like 34588860 or 2897865844.

Thank you. I really need to get your script running properly (with GetMenuItemRect). I need this.

Best regards, Marius.
-------------------------
KeyPress OSD v4: GitHub or forum. (presentation video)
Quick Picto Viewer: GitHub or forum.
AHK GDI+ expanded / compilation library (on GitHub)
My home page.

Return to “Ask for Help (v1)”

Who is online

Users browsing this forum: OrangeCat, RussF, Spawnova and 121 guests