This is menu's default look:
And this is the menu with the new look:
I wanted to learn how to change the default 3D look of the menus in Windows XP classic appearance, because when implementing owner-draw menus, windows gives you only the client area of the menu window for drawing. And when I have Windows XP appearance, it is just fine with its flat menus, but when switching to classic - it is awful, the 3D border isn't fitting the menu items at all. Then, I started looping through the .NET Framework SDK searching solution of my problem and finally I gave up - there was not such a class or
enum or whatever... Then (as I am a little bit stubborn and wanted to learn the know-how), I went deeper in the Platform SDK and Win32 APIs...
In a few words, the idea is subclassing the default window class Windows provides for its menus. This subclassing is made by using P/Invokes and calling native APIs.
Using the Code
First, how to give your menus a flat look - simply inherit from
Base class and that's it! I added two extra properties to
BorderColor - use it to change the border color around the menus
MenuStyle - it is enumeration which consists of two fields -
MenuStyle.Default to use the default menu look and the other for the flat look.
Now let's start from the very beginning - my first efforts in doing custom painting on the menu window. At first, I tried something like this:
IntPtr hdc = GetWindowDC(mainMenu1.Handle);
Graphics g = Graphics.FromHdc(hdc);
Rectangle r = new Rectangle(0,0,(int)g.VisibleClipBounds.Width-1,
Well, it did not work at all, because I always got an exception "
Out of Memory" (it is because, as I traced why that is so, an
INVALID_WINDOW_HANDLE win32 error is thrown when calling
GetWindowDC ). I tried that code with a
MenuItem.Handle property but got the same exception. And then I started reading for subclassing a window and changing its default
WndProc. And then an idea arose - why not try to subclass the menu window first... Everything seems OK till now, but how to subclass a window when I don't have a valid window handle? Now in help comes the
SetWindowsHookEx API with
WH_CALLWNDPROC hook type specified - it installs a hook procedure that monitors messages before the system sends them to the destination window procedure.
hookHandle = SetWindowsHookEx(4,hookProc,IntPtr.Zero,Win32.GetWindowThreadProcessId(Handle,0));
The second parameter of that function is of great importance - it is the address of my
HookProc which will monitor for special messages (by the way, when you have to declare API in managed code and you have a function pointer, you use
delegate ). Here is the
HookProc delegate declaration:
delegate int HookProc(int code, IntPtr wparam, ref Win32.CWPSTRUCT cwp);
CWPSTRUCT structure defines the message parameters passed to a
WH_CALLWNDPROC hook procedure:
public struct CWPSTRUCT
public IntPtr lparam;
public IntPtr wparam;
public int message;
public IntPtr hwnd;
As I needed the window (I mean the main form window) to be hooked when constructed, I put this
SetWindowsHookEx call in the form's constructor. And here is the implementation of the
int Hooked(int code, IntPtr wparam, ref Win32.CWPSTRUCT cwp)
string s = string.Empty;
char className = new char;
int length = Win32.GetClassName(cwp.hwnd,className,9);
s += className[i];
if(s == "#32768")
defaultWndProc = SetWindowLong(cwp.hwnd, (-4), subWndProc);
return Win32.CallNextHookEx(hookHandle,code,wparam, ref cwp);
Another great difficulty was to get the appropriate window class. This is system defined class for use only by the system but its name is given in the Platform SDK documentation - it is "
#32768". And when get the right class - subclass it using
SetWindowLong API with
GWL_WNDPROC value which sets a new address for the window procedure ((-4) stands for
subWndProc parameter is delegate of type
delegate int MyWndProc(IntPtr hwnd,int msg,IntPtr wparam,IntPtr lparam);
The implementation of the
int SubclassWndProc(IntPtr hwnd, int msg, IntPtr wparam, IntPtr lparam)
IntPtr menuDC = Win32.GetWindowDC(hwnd);
Graphics g = Graphics.FromHdc(menuDC);
int result = Win32.CallWindowProc(defaultWndProc,hwnd,msg,wparam,lparam);
menuDC = wparam;
g = Graphics.FromHdc(menuDC);
It may seem strange, but Windows sends a
WM_PRINT message AFTER
WM_NCPAINT. I spent hours and hours trying to understand what is wrong with my code and why it is not working until I put a simple tracer to the
SubclassWndProc and found out what messages are sent to the menu window. And when processing the
WM_PRINT message, it all worked fine - BINGO! Finally, I changed the default appearance of the menu window!
I wanted also to process the
WM_NCCALCSIZE message in order to reduce the non-client area of the menu but failed... Any suggestion on how this might be done in managed code (I achieved it in MFC) would be very much appreciated! Also, I couldn't override the default implementation of
WM_WINDOWPOSCHANGED - I lost the default system animation...
And yet another thing - I haven't tested this code on other platforms (mine is Windows XP) so if you find some bugs in it please, let me know!!!
Points of Interest
I have also added an implementation of owner-draw menus with flat look and to some extent, they now really look like the Visual Studio .NET ones! I haven't implemented the shadow on the right side of the top menu items. May be it might be achieved by getting the desktop window DC, draw on it and then invalidate that rectangle - if I have enough time, I will try it.
Here is what I finally got:
Well, that's it!
Once again - any comments or suggestions or even criticism are welcome!
- 1st June, 2003: First revision
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.
A list of licenses authors might use can be found here.