Rev 1.2

Introduction

Changing the default appearance of elements of the Windows GUI has been a never ending challenge for Windows programmers since the first release of the Windows API. And anyone who has seriously tried to change the appearance of menus (beyond simple owner-draw techniques) will know that it represents one of holy grails of Windows GUI programming.

Note: To reduce the clutter in the article I am going to use the word 'skin' in place of 'changing the default appearance'.

What I want to present here is an entirely new and original approach to skinning menus which will allow you to skin every menu that your application produces, including (but not restricted to):

menubars (e.g.. application main menu)

all right-click (context) popup menus (including your own and those of internet explorer and the standard file dialog)

the system menu (both in a window's title bar and on the Taskbar)

toolbar drop-down menus

your own or 3rd party owner-drawn menus (check-out the 'New' and 'Send To' sub menus in the File Dialog)

The Challenge

Windows menus have always been a black box to developers, with all of the implementation hidden away inside user32.dll. All that Windows has allowed a developer to do is to define the menu as owner-draw and then handle the WM_MEASUREITEM and WM_DRAWITEM messages for drawing one or more menu items.

This is fine if your interest in owner-draw menus is to replace the standard text interface of, say, a 'select color' menu with menu items displaying the colors themselves, but it falls way short of an architecture to replace all menus with a different style to the gray (or COLOR_MENU) default.

In particular:

It would require you to have access to every menu handle in your application so that you could modify them to be owner-draw (as an experiment: try getting a handle to the context menu of an edit box)

It would require you to be able to insert code into every window or control that displayed the menu to handle the WM_MEASUREITEM and WM_DRAWITEM messages

It provides no mechanism for replacing the non-client (border) drawing with your own

Much more fundamentally however, there is no way for you to handle menus that someone else (like Windows itself) has already defined as owner-draw, because generally you will have no idea what memory structures have been used for storing the information needed for the drawing stage.

And it is this last point which ultimately kills off owner-draw as a possible solution to the problem.

The Solution

Part 1

How the 'solution' occurred to me, I have no idea. I may have been taking a bath, or possibly sitting under a tree when an apple fell on my head, I don't know. But it did. 'What if', I thought, 'I could redirect all of the painting of a menu away from the screen and onto a memory DC so that i could post-process it. 'Then I could do anything I liked to it before finally rendering the menu on the screen.' And the rest as they say, was hard slog.

Part 2

The implementation of this solution can be broken down into 2 distinct tasks which needed solving:

A means of detecting when a menu is about to be shown.

A way of overriding the drawing for a particular menu.

The solution to the former was definitely the easier of the two. All I needed to do was to install a Windows Hook to detect the creation of all menu windows.

Note: For those of you who don't know, menu windows have a special class name (#32768) which makes their detection no more difficult that any other window.

Having caught the menu window just before its creation, however, still leaves the question, 'What to do with it?' to which the answer is (of course) subclassing. And this is where I must give due thanks to Paul DiLascia and his now legendary CSubclassWnd implementation. CSubclassWnd is an MFC class that Paul wrote to allow the interception and overriding of all Windows messages for a given window, even when you did not create the window itself. So that's essentially it: Catch all menu windows just before they are shown and subclass them so that we can override the drawing.

Overriding the Default Drawing

Its often the case in programming (and certainly the case in engineering) that if the design is sound then the implementation will generally fall into place. Unfortunately, this was not exactly one of those occasions. I've found with Windows GUI programming that there are just too many quirks and variations between the various flavours of Windows (95 -> XP) not to have to resort to using undocumented tweaks and fudge factors to achieve a reasonable solution.

Moreover, in this particular situation, there was no documentation to either support or suggest that I stood any reasonable chance of being successful. My first task was to determine what Windows messages prompted menus to (re)draw themselves so that I could replace these with my custom implementations. Fortunately, what I found was that menu drawing is quite simple (if undocumented). This is a summary of the Windows messages which needed to be handled:

WM_PRINTCLIENT - requests the menu to draw its client area in the DC supplied in wParam (Windows 9x, 2K, XP)

WM_PAINT - requests the menu to draw the foreground of its invalid area (Windows 9x)

WM_ERASEBKGND - requests the menu to draw the background of its invalid area (Windows 9x)

0x01e5 (undocumented) - requests the menu to redraw the item supplied in the message wParam using an internal method (Windows 9x, 2K, XP)

This last message (0x01e5) is the most interesting and the most crucial discovery I made on this project. Windows sends it to the menu every time you twitch the mouse inside or outside the menu, so that the menu can redraw the specified item. At first it might seem like a gross inefficiency since no other window controls behave that way, but if you've got menu animation turned on you'll see why its necessary.

Normally when GUI events happen in Windows its usual to wait until they are complete and then redraw the control as required. Not so with menu fading; in this case, the menu fading is carried out after the event has happened using a system timer (I believe). So if you only redraw the menu once after the event triggers then you get drawing artifacts all over the place. Luckily, it was simply a matter of calling SetRedraw() before and after the default implementation of this message to ensure that the menu did not do its 'under the cover' drawing, followed by invalidating the rectangle of the menu item in question.

Replacing the System Colors

Once I had control of the redrawing, I still needed to work out how to replace the system colors with the users choices.

As I had already been working extensively on a application skinning system and had been using TransparentBlt() to good effect there, I hit on the idea of using it to do multi-pass post-processing of the menu image, replacing one system color in each pass.

I had anticipated a fair performance hit with this idea but in practice it seems to be quite reasonable. Even under Windows 98 where I have substituted my own implementation of TransparentBlt() (the default version has a well documented resource leak) it still performs quite acceptably, although I have not done nearly enough testing on lower end machines.

I have also gone to some lengths to ensure that I carry out the minimum number of TransparentBlt() in thsoe case where some system colors resolve to the same COLORREF.

Other Implementation Details

Some of you sharper readers may have noticed an implicit reference to a menu handle (HMENU) in the previous paragraph ('...rectangle of the menu item...') which I had not mentioned before. Whilst its true that the menu skinning can be achieved without having the menu handle, the implementation can be made more efficient by only invalidating the menu items which need redrawing rather than the whole window.

Determining the menu handle, however, is a real pain because Windows provides no explicit mapping between a menu window and its associated menu handle. This is where the fudge I referred to earlier comes in. To retrieve the menu handle, I wait until I detect a WM_INITMENUPOPUP and then either attach it to the menu window if it has already been created, or hold on to it until the menu window is subclassed and add it then. There's no guarantee that the match-up will correct but it seems to work okay. A real fudge but without an alternative as far as I can see.

Using the Code

Add the following source files to your project:

Note: in my demo project, these files are in a separate 'skinwindows' folder because they form a subset of a much larger skinning system, but there is no need for you to do the same.

Add NO_SKIN_INI to the preprocessor definitions in your project settings. This is to avoid compilation problems due to missing files, because this project forms part of a larger system which supports loading color information from a file, which is not included here.

Have a look at the implementation of CSkinMenuMgr::Initialize() for more detail on the options available. In particular you can elect to display a sidebar of a given width and give the menu border a flat or beveled edge.

For total control over how the menus are drawn, including the menu background, derive a class from ISkinMenuRender which is defined in skinmenu.h and pass to CSkinMenu::SetRenderer() which is a static method. Then during the drawing process, the CSkinMenu class will call back into your derived class giving you the opportunity to drawn whichever portions of the menu you choose to. (see CSkinMenuTestDlg for an example implementation)

Further Work

It doesn't handle scrolling menus (thanks to saltynuts2002)

It doesn't work under Windows CE (thanks to João Paulo Figueira)

It's a bit slow under XP.

The rendering speed could probably be improved generally by taking more account of the clip box.

Warnings

When using keyboard navigation under the debugger be prepared for an internal Windows breakpoint to show up; something that does seem to be a problem outside the debugger.

To put it bluntly, debugging under Windows 98 and Me is to be avoided if at all possible. Windows does not like having menus skinned on these platforms and you should expect regular reboots if you set breakpoints inside the menu redrawing code.

Although the demo application runs fine on Windows 98 and Me, the more fragile nature of these platforms means that the slightest bugs can crash the system (not Blue-Screen but you still have to restart)

When carrying out non-client drawing it is imperative to restore the state of the DC before you finish. It appears that Windows re-uses the same DC for rendering all menus, and once you've messed with it it stays messed.

It's a bit slow under XP for reasons I'm looking into.

Please take the time to read the code in detail before asking any questions about it.

Copyright

The code is supplied here for you to use and abuse without restriction, except that you may not modify it and pass it off as your own. The concept and design, however, remains my intellectual property in perpetuity.

History

1.0 Initial Release

1.1 Bug Fixes

added support for keyboard navigation, including multi-column menus

corrected color substitution for disabled items and dividers

added support for unskinning when menus are hidden (Win 9x). This fixes what Windows reported as a resource leak with menu bars.

1.2 Bug Fixes + Features

updated to support CHookMgr

handles scrolling menus much better

disables the client area callback under 95/NT4 but still supports non-client drawing

adds an option to CSkinMenuMgr::Initialize() to disable menu skinning under XP

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

thanks alot!!!the menu have diffused me more than one year.i try many ways,such as owner draw,create owner window,but i still cann't owner draw the edit context menu,even i use api hook (TrackpopupMenuEx) to modify the menu into owner draw style(in this way error often occur after WM_EXITMENULOOP message).
after i see your method,i had to say:wow!how smart man!

and so many pepole ask the same queastion : not work under windows xp.
(i have see your ansower about it)why don't you edit your article to hilight this issue?

I don't know if anyone had the same problem, but my menus get rendered differently on different machines?? For example, I have a blue menu with white letters, and the guy next to me has no letters at all?? Then, on my PC at home, separator gets rendered the same as a normal item (on hover), but not on my PC at work?? Is this 'normal' or am I missing something??

---http://sprdsoft.cmar-net.org - We Sprd You Softly
Our site features contents and several images.
Better check it out before the site grows even dumber.

in the message of Tim Stubbs 27 Jun '03 "Modifying the highlighting on a menu item?", it seems to resolve the problem.
but I test Tim's code in my project, it no effect, i don't know why?
where can i get the update project with Tim's code to see the effect? Thank you.

Of course, Thanks Dan, he did the truly re-usefull code,that's great.
also thanks Tim,
Dan and Tim made my affliction disappeared.

but,...,the last questing that Tim metioned:
"The only problem i have now is that the text color when highlighted is white, not black (as it should be) which makes it a little hard to read."
Do you resolve it?
I try it for some time,but no effect.
Can you help me more? Thank you.

The fact is when you subclass the windows-class menu "#32768" you can't draw the common-dialog context menus with bitmaps because they are owner draw and the data that gives you the bitmaps and strings is in a private format.

If you try and get the strings or bitmap items using GetMenuItemInfo() after the WM_INITMENUPOPUP with the WH_CALLWNDPROCRET hook all you'll get is zeros...

I ran the program and compiled the source code and both of them failed on WinXP SP2.

The best way to skin menus is to create your own popup menu and toolbar menu. Then you can intercept the system menu and windowclasslong override the context menus in common controls.

But did you know, there's a problem when you use the cursor keys to navigate through the menu?
If you run the demo project, use the mouse to open the, for example, "File" menu and then hit the "Cursor Left" key several time, everything is OK. If you use the "Cursor Right" key, old menus weren't deleted.

If I try to integrate this class in my application, I get a assertion in file SkinMenuMgr.cpp, function CSkiMenuMgr::Unskin(HWND) in the line containing "m_mapMenus.RemoveKey(hWnd);" (caused by an invalid pElements-Pointer in DestructElements() in file AfxTempl.h).

Everything works perfectly while using the mouse. I don't know to get into it. I tried crtitical sections to ensure that menus will not be skinned while another is unskinned and many other things. Nothing was successful.

my menu also is something like yours but at the end i cannot get the hmenu from the window menu. your idea of attaching the hMenu to the window menu is very original and good, congratulation!

ok, here is a problem... cannot set the custom item height onto the system menu's item.
since the trick is subclass + hook... is there a way to change the system menu item height? well, since we are already subclassed the window menu, then why not going more than current code to support the custom height? fires the WM_MEASUREITEM before showing the menu window (most likely can be done after recieving the WM_SIZE) and then change the item height. then, emm... remapping the mouse cursor coordinate according to the original size to select the correct item... or returning the item id based on the new height.