How can I customize the system menu of a Windows Form?

I want to add the age old About menu item to my application. I want to add it to the 'system menu' of the application (the one which pops up when we click the application icon in the top-left corner). So, how can I do it in .NET?


Windows makes it fairly easy to get a handle to a copy of the form's system menu for customization purposes with the GetSystemMenu function. The hard part is that you're on your own to perform the appropriate modifications to the menu it returns, using functions such as AppendMenu, InsertMenu, and DeleteMenu just as you would if you were programming directly against the Win32 API.

However, if all you want to do is add a simple menu item, it's really not all that difficult. For example, you would only need to use the AppendMenu function because all you want to do is add an item or two to the end of the menu. Doing anything more advanced (like inserting an item in the middle of the menu, displaying a bitmap on the menu item, showing menu items checked, setting a default menu item, etc.) requires a bit more work. But once you know how it's done, you can go wild. The documentation on menu-related functions tells all.

Here's the complete code for a form that adds a separator line and an "About" item to the bottom of its system menu (also called a window menu):

using System;
using System.Windows.Forms;
using System.Runtime.InteropServices;

public class CustomForm : Form
{
    // P/Invoke constants
    private const int WM_SYSCOMMAND = 0x112;
    private const int MF_STRING = 0x0;
    private const int MF_SEPARATOR = 0x800;

    // P/Invoke declarations
    [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    private static extern IntPtr GetSystemMenu(IntPtr hWnd, bool bRevert);

    [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    private static extern bool AppendMenu(IntPtr hMenu, int uFlags, int uIDNewItem, string lpNewItem);

    [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    private static extern bool InsertMenu(IntPtr hMenu, int uPosition, int uFlags, int uIDNewItem, string lpNewItem);


    // ID for the About item on the system menu
    private int SYSMENU_ABOUT_ID = 0x1;

    public CustomForm()
    {
    }

    protected override void OnHandleCreated(EventArgs e)
    {
        base.OnHandleCreated(e);

        // Get a handle to a copy of this form's system (window) menu
        IntPtr hSysMenu = GetSystemMenu(this.Handle, false);

        // Add a separator
        AppendMenu(hSysMenu, MF_SEPARATOR, 0, string.Empty);

        // Add the About menu item
        AppendMenu(hSysMenu, MF_STRING, SYSMENU_ABOUT_ID, "&About…");
    }

    protected override void WndProc(ref Message m)
    {
        base.WndProc(ref m);

        // Test if the About item was selected from the system menu
        if ((m.Msg == WM_SYSCOMMAND) && ((int)m.WParam == SYSMENU_ABOUT_ID))
        {
            MessageBox.Show("Custom About Dialog");
        }

    }
}

And here's what the finished product looks like:

  Form with custom system menu


I've taken Cody Gray's solution one step further and made a reusable class out of it. It's part of my application log submit tool that should hide its About info in the system menu.

https://github.com/ygoe/FieldLog/blob/master/LogSubmit/Unclassified/UI/SystemMenu.cs

It can easily be used like this:

class MainForm : Form
{
    private SystemMenu systemMenu;

    public MainForm()
    {
        InitializeComponent();

        // Create instance and connect it with the Form
        systemMenu = new SystemMenu(this);

        // Define commands and handler methods
        // (Deferred until HandleCreated if it's too early)
        // IDs are counted internally, separator is optional
        systemMenu.AddCommand("&About…", OnSysMenuAbout, true);
    }

    protected override void WndProc(ref Message msg)
    {
        base.WndProc(ref msg);

        // Let it know all messages so it can handle WM_SYSCOMMAND
        // (This method is inlined)
        systemMenu.HandleMessage(ref msg);
    }

    // Handle menu command click
    private void OnSysMenuAbout()
    {
        MessageBox.Show("My about message");
    }
}