Menu Icons v2
I am having some issues with it, probably missed a comment somewhere, but take this example.
; Uncomment this if MI.ahk is not in your function library: ;#include %A_ScriptDir%\MI.ahk #NoEnv ; Sample menu items. Menu, M, Add, 16x16 Icon, ItemClick MI_SetMenuItemIcon("M", 1, "shell32.dll", 4, 16) Menu,Tray,Add,M,:M hTM := MI_GetMenuHandle("Tray") if (A_OSVersion != "WIN_VISTA") { ; It is necessary to hook the tray icon for owner-drawing to work. ; (Owner-drawing is not used on Windows Vista.) OnMessage(0x404, "AHK_NOTIFYICON") OnMessage(0x111, "WM_COMMAND") ; To track "pause" status. MI_SetMenuStyle(hTM, 0x4000000) ; MNS_CHECKORBMP (optional) } SplitPath, A_AhkPath,, SpyPath SpyPath = %SpyPath%\AU3_Spy.exe MI_SetMenuItemIcon(hTM, 1, A_AhkPath, 1, 16) ; open MI_SetMenuItemIcon(hTM, 2, A_WinDir "\hh.exe", 1, 16) ; help ;- MI_SetMenuItemIcon(hTM, 4, SpyPath, 1, 16) ; spy ; reload - icon needed! MI_SetMenuItemIcon(hTM, 6, A_AhkPath, 2, 16) ; edit ;- MI_SetMenuItemIcon(hTM, 8, A_AhkPath, 3, 16) ; suspend MI_SetMenuItemIcon(hTM, 9, A_AhkPath, 4, 16) ; pause MI_SetMenuItemBitmap(hTM, 10, 8) ; exit MI_ShowMenu("M") return ItemClick: Menu, M, Deleteall Menu, M, Add, 16x16 Icon, ItemClick MI_SetMenuItemIcon("M", 1, "shell32.dll", 4, 16) return AHK_NOTIFYICON(wParam, lParam) { global hTM, M_IsPaused if (lParam = 0x205) ; WM_RBUTTONUP { ; Update "Suspend Script" and "Pause Script" checkmarks. DllCall("CheckMenuItem","uint",hTM,"uint",65305,"uint",A_IsSuspended ? 8:0) DllCall("CheckMenuItem","uint",hTM,"uint",65306,"uint",M_IsPaused ? 8:0) ; Show menu to allow owner-drawing. MI_ShowMenu(hTM) Tooltip,Bleah return 0 } } WM_COMMAND(wParam, lParam, Msg, hwnd) { Critical global M_IsPaused id := wParam & 0xFFFF if id in 65306,65403 ; tray pause, file menu pause { ; When the script is not paused, WM_COMMAND() is called once for ; AutoHotkey --** and once for OwnerDrawnMenuMsgWin **--. DetectHiddenWindows, On WinGetClass, cl, ahk_id %hwnd% if cl != AutoHotkey return ; This will become incorrect if "pause" is used from the script. M_IsPaused := ! M_IsPaused } }
After you click the custom menu for the first time and the deleteall is performed the tray menu will no longer open.
This is XP of course otherwise I wouldnt need the mi_showmenu.
; Uncomment this if MI.ahk is not in your function library: ;#include %A_ScriptDir%\MI.ahk #NoEnv ; Sample menu items. Menu, M, Add, 16x16 Icon, ItemClick MI_SetMenuItemIcon("M", 1, "shell32.dll", 4, 16) Menu,Tray,Add,M,:M gosub SetTrayMenuIcons if A_OSVersion != WIN_VISTA { ; It is necessary to hook the tray icon for owner-drawing to work. ; (Owner-drawing is not used on Windows Vista.) OnMessage(0x404, "AHK_NOTIFYICON") OnMessage(0x111, "WM_COMMAND") ; To track "pause" status. } SplitPath, A_AhkPath,, SpyPath SpyPath = %SpyPath%\AU3_Spy.exe MI_ShowMenu("M") return ItemClick: Menu, M, Deleteall Menu, M, Add, 16x16 Icon, ItemClick MI_SetMenuItemIcon("M", 1, "shell32.dll", 4, 16) ; ALSO SET TRAY MENU ICONS: SetTrayMenuIcons: hTM := MI_GetMenuHandle("Tray") if A_OSVersion != WIN_VISTA MI_SetMenuStyle(hTM, 0x4000000) ; MNS_CHECKORBMP (optional) MI_SetMenuItemIcon(hTM, 1, A_AhkPath, 1, 16) ; open MI_SetMenuItemIcon(hTM, 2, A_WinDir "\hh.exe", 1, 16) ; help ;- MI_SetMenuItemIcon(hTM, 4, SpyPath, 1, 16) ; spy ; reload - icon needed! MI_SetMenuItemIcon(hTM, 6, A_AhkPath, 2, 16) ; edit ;- MI_SetMenuItemIcon(hTM, 8, A_AhkPath, 3, 16) ; suspend MI_SetMenuItemIcon(hTM, 9, A_AhkPath, 4, 16) ; pause MI_SetMenuItemBitmap(hTM, 10, 8) ; exit return AHK_NOTIFYICON(wParam, lParam) { global hTM, M_IsPaused if (lParam = 0x205) ; WM_RBUTTONUP { ; Update "Suspend Script" and "Pause Script" checkmarks. DllCall("CheckMenuItem","uint",hTM,"uint",65305,"uint",A_IsSuspended ? 8:0) DllCall("CheckMenuItem","uint",hTM,"uint",65306,"uint",M_IsPaused ? 8:0) ; Show menu to allow owner-drawing. MI_ShowMenu(hTM) Tooltip,Bleah return 0 } } WM_COMMAND(wParam, lParam, Msg, hwnd) { Critical global M_IsPaused id := wParam & 0xFFFF if id in 65306,65403 ; tray pause, file menu pause { ; When the script is not paused, WM_COMMAND() is called once for ; AutoHotkey --** and once for OwnerDrawnMenuMsgWin **--. DetectHiddenWindows, On WinGetClass, cl, ahk_id %hwnd% if cl != AutoHotkey return ; This will become incorrect if "pause" is used from the script. M_IsPaused := ! M_IsPaused } }
#include %A_ScriptDir%\MI.ahk #NoEnv #SingleInstance force SendMode Input SetWorkingDir %A_ScriptDir% ; Ensures a consistent starting directory. myMenu1 = ( D @EXPLORER.EXE /n, /e, d:\ Notepad @Notepad ) ; myMenu ~LButton & WheelUp:: CreateMenu("myMenu", myMenu1, "myMenu2") Menu myMenu, Show Return myMenu2: RunMenuItem(myMenu1, A_ThisMenuItemPos) Return ; MI_SetMenuItemIcon("myMenu1", 1, "shell32.dll", 4, 16) ; MI_SetMenuStyle("myMenu1", 0x4000000) CreateMenu(_menuName, _menuDef, _menuLabel) { Loop Parse, _menuDef, `n { If (Mod(A_Index, 2) = 1) ; Odd { Menu %_menuName%, Add, %A_LoopField%, %_menuLabel% } } } RunMenuItem(_menuDef, _index) { local cmd, toRun, tmp Loop Parse, _menuDef, `n { If (_index * 2 = A_Index) { StringLeft cmd, A_LoopField, 1 StringTrimLeft toRun, A_LoopField, 1 If (cmd = "@") { Run %toRun% } Else { MsgBox 16, RunMenuItem, Invalid command: '%cmd%'! (%A_LoopField%) } Break } } }
It appears you've used the wrong name. The menu name you should be using is the name you used with the Menu command:; MI_SetMenuItemIcon("myMenu1", 1, "shell32.dll", 4, 16) ; MI_SetMenuStyle("myMenu1", 0x4000000)
You will need to use MI_ShowMenu if you are not on Windows Vista.Menu myMenu, Show
but i cant get it to work. im on xp sp3
I get no errors with this code, but no icons either. Also the menu gets stuck if i try to click it away (outside of the menu). I tried to experiment within the CreateMenu() loop with no luck.
The example above gives me icons but with 20-30 menu/submenu items it gets alot lines of code that way ...
Maybe im lost in translation, is this the way you meant?
#include %A_ScriptDir%\MI.ahk #NoEnv #SingleInstance force SendMode Input SetWorkingDir %A_ScriptDir% ; Ensures a consistent starting directory. myMenu1 = ( D @EXPLORER.EXE /n, /e, d:\ Notepad @Notepad ) Return MI_SetMenuItemIcon([color=red]"myMenu"[/color], 1, "shell32.dll", 4, 16) MI_SetMenuStyle([color=red]"myMenu"[/color], 0x4000000) ; myMenu ~LButton & WheelUp:: CreateMenu("myMenu", myMenu1, "myMenu2") [color=red]MI_ShowMenu("myMenu")[/color] Return myMenu2: RunMenuItem(myMenu1, A_ThisMenuItemPos) Return CreateMenu(_menuName, _menuDef, _menuLabel) { Loop Parse, _menuDef, `n { If (Mod(A_Index, 2) = 1) ; Odd { Menu %_menuName%, Add, %A_LoopField%, %_menuLabel% } } } RunMenuItem(_menuDef, _index) { local cmd, toRun, tmp Loop Parse, _menuDef, `n { If (_index * 2 = A_Index) { StringLeft cmd, A_LoopField, 1 StringTrimLeft toRun, A_LoopField, 1 StringReplace toRun, toRun, ***, %Clipboard%, All If (cmd = "@") { Run %toRun% } Else { MsgBox 16, RunMenuItem, Invalid command: '%cmd%'! (%A_LoopField%) } Break } } }
... myMenu1 = ( D @EXPLORER.EXE /n, /e, d:\ Notepad @Notepad ) [color=red]Return[/color] ; "Stop here; don't go any further." [color=#606060]MI_SetMenuItemIcon("myMenu", 1, "shell32.dll", 4, 16) MI_SetMenuStyle("myMenu", 0x4000000) [/color] ...Move Return. You must also create the menu before setting icons...
My question is: will memoizing the values cause problems? I presume not since you suggested a similar technique, but just wanted to make sure.
Here is the "updated" MI_GetMenuHandle method. This way, you don't have to pass the handle, but can just pass the menu name (e.g. "Tray"), and it will (on first access), save the value, and on subsequent calls reuse that value. And, thanks to your excellent encapsulation, this applies to ALL the methods.
Of course you can choose to add this or discard it. If you discard it because there's some problem with it please do tell - I don't want to build code using this method if it's faulty.
Here's the code, pay attention to the lines in red.
MI_GetMenuHandle(menu_name) { static h_menuDummy ;use memoized value (if it exists) [color=red] if MI_getMenuHandleCache(menu_name) return MI_getMenuHandleCache(menu_name)[/color] If h_menuDummy= { Menu, menuDummy, Add Menu, menuDummy, DeleteAll Gui, 99:Menu, menuDummy Gui, 99:Show, Hide, guiDummy old_DetectHiddenWindows := A_DetectHiddenWindows DetectHiddenWindows, on Process, Exist h_menuDummy := DllCall( "GetMenu", "uint", WinExist( "guiDummy ahk_class AutoHotkeyGUI ahk_pid " ErrorLevel ) ) If ErrorLevel or h_menuDummy=0 return 0 DetectHiddenWindows, %old_DetectHiddenWindows% Gui, 99:Menu Gui, 99:Destroy } Menu, menuDummy, Add, :%menu_name% h_menu := DllCall( "GetSubMenu", "uint", h_menuDummy, "int", 0 ) DllCall( "RemoveMenu", "uint", h_menuDummy, "uint", 0, "uint", 0x400 ) Menu, menuDummy, Delete, :%menu_name% ;memoizes values for later use [color=red]MI_setMenuHandleCache(menu_name, h_menu) MI_setMenuName(h_menu, menu_name)[/color] return h_menu }
And then the getter/setter used
MI_getMenuHandleCache(menu_name) { return MenuIconsMenuHandle[%menu_name%] } MI_setMenuHandleCache(menu_name, h_menu) { global MenuIconsMenuHandle[%menu_name%] := h_menu } MI_getMenuName(h_menu) { return MenuIconsMenuName[%h_menu%] } MI_setMenuName(h_menu, menu_name) { global MenuIconsMenuName[%h_menu%] := menu_name }
MI_getMenuHandleCache allows you to get the menu handle for the given menu name, and
MI_getMenuName allows you to get the menu name for the given menu handle.
Add OOP to your scripts via the Class Library. Check out my scripts.
Old version:
MI_GetMenuHandle(menu_name) { static h_menuDummy If h_menuDummy= { Menu, menuDummy, Add Menu, menuDummy, DeleteAll Gui, 99:Menu, menuDummy Gui, 99:Show, Hide, guiDummy old_DetectHiddenWindows := A_DetectHiddenWindows DetectHiddenWindows, on Process, Exist h_menuDummy := DllCall( "GetMenu", "uint", WinExist( "guiDummy ahk_class AutoHotkeyGUI ahk_pid " ErrorLevel ) ) [color=darkred]If ErrorLevel or h_menuDummy=0 return 0[/color] [color=red]DetectHiddenWindows, %old_DetectHiddenWindows%[/color] Gui, 99:Menu Gui, 99:Destroy } Menu, menuDummy, Add, :%menu_name% h_menu := DllCall( "GetSubMenu", "uint", h_menuDummy, "int", 0 ) DllCall( "RemoveMenu", "uint", h_menuDummy, "uint", 0, "uint", 0x400 ) Menu, menuDummy, Delete, :%menu_name% return h_menu }
should be: (moved the restore of the DetectHiddenWindows setting above the return statement)
MI_GetMenuHandle(menu_name) { static h_menuDummy If h_menuDummy= { Menu, menuDummy, Add Menu, menuDummy, DeleteAll Gui, 99:Menu, menuDummy Gui, 99:Show, Hide, guiDummy old_DetectHiddenWindows := A_DetectHiddenWindows DetectHiddenWindows, on Process, Exist h_menuDummy := DllCall( "GetMenu", "uint", WinExist( "guiDummy ahk_class AutoHotkeyGUI ahk_pid " ErrorLevel ) ) [color=red]DetectHiddenWindows, %old_DetectHiddenWindows%[/color] [color=darkred]If ErrorLevel or h_menuDummy=0 return 0[/color] Gui, 99:Menu Gui, 99:Destroy } Menu, menuDummy, Add, :%menu_name% h_menu := DllCall( "GetSubMenu", "uint", h_menuDummy, "int", 0 ) DllCall( "RemoveMenu", "uint", h_menuDummy, "uint", 0, "uint", 0x400 ) Menu, menuDummy, Delete, :%menu_name% return h_menu }
and you didn't restore the setting for DetectHiddenWindows here, was it intentional?
MI_OwnerDrawnMenuItemWndProc(hwnd, Msg, wParam, lParam) { static WM_DRAWITEM = 0x002B, WM_MEASUREITEM = 0x002C, WM_COMMAND = 0x111 static ScriptHwnd Critical 500 if (Msg = WM_MEASUREITEM && wParam = 0) { ; MSDN: wParam - If the value is zero, the message was sent by a menu. h_icon := NumGet(lParam+20) if !h_icon return false ; Measure icon and put results into lParam. VarSetCapacity(buf,24) if DllCall("GetIconInfo","uint",h_icon,"uint",&buf) { hbmColor := NumGet(buf,16) hbmMask := NumGet(buf,12) x := DllCall("GetObject","uint",hbmColor,"int",24,"uint",&buf) DllCall("DeleteObject","uint",hbmColor) DllCall("DeleteObject","uint",hbmMask) if !x return false NumPut(NumGet(buf,4,"int")+2, lParam+12) ; width NumPut(NumGet(buf,8,"int") , lParam+16) ; height return true } return false } else if (Msg = WM_DRAWITEM && wParam = 0) { hdcDest := NumGet(lParam+24) x := NumGet(lParam+28) y := NumGet(lParam+32) h_icon := NumGet(lParam+44) if !(h_icon && hdcDest) return false return DllCall("DrawIconEx","uint",hdcDest,"int",x,"int",y,"uint",h_icon ,"uint",0,"uint",0,"uint",0,"uint",0,"uint",3) } else if (Msg = WM_COMMAND && !(wParam>>16)) ; (clicked a menu item) { [color=red]DetectHiddenWindows, On[/color] if !ScriptHwnd { Process, Exist ScriptHwnd := WinExist("ahk_class AutoHotkey ahk_pid " ErrorLevel) } if (hwnd != ScriptHwnd) { ; Forward this message to the AutoHotkey main window. PostMessage, Msg, wParam, lParam,, ahk_id %ScriptHwnd% return ErrorLevel } } if A_EventInfo ; Let the "super-class" window procedure handle all other messages. return DllCall("CallWindowProc","uint",A_EventInfo,"uint",hwnd,"uint",Msg,"uint",wParam,"uint",lParam) else ; Let the default window procedure handle all other messages. return DllCall("DefWindowProc","uint",hwnd,"uint",Msg,"uint",wParam,"uint",lParam) }
I just want to say that I never know if correcting another's code comes off as offensive. If you took offense, sorry, that wasn't my intent.
Add OOP to your scripts via the Class Library. Check out my scripts.
Right.should be: (moved the restore of the DetectHiddenWindows setting above the return statement)
Yes - MI_OwnerDrawnMenuItemWndProc executes on a new thread, so A_DetectHiddenWindows is saved and restored automatically.and you didn't restore the setting for DetectHiddenWindows here, was it intentional?
Add OOP to your scripts via the Class Library. Check out my scripts.
There is a problem, but only if the menu is deleted and recreated - i.e. because the handle changes. Is there any particular reason you think the menu handles need to be cached?
The other reason is that I presume that many people, myself being one, don't want to have to keep track of another variable to store the handle of the menu when using your suite. Instead, I would much rather call the functions using the menu name (versus the handle) and have the code cache for me.
Another benefit is that anyone else that has any menu wrappers they want to add to your suite (and mine when ready) will then be able to have an easy link (since both the menu names and handles will be easily linked).
In my menu wrapper I will account for the fact that deleting a menu will change the handles. Will it change just the menu handle for the deleted menu or for all menus?
I am just tired of having to keep track of all the values that I use for my menus - the menu handle, the item names, the displayed item names (which may differ if for example the item contains ampersands as literals), a pointer to the menu data (so to allow easy moving of menu items in the menu without having to move the menu data that goes with it), the menu label, the menu size (both the displayed menu size, and the number of items already added to the "menu storage"), and others that are program dependant - which is why I'm creating a menu wrapper. Then, I thought that I could combine it with the functonality of your menu icons program - and to do this (and account for all the code that uses menu handles and not menu names), I need all my functions that use menu names to allow the handle to be used instead (which then checks the cache for the menu name).
So, in "short", it's because I think AHK needs "good" wrapper methods so that menu creation is easier (like we have many GUI wrappers to make GUI creation easier).
The code should be up in a few days (working out some of the small details), so you don't have to decide now, and instead can see my menu wrapper and see if you want to.
I believe that the code recommendations are backward compatable and thus all the code already running will be fine - I just thought that a little upgrade would help to incorporate your suite with mine (and any others that do/will use menu names to store menu data)
Also, to clear up any confusion I may have created, when I say "incorporate" I don't mean they need to be combined, just that by adding these functions they can "work well" with each other (and other menu suites that are/will be developed through the life of AHK).
Add OOP to your scripts via the Class Library. Check out my scripts.
You can simply use the menu name, hence the parameter name "MenuNameOrHandle". [Edit] There probably isn't much performance benefit to caching the menu handle in a dynamic variable.The other reason is that I presume that many people, myself being one, don't want to have to keep track of another variable to store the handle of the menu when using your suite.
Technically the menu handle does not change - one menu is deleted, and another is created with the same name.Will it change just the menu handle for the deleted menu or for all menus?
Perhaps a built-in method to retrieve the menu handle is worth considering.So, in "short", it's because I think AHK needs "good" wrapper methods so that menu creation is easier
I'm not sure what you mean here. Does this mean that the other menu handles remain the same?Technically the menu handle does not change - one menu is deleted, and another is created with the same name.
This is a very good idea - do you have a reference so I can figure out how to do that?Perhaps a built-in method to retrieve the menu handle is worth considering.
Add OOP to your scripts via the Class Library. Check out my scripts.