Menu Icons
Provides a set of functions for implementing icons in menus.

Known Issues
  • Using MI_EnableOwnerDrawMenus on the script's main window:
    • breaks the "Pause Script" menu item; and
    • makes ListLines less useful, since interacting with the main window causes script to execute.

Usage example(s):
Code (Expand):
; 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
Menu, M, Add, 32x32 Icon, ItemClick
Menu, M, Add, 48x48 Icon, ItemClick

; Set item 1's icon to shell32.dll, icon 4, 16x16.
MI_SetMenuItemIcon("M", 1, "shell32.dll", 4, 16)
; Set item 2's icon to shell32.dll, icon 4, 32x32.
MI_SetMenuItemIcon("M", 2, "shell32.dll", 4, 32)
; Windows 2000 or later required (supports sizes other than 16x16 and 32x32):
MI_SetMenuItemIcon("M", 3, "shell32.dll", 4, 48)
; Usually looks better:
MI_SetMenuStyle("M", 0x4000000)

; Note: This menu is shown automatically after setting up the tray menu.


;
; Icons in the Tray menu!
;
; Refer to a menu by handle for efficiency.
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:
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)
        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
    }
}



Download (or view) MI.ahk
Covered by Lexikos' default copyright license.

Version History
v2.21 (2010-03-13)
  • Fixed: WM_COMMAND messages received by subclassed GUI windows were erroneously forwarded to the script's main window. This broke button gLabels.
  • Fixed: Unicode incompatibility in MI_ExtractIcon.
v2.2 (2009-01-08)
  • Changed MI_SetMenuItemIcons to automatically delete the previous icon or bitmap.
  • If individual menu items are to be removed, call MI_SetMenuItemIcons(MenuNameOrHandle, ItemPos, 0) to remove the icon.
  • Added MI_RemoveIcons, to be called before deleting a menu.
  • Misc optimizations (thanks animeaime).
v2.1 (2007-12-25)
  • Added MI_EnableOwnerDrawnMenus(), which can be used to enable owner-drawn menus for a given window (as an alternative to MI_ShowMenu().)
  • MI_SetMenuStyle() now accepts a menu name or handle.
v2 (2007-10-19)
  • Now stdlib compatible (added MI_ prefix to functions.)
  • Merged everything into MI.ahk, removing wrapper functions in the process.

Operating System Notes:

On Windows Vista, the script generates a 32-bit device-independent bitmap. This allows nice transparency (alpha blending) in icons, and allows Vista's menu style to apply.

On earlier versions of Windows, this method is not supported. Instead, the menu icons are owner-drawn. ShowMenu() or ShowOwnerDrawnMenu() must be called to show the menu with icons. ShowOwnerDrawnMenu() creates an invisible message window to act as the menu's owner (once per instance of the script.) As a side effect, showing a menu should not make the script/thread uninterruptible.

On Windows 2000 and later, PrivateExtractIcons() is used by SetMenuItemIcon() to extract an icon from a file. This should support any size of icon (tested with 16, 32 and 48.)

ExtractIconEx() is used on earlier versions of Windows, as PrivateExtractIcons() is not available. This supports 16x16 and 32x32 icons only. Other sizes may be specified, but the icon will be stretched to fit, regardless of which sizes are available in the source file. (However, LoadImage() can still be used to load the first icon of any given size from an .ico file.)

Other Methods

MI.Test.ahk (requires MI v1) shows a few other ways of displaying icons. In the following screenshot I've changed the menu colour to distinguish between the bitmap and the menu background. (On most versions of Windows - but not Vista - the menu background would be the same colour as the bitmap backgrounds.)



The "FakeAlphaTest" method could be used to fake transparency in a situation where owner-drawing is not an option (i.e. for some other app's menus.) The main issue with this method is:
(with Windows Standard/Classic colour scheme)
If you look closely, you can see the "background" in the bitmap is the inverse of the menu colour, and is a different colour to the selection. (Not an issue if you change the selection colour to match Laughing.)

You can see what effect owner-drawing icons has on Vista:

(no visual style)


Bitmap Manipulation

MI.Test.ahk (specifically the "FakeAlpha" tests; MI.Test.ahk requires MI v1) also demonstrates how to manipulate bitmaps using AutoHotkey. It would be possible, for example, to dynamically colour an icon.

References
Shell Blog: Vista Style Menus, Part 1 - Adding icons to standard menus
nanoANT: Themed menu’s icons, a complete Vista and XP solution


Note: I chose to write this rather than use MMenu because:
  • I did not want a whole new menu API.
  • MMenu exclusively uses owner-drawing for icons, which disables Windows Vista's menu styles.