Themed menu’s icons, a complete Vista and XP solution (updated)
Update: Steve King has patched my Vista GDI+ based menus with pure GDI method at TortoiseSVN revision 14191 as described lately by Microsoft. Pure GDI method no longer requires GDI+, which is not present in Premium versions of Vista, maintaining full compatibility with older versions of Windows.
I’m an author of few patches for both Tortoise SVN and Tortoise CVS that makes them display the explorer’s context menu icons nicely on XP and Windows 2000. Both programs are implementing IContextMenu and using QueryContextMenu function to create items of popup menu of explorer. Briefly the called extension must fill menu items with InsertMenuItem using suplied HMENU hmenu parameter.
During development of those few patches I’ve learnt some few new things about way we make icons displayed next to menu items I want to share with you…
How to get icons in context menus
Methods described here are related to shell context menu extension, however they can be used in any Windows application.
hbmp(Un)checked method
Initially both Tortoises were filling hbmpUnchecked & hbmpChecked fields of MENUITEMINFO that is passed to InsertMenuItem with HBITMAP created from icon to get icons on menu item. This solution works on all Windows since 95. However the strong limitation is that HBITMAP must be SM_CXMENUCHECK x SM_CYMENUCHECK (usually 12 x 12). So if you are using 16 x 16 icon, the icon gets squished and looks awfully. The function used to convert icon to bitmap is:
HBITMAP
Ownerdraw method
Tortoise SVN was using also owner draw method. I won’t describe here details of this method. This relays on MENUITEMINFO fType flag set to MFT_OWNERDRAW. Shell extension in HandleMenuMsg2 callback should handle WM_MEASUREITEM and WM_DRAWITEM. This method is generally OK, however it has several flaws:
- We need to measure & draw menu in all stated ourselves, which makes us write plenty of code.
- Ownerdraw menus are not respecting visual styles of Windows XP or Vista. We would need to use uxtheme functions to somehow handle rendering of menu parts on those systems.
- We need to keep exta context information for each menu item with text, icon handle, etc.
- Keyboard shortcuts doesn’t work automatically, we must handle WM_MENUCHAR to make them work.
HBMMENU_CALLBACK method
Since Windows 98 MENUITEMINFO has extra field hbmpItem. This field can be used for setting the HBITMAP with bitmap that is displayed next to the menu item. hbmpItem can be set also to HBMMENU_CALLBACK which will make menu item work like owner-draw, but WM_MEASUREITEM & WM_DRAWITEM just need to handle icon drawing, rest will be done by Windows. This method is easiest to implement and so it is used inside many application, I just name on I use or develop: wxWidgets SDK, Miranda IM. We just need to initialize menu item like that:
MENUITEMINFO menuiteminfo;
menuiteminfo.cbSize = sizeof(menuiteminfo);
menuiteminfo.fMask = MIIM_FTYPE | MIIM_ID | MIIM_SUBMENU | MIIM_DATA | MIIM_BITMAP | MIIM_STRING;
menuiteminfo.fType = MFT_STRING;
menuiteminfo.dwTypeData = lpszMenuTitle;
menuiteminfo.cch = _tcslen(lpszMenuTitle);
menuiteminfo.hbmpItem = HBMMENU_CALLBACK;
menuiteminfo.wID = id;
Rest is done in WM_MEASUREITEM where we need to just make sure we have space for 16 x 16 image using:
case WM_MEASUREITEM:
break;
Then to draw an icon we need to handle WM_DRAWITEM, but just drawing the icon, nothing else:
case WM_DRAWITEM:
break;
Simple ? Yes it is. However there are some issues with this method as well:
- When this method is used on Windows 2000 shell extension window background popup menu, then shell.dll is removing text from the menu, so we see just icons.This is obviously a bug of Windows 2000 shell.dll, because MSDN documentation states this shall work regardless of the sittuation, but we need to somehow get over it. Surprisingly it does work fine when we right-click on explorer item (file or folder). The easiest solution is using hbmp(Un)checked method when uFlags == 0 of QueryContextMenu, which indicated we clicked the background, so we fall back to most primitive method, but at least we got text and “some” icons in the menu.Note: This bug only appears in Windows 2000 explorer’s background menu of shell extension, so in every other situation as standalone program menus HBMMENU_CALLBACK method can be used without any problem. So you may not care about it unless you are shell extension developer.
- Vista is removing menu theme when some menu item has hbmpItem set to HBMMENU_CALLBACK, so we will have nice icons and nice menu, but if we want to have nice icons with 100% themed menu on Vista we need to use last method…
Vista PARGB32 hbmpItem bitmap method (Updated)
Vista strongly relays on 32-bit pre-multiplied alpha RGB bitmaps for rendering its interface. In Vista hbmpItem can be set to PARGB32 HBITMAP and this bitmap will be nicely displayed by Vista together with theming as you can see on the screenshot at right. I got know of this possibility reading nice article Vista Style Menus, Part 1 – Adding icons to standard menus at ShellRevealed blog.
The most important question is how to we get our icon (regardless it is 32-bit with alpha, or 256-color with mask) converted to PARGB32 HBITMAP. Windows API doesn’t give such possibility straight of the box. Article from ShellRevealed proposes WIC (Windows Imaging Component) which is cool & quite simple for conversion or Vista‘s GDI method, but those require Vista SDK, which may be annoying for those using Visual Studio‘s out of the box.
As an alternative to that I’ve used Gdiplus which is present on most of the systems since Windows 2000, and most shipped with Visual Studios Platforms SDKs. This method is also much simpler than WIC or GDI method from described article.
Alternative solution to WIC is to use pure Vista GDI (UxTheme) calls as described at http://msdn.microsoft.com/en-us/library/bb757020.aspx?s=6 (GDI_CVistaMenuApp.cpp sample). It was implemented in TortoiseSVN revision 14191 by Steve King. It uses BeginBufferedPaint, EndBufferedPaint and GetBufferedPaintBits dynamically loaded from Vista‘s UXTHEME.DLL and Create32BitHBITMAP and ConvertBufferToPARGB32 from GDI_CVistaMenuApp.cpp sample.
The PARGB32 function is as follows:
HBITMAP
So in this case instead menuiteminfo.hbmpItem = HBMMENU_CALLBACK we do menuiteminfo.hbmpItem = IconToBitmapPARGB32(lpszIconResourceID). We shouldn’t forget of initializing Gdiplus library with GdiplusStartup(&m_gdipToken, &gdiplusStartupInput, NULL) in program/DLL initialization code and shutting it down after all with GdiplusShutdown(m_gdipToken).
If we want to be compatible with older Windows versions, we shall load (map) the Vista‘s UXTHEME.DLL functions dynamically only on Vista:
typedef DWORD ARGB;
typedef
typedef
typedef
/* (...) */
HMODULE hUxTheme = ::GetModuleHandle (_T("UXTHEME.DLL"));
pfnGetBufferedPaintBits = (FN_GetBufferedPaintBits)::GetProcAddress(hUxTheme, "GetBufferedPaintBits");
pfnBeginBufferedPaint = (FN_BeginBufferedPaint)::GetProcAddress(hUxTheme, "BeginBufferedPaint");
pfnEndBufferedPaint = (FN_EndBufferedPaint)::GetProcAddress(hUxTheme, "EndBufferedPaint");
Since we are going to use Gdiplus only on Vista, we may use Gdiplus.dll as delayed load DLL, so it won’t be loaded on older systems using previous methods, saving us some memory. Simple enough ?
Testing Windows version number with GetVersionEx and combining this method for Vista with HBMENU_CALLBACK method for Windows XP and older systems (with hbmp(Un)checked fallback on explorer extension on Windows 2000 if needed) is my opinion best method of having nice menus in all modern Windows systems. This is also current display method of Tortoise CVS & Tortoise SVN latest development versions. If you need full code to browse you may want look into Tortoise SVN SVN trunk files: srcTortoiseShellContextMenu.cpp, srcTortoiseShellShellExt.cpp and srcTortoiseShellShellExt.h.
Conclustion
All those hacks and recipes would be worthless if only there was simple consistent API for making menu item icons. Unfortunately menu icons, something that was always present in Windows and Microsoft applications, never got any decent API, moreover the methods to get those icons working change for every major Windows release, making us developers wasting our time “porting” our applications to new “shinny” Windows rather than doing something productive.
One thing that is simply unacceptable for me (even more since I now work regularly on OSX) is that Windows system apps and Microsoft regular applications are using so many UI hacks and mods that are never exposed to the developers trough API. Those are either closed libraries like one for Office’s or Visual Studio GUI, or Vista hacks with PARGB that require tricky in memory conversions rather than just pointing hbmpItem to HICON and making Vista to do the conversion on its own.

Adam, thank you for wonderful post. I tried to implement owner-draw method today, but there are too much problems. WM_MENUCHAR doesn’t work at all. As far as I understand, shell interpret this message itself and doesn’t pass it to HandleMenuMsg2. (This article – http://www.codeproject.com/shell/ShellExtGuide7.asp – confirms that only WM_MEASUREITEM and WM_DRAWITEM are passed to HandleMenuMsg / HandleMenuMsg2.) So you must not use owner-draw menu items in context menu if you want keyboard accelerators.
When using owner-draw method, there is also a minor problem with positioning the icon on Win98. And, as you said, a lot of code need to be written (I wrote 83 lines by now, and it’s still not perfect).
So, I’m thinking of swithing to hbmp(un)checked method. Could you please tell how does it look on Vista? If you have a screenshot, please post it. Thank you in advance.
Very well explained, thank you. Just one question, when using the hbmp(Un)checked method, the icons get color-inverted when highlighted? because that is what I get.
I am using Vista Home Premium 64bit and there is no gdiplus installed by default afaik. Are you sure that this is the easiest method available? I don’t really like to install PSDK just so use TortoiseSVN and I am sure more other users aren’t going to like it much either.
XP does not have themed menus (Other than start-menu .. all programs, but that is not a menu, its a toolbar (yes, really)) The only thing you need to do to get 100% correct icons on XP is to check to see if flat menus are on, if they are, the selection rectangle needs a border with COLOR_HIGHLIGHT and fill it with COLOR_MENUHILIGHT
Carlos: Indeed hbmp(Un)checked makes all icons to be inverted when item is focused, this is the way it works, probably due it was designed to handle monochrome bitmaps in most circumstances.
Dieter: Are you sure they are not present in the system? AFAIK GdiPlus is shipped with all Windows begging with XP, please have a look coz newer systems may not have GdiPlus.dll inside of system32 folder directly but inside WinSxS, i.e.:
C:\WINDOWS\WinSxS\amd64_Microsoft.Windows.GdiPlus_6595b64144ccf1df_1.0.3790.3959_x-ww_B45BA3BE\GdiPlus.dll
C:\WINDOWS\WinSxS\x86_Microsoft.Windows.GdiPlus_6595b64144ccf1df_1.0.3790.3959_x-ww_8251BDDE\GdiPlus.dll
Having them in WinSxS requires proper manifest inside your application to load.
Anders: If you have read the article more carefully you’d see that the problem is actually ownerdraw method that requires much more code and attention, also it makes keyboards shortcuts stop working. Moreover “themed” is just a term, you say XP menus are not themed, but they may be flat or not. But isn’t this flatness a part of XP theming functionality? How do you tell menu is flat or now w/o using uxtheme API?
I tried to do this kind of thing in VB6 for Vista, however I was creating the icon on the fly. The icon I am creating is actually just a red checkmark.
Unfortunately, I cannot seem to get this to work if the user is not running in 32-bit color mode. Any thoughts or insights would be helpful. If you would like me to post the code I used, just let me know. (It is on my other PC)
Dieter: Indeed I’ve seen numerous reports that some Vista are not shipped with GDI+. What a mess, Microsoft is making a retreat from a technology that was “forcing” last few years. I have updated my recipe to include now pure GDI method described on MSDN and committed into TortoiseSVN.
Ad. Carlos, Peter: hbmp(Un)checked method always inverts the icon on highlight, moreover icons are squashed – not rendered full 16×16 size (as you can see on the 1st screenshot). While this may be acceptable on not really pretty Windows 2000 UI, on Vista it just looks ugly. So pure GDI way on Vista is the best way I presume now and it is how TortoiseSVN is now.
For a full implementation code please have a look at: http://guest@tortoisesvn.tigris.org/svn/tortoisesvn/trunk/src/TortoiseShell/ContextMenu.cpp
Mark: I am very sorry but I don’t use VB really. Just updated the Vista method to pure GDI way, maybe you can just have it working with VB6 too instead of dealing with GDI+
Lines 2504-2513 of ContextMenu.cpp, I believe, can be simplified to just *pargb++ = *pargbMask++.
Actually more like *pargb++ = 0xFF – *pargMask++.
If you do what you mention, you should also set the style of the menu using MNS_CHECKORBMP. Otherwise you end up with two icons. Say, if you have Notepad++ installed and TortoiseSVN, the context menu will show a column for the check bitmap (hbmpChecked) and a column for the item bitmap (hbmpItem). The occurs in Vista or Windows 7 with the Classic theme. I believe the Aero theme sets this style on the context menu by default.
http://social.msdn.microsoft.com/Forums/en-US/windowsuidevelopment/thread/b3dde0f6-b20b-4a96-885e-f72bad3a2ae6
[...] http://www.nanoant.com/programming/themed-menus-icons-a-complete-vista-xp-solution [...]
Hi,
I’m trying to implement a shell extension that extends IContextMenu3 and IShellExtInit, and i’m inserting menu items using the method described in section “HBMMENU_CALLBACK method” but in my project the method HandleMenuMsg2 or the HandleMenuMsg is never called.
Can anyone please explain me what is required to receive the HandleMenuMsg2 calls?
My ATL object is implemented like that:
// CTestPlugin
class ATL_NO_VTABLE CTestPlugin :
public CComObjectRootEx<CComMultiThreadModel>,
public CComCoClass<CTestPlugin, &CLSID_CTestPlugin>,
public IShellExtInit,
public IContextMenu3
{
public:
DECLARE_REGISTRY_RESOURCEID(IDR_TESTPLUGIN)
DECLARE_NOT_AGGREGATABLE(CTestPlugin)
BEGIN_COM_MAP(CTestPlugin)
COM_INTERFACE_ENTRY(IShellExtInit)
COM_INTERFACE_ENTRY(IContextMenu)
COM_INTERFACE_ENTRY(IContextMenu2)
COM_INTERFACE_ENTRY(IContextMenu3)
END_COM_MAP()
DECLARE_PROTECT_FINAL_CONSTRUCT()
…
// IShellExtInit
STDMETHODIMP Initialize(LPCITEMIDLIST, LPDATAOBJECT, HKEY);
// IContextMenu
STDMETHODIMP GetCommandString(UINT, UINT, UINT*, LPSTR, UINT)
{ return S_OK; }
STDMETHODIMP InvokeCommand(LPCMINVOKECOMMANDINFO);
STDMETHODIMP QueryContextMenu(HMENU, UINT, UINT, UINT, UINT);
// IContextMenu2
STDMETHODIMP HandleMenuMsg(UINT uMsg, WPARAM wParam, LPARAM lParam);
// IContextMenu3
STDMETHODIMP HandleMenuMsg2(UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT *pResult);
And i’m inserting menu items like described in the nanoANT page:
bool CTestPlugin::AddNewMenuItem(HMENU hmenu, UINT un_menu_text_id, UINT un_menu_index, UINT icon, UINT& uCmdID)
{
TCHAR chText[MAX_PATH];
::LoadString(
_AtlModule.m_hResInstance,
un_menu_text_id,
chText,
MAX_PATH);
MENUITEMINFO menuiteminfo;
ZeroMemory(&menuiteminfo, sizeof(menuiteminfo));
menuiteminfo.cbSize = sizeof(menuiteminfo);
menuiteminfo.fMask = MIIM_FTYPE | MIIM_ID | MIIM_SUBMENU | MIIM_DATA | MIIM_BITMAP | MIIM_STRING;
menuiteminfo.fType = MFT_STRING;
menuiteminfo.dwTypeData = chText;
menuiteminfo.cch = _tcslen(chText);
if (icon) {
menuiteminfo.hbmpItem =
SysInfo::Instance().IsVistaOrLater()
?
_AtlModule.m_iconBitmapUtils.IconToBitmapPARGB32(_AtlModule.m_hResInstance, icon)
:
HBMMENU_CALLBACK;
}
menuiteminfo.wID = (UINT)uCmdID++;
m_mapIdToIcon[menuiteminfo.wID] = icon;
return (TRUE==InsertMenuItem(hmenu, un_menu_index, TRUE, &menuiteminfo));
}
STDMETHODIMP CTestPlugin::HandleMenuMsg(UINT uMsg, WPARAM wParam, LPARAM lParam)
{
LRESULT res;
return HandleMenuMsg2(uMsg, wParam, lParam, &res);
}
STDMETHODIMP CTestPlugin::HandleMenuMsg2(UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT *pResult)
{
…
}
With all this the menu entries apear in explorer context menu but no images are displayed, both methods HandleMenuMsg and HandleMenuMsg2 are never called, and the system that i’m testing is WinXP (in vista all is ok because we use the hbmpItem).
I’m missing some inicialization or what? can anyone explain me?
Thanks