Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Customizing WinForm's System Menu

0.00/5 (No votes)
28 Jul 2004 2  
How to customize the WinForm system menu.

Why? Why Not?

Why would you want to do it? Why not, if I could do it before, why not in .NET? The better answer is that sometimes you just need a quick way to get some options hooked in without having to create a full menu and/or toolbar. Or maybe you don�t want to use up the space required by a menu/toolbar. Or maybe you want to provide some special functionality that just doesn�t belong to the application�s menu. For whatever reason, why not?

Anyway, getting to it was not that obvious to me. Try it. A system menu is automatically created for you by the Wizard when you specify a �Windows Application� as the project type. See if you can figure out how to get access to it. If you look through the docs, you�ll see that you can specify some of the items on the menu indirectly through properties of the Form. For example, if you set the MaximizeBox property to false, then the corresponding item on the system menu will be disabled. You can also completely remove the system menu by setting the ControlBox property to false. Of course, this has an additional side effect of changing some of the Window Style properties.

The Form does has a Menu property but it�s for the application menu that you specify. And what�s displayed when you click on the application icon on the Title Bar is definitely a menu. So, where is it and how can I get access to it? It�s my application! Some people may want to argue that it�s the �System Menu� and you shouldn�t be putting items there in the first place. Well, it�s just that I used to be able to do it!

Let's Do It

OK, enough foreplay. Let�s get down to it. Create new project and select �Windows Project� on the �New Project� dialog. Go ahead and create an event handler for the Form_Load since we�ll be using it in a little bit. So, it turns out that the code controlling the �System Menu� is actually in the �User32� DLL! That means that in order to get access to the �System Menu�, we�ve got to use the old Win32 API. Holy molony! I just want to add an item or two to the menu. Maybe this wasn�t such a good idea. I mean, it�s real easy to add a menu to the application, just some drag and drop and we�re done. Nope, not gonna listen. It�s got to be the hard way.

So, here�s what I�m guessing, the code that I write is managed code which means it runs in a nice little protected environment called the .NET runtime. Which is kind of like a really big application that thinks it�s an operating system. I mean the code you write acts, behaves, and has access to the system services (with some restrictions of course) just like a Win32 app. Code that runs outside of the .NET runtime is considered unmanaged code and of course is anything that is executed by Windows, a COM server, an executable, a DLL. So to solve our little problem, we have to figure out how to make a call from our managed code to unmanaged code.

As usual, it�s not so bad once you know how to do it. One of the namespaces provided under the .NET umbrella is the System.Runtime.InteropServices. The name kinda gives it away. It�s a collection of types used to �interoperate� with the outside world. The one we need is the �Platform Invocation Services� (isn�t that refreshing having a name that actually describes what it does?). PInvoke, for short, provides the facilities for managed code to make calls to unmanaged code. It handles all the marshaling, loading, and locating the function of interest. To get this magic to happen, all we have to do is specify the function that we want to call (in the unmanaged piece of code) and then decorate the declaration with the DllImport attribute which is part of System.Runtime.InteropServices.

Add the following at the beginning of the Form definition.

[DllImport("user32.dll")]
private static extern IntPtr GetSystemMenu(IntPtr hWnd, bool bRevert);
[DllImport("user32.dll")]
private static extern bool InsertMenu (IntPtr hMenu, 
    Int32 wPosition, Int32 wFlags, Int32 wIDNewItem, 
    string lpNewItem);

Those are the two functions we�ll need to call, and the DllImport attribute tells the system where they can be found. From this, it�s a small stretch to see how we can use this mechanism to access any other legacy code we may have. By the way, there are other fields that can specify additional options for the DllImport attribute.

In the Form_Load event handler, add the following:

IntPtr sysMenuHandle = GetSystemMenu(this.Handle, false);
//It would be better to find the position at run time of the 'Close' item, but...

InsertMenu(sysMenuHandle, 5, MF_BYPOSITION |MF_SEPARATOR, 0, string.Empty);
InsertMenu(sysMenuHandle, 6, MF_BYPOSITION , IDM_EDITFUNDS, "Edit Funds");
InsertMenu(sysMenuHandle, 7, MF_BYPOSITION , IDM_ANALYZE, "Analyze");

The first thing we do is get a handle to the system menu. Then we just add the items we want. To make things compile, add the following after the DllImport attributes added above:

public const Int32 WM_SYSCOMMAND = 0x112;
public const Int32 MF_SEPARATOR = 0x800;
public const Int32 MF_BYPOSITION = 0x400;
public const Int32 MF_STRING = 0x0;
public const Int32 IDM_EDITFUNDS  = 1000;
public const Int32 IDM_ANALYZE = 1001;

Oh, and make sure you bring in the System.Runtime.InteropServices namespace. Compile the application and check out the two additional items in the system menu. That�s good, we got what we wanted. But having those items without doing anything doesn�t help much. So now, we need to provide a way to capture the selection of those items so we can do something. Again, that�s not too bad. All that�s needed is to override the Form�s WndProc method as shown below:

protected override void WndProc(ref Message m)
{
    if(m.Msg == WM_SYSCOMMAND)
    {
        switch(m.WParam.ToInt32())
        {
            case IDM_EDITFUNDS : 
                MessageBox.Show("Clicked 'EditFunds'");
                return;
            case IDM_ANALYZE :
                MessageBox.Show("Clicked 'Analyze'");
                return;
            default:
                break;
        } 
    }
    base.WndProc(ref m);
}

There you have it. A simple little question like, �Hey, there�s already a menu there, why don�t we use that?�, turns into a little adventure involving the System Interop Services. Well, it's on to the next adventure Keemo Sabee.

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.

A list of licenses authors might use can be found here