This is menu's default look:
And this is the menu with the new look:
<!------------------------------- STEP 3 ---------------------------><!-- Add the article text. Please use simple formatting (
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 API's...
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 API's
Using the code
First - how to give your menus flat look - simply inherit from
Base class and that's it! I added two extra properties to FlatMenuForm:
- BorderColor - use it to change the border color around the menus.
- MenuStyle - it is enumaration which consists of two fields - Flat and Default. Use
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 worked at all, because I always got an exception "Out of Memory" ( it is because , as I traced why is that, a
win32 error is thrown when calling
). I tried that code with a
property but got the same exception. And then I started reading for subclassing a window and changing its default
. And then an idea arise - why not trying 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
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 secont parameter of that function is of great importance - it is the address of my
which will monitors for special messages ( by the way when you have to declare API in managed code and you have function pointer you use
). Here is the
delegate declaration :
delegate int HookProc(int code, IntPtr wparam, ref Win32.CWPSTRUCT cwp);
structure defines the message parameters passed to a
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
call in the form's constructor. And here is the implementation of the Hook procedure:
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
value which sets a new address for the window procedure ( (-4) stands for
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 seems 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 mi know!!!
Points of Interest
I have also added an implementation of owner-draw menus with flat look ant 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 !
- June 1st, 2003: First revision