How to Customize the System Menu of a Windows Form

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

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

Windows Forms - add a new option in the Menu

You need to override the ContextMenu property of the control you want to add the option to.

http://msdn.microsoft.com/en-us/library/aa984254(v=vs.71).aspx is the MSDN article that explains how you can do this. it's doable both in the design mode and the code-behind.

Adding an entry to the System Menu (Left-Upper-Corner Icon Menu) in WinForms?

This menu is added to the form when you set the FormBorderStyle to anything except 'None'. When the form border style is changed, a routine called AdjustSystemMenu is called. This routine uses a GetSystemMenu method to retrieve a SystemMenu? from somewhere. The 'somewhere' is the problem. There does not appear to be a SystemMenu object anywhere that can be accessed.

EDIT:
Just found this link, it looks like it might do what you want.

public partial class Form1 : Form
{
#region Win32 API Stuff

// Define the Win32 API methods we are going to use
[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);

/// Define our Constants we will use
public const Int32 WM_SYSCOMMAND = 0x112;
public const Int32 MF_SEPARATOR = 0x800;
public const Int32 MF_BYPOSITION = 0x400;
public const Int32 MF_STRING = 0x0;

#endregion

// The constants we'll use to identify our custom system menu items
public const Int32 _SettingsSysMenuID = 1000;
public const Int32 _AboutSysMenuID = 1001;

public Form1()
{
InitializeComponent();
}

private void Form1_Load(object sender, EventArgs e)
{
/// Get the Handle for the Forms System Menu
IntPtr systemMenuHandle = GetSystemMenu(this.Handle, false);

/// Create our new System Menu items just before the Close menu item
InsertMenu(systemMenuHandle, 5, MF_BYPOSITION | MF_SEPARATOR, 0, string.Empty); // <-- Add a menu seperator
InsertMenu(systemMenuHandle, 6, MF_BYPOSITION, _SettingsSysMenuID, "Settings...");
InsertMenu(systemMenuHandle, 7, MF_BYPOSITION, _AboutSysMenuID, "About...");
}

protected override void WndProc(ref Message m)
{
// Check if a System Command has been executed
if (m.Msg == WM_SYSCOMMAND)
{
// Execute the appropriate code for the System Menu item that was clicked
switch (m.WParam.ToInt32())
{
case _SettingsSysMenuID:
MessageBox.Show("\"Settings\" was clicked");
break;
case _AboutSysMenuID:
MessageBox.Show("\"About\" was clicked");
break;
}
}

base.WndProc(ref m);
}
}

Add System Default Context Menu to Borderless Windows Form

A border-less form doesn't have system menu by default. You should first enable system menu for the form by adding WS_SYSMENU style in CreateParams. Then you can send WM_POPUPSYSTEMMENU to the window in OnMouseDown.

C#

Set this.FormBorderStyle = Windows.Forms.FormBorderStyle.None; then:

private const int WS_SYSMENU = 0x80000;
private const int WS_MINIMIZEBOX = 0x20000;
private const int WS_MAXIMIZEBOX = 0x10000;
protected override CreateParams CreateParams
{
get
{
CreateParams p = base.CreateParams;
p.Style = WS_SYSMENU | WS_MINIMIZEBOX | WS_MAXIMIZEBOX;
return p;
}
}

[DllImport("user32.dll")]
private static extern IntPtr SendMessage(IntPtr hWnd, int msg,
IntPtr wParam, IntPtr lParam);
private const int WM_POPUPSYSTEMMENU = 0x313;
protected override void OnMouseDown(MouseEventArgs e)
{
base.OnMouseDown(e);
if (e.Button == System.Windows.Forms.MouseButtons.Right)
{
var p = MousePosition.X + (MousePosition.Y * 0x10000);
SendMessage(this.Handle, WM_POPUPSYSTEMMENU, (IntPtr)0, (IntPtr)p);
}
}

VB.NET

Set Me.FormBorderStyle = Windows.Forms.FormBorderStyle.None then:

Private Const WS_SYSMENU As Integer = &H80000
Private Const WS_MINIMIZEBOX As Integer = &H20000
Private Const WS_MAXIMIZEBOX As Integer = &H10000
Protected Overrides ReadOnly Property CreateParams As System.Windows.Forms.CreateParams
Get
Dim p = MyBase.CreateParams
p.Style = WS_SYSMENU + WS_MINIMIZEBOX + WS_MAXIMIZEBOX
Return p
End Get
End Property

<DllImport("user32.dll")>
Private Shared Function SendMessage(ByVal hWnd As IntPtr, ByVal msg As Integer, _
ByVal wParam As IntPtr, ByVal lParam As IntPtr) As IntPtr
End Function
Private Const WM_POPUPSYSTEMMENU As Integer = &H313
Protected Overrides Sub OnMouseDown(ByVal e As System.Windows.Forms.MouseEventArgs)
MyBase.OnMouseDown(e)
If e.Button = MouseButtons.Right Then
Dim p = MousePosition.X + (MousePosition.Y * &H10000)
SendMessage(Me.Handle, WM_POPUPSYSTEMMENU, 0, p)
End If
End Sub

Note

WM_POPUPSYSTEMMENU is undocumented but completely working. If you want to use a documented way you can get the system menu using GetSystemMenu and then show it using TrackPopupMenu and using a SendMessage execute returned command., you can declare:

Private Const TPM_LEFTBUTTON As Integer = &H0
Private Const TPM_RIGHTBUTTON As Integer = &H2
Private Const TPM_RETURNCMD As Integer = &H100
Private Const WM_SYSCOMMAND As Integer = &H112
<DllImport("user32.dll")> _
Private Shared Function GetSystemMenu(ByVal hWnd As IntPtr, _
ByVal bRevert As Boolean) As IntPtr
End Function
<DllImport("user32.dll")>
Private Shared Function TrackPopupMenu(ByVal hMenu As IntPtr, ByVal uFlags As Integer, _
ByVal x As Integer, ByVal y As Integer, ByVal nReserved As Integer, _
ByVal hWnd As IntPtr, ByVal prcRect As IntPtr) As Integer
End Function
<DllImport("user32.dll")>
Private Shared Function SendMessage(ByVal hWnd As IntPtr, ByVal msg As Integer, _
ByVal wParam As IntPtr, ByVal lParam As IntPtr) As IntPtr
End Function

And show the menu this way:

Dim menu = GetSystemMenu(Me.Handle, False)
Dim command = TrackPopupMenu(menu, TPM_RETURNCMD + TPM_LEFTBUTTON + TPM_RIGHTBUTTON, _
MousePosition.X, MousePosition.Y, IntPtr.Zero, _
Me.Handle, IntPtr.Zero)
If (command > 0) Then
SendMessage(Me.Handle, WM_SYSCOMMAND, command, IntPtr.Zero)
End If

Adding a custom context menu item to Windows Form title bar

You must override the WndProc method and intercept the id of your new menu.

Try this:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;

namespace WindowsFormsApplication11
{
public partial class Form1 : Form
{
public const Int32 WM_SYSCOMMAND = 0x112;
public const Int32 MF_BYPOSITION = 0x400;
public const Int32 MYMENU1 = 1000;
public const Int32 MUMENU2 = 1001;

[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);

public Form1()
{
InitializeComponent();
}

protected override void WndProc(ref Message msg)
{
if (msg.Msg == WM_SYSCOMMAND)
{
switch (msg.WParam.ToInt32())
{
case MYMENU1:
MessageBox.Show("Hi from My Menu 1¡¡¡¡");
return;
case MUMENU2:
MessageBox.Show("Hi from My Menu 2¡¡¡¡");
return;
default:
break;
}
}
base.WndProc(ref msg);
}

private void Form1_Load(object sender, EventArgs e)
{
IntPtr MenuHandle = GetSystemMenu(this.Handle, false);
InsertMenu(MenuHandle, 5, MF_BYPOSITION, MYMENU1, "My Menu 1");
InsertMenu(MenuHandle, 6, MF_BYPOSITION, MUMENU2, "My Menu 2");
}
}
}

Custom Windows Form menu in vb.net

You have a few options, Firstly you can extend the ClientArea into the NonClientArea
and you would end up with something like: http://www.codeproject.com/Articles/44235/Painting-Vista-s-Aero-NonClientArea-in-VB-NET

After doing some digging i found a .net wrapper for the windows 7 ribbon control, I haven't tried using it yet but you might be able to make it work:
http://windowsribbon.codeplex.com/

Alternatively you can create it all from scratch, setting the FormBorderStyle to None and handling the moving/re-sizing etc by your own code.
As well as painting the Ribbon-like controls yourself.

The latter is a lot more work but is the most customizable, I hope this is helpful and gets you on the right track!

Launch window's System Menu on custom window

The menu that you want to show is system ContextMenu. To work with that you need to import some user32 functions as shown in the code below. I have launched the system menu on button click. You can launch it on any action, right mouse click etc

GetSystemMenu gets the system menu and TrackPopupMenuEx is used to display it. PostMessage is the send system command on menuitem click.

public partial class Window3 : Window
{

private const int WM_SYSCOMMAND = 0x112;
uint TPM_LEFTALIGN = 0x0000;
uint TPM_RETURNCMD = 0x0100;
const UInt32 MF_ENABLED = 0x00000000;
const UInt32 MF_GRAYED = 0x00000001;
internal const UInt32 SC_MAXIMIZE = 0xF030;
internal const UInt32 SC_RESTORE = 0xF120;

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

[DllImport("user32.dll")]
static extern int TrackPopupMenuEx(IntPtr hmenu, uint fuFlags,
int x, int y, IntPtr hwnd, IntPtr lptpm);

[DllImport("user32.dll")]
public static extern IntPtr PostMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);

[DllImport("user32.dll")]
static extern bool EnableMenuItem(IntPtr hMenu, uint uIDEnableItem,
uint uEnable);

private void Button_Click(object sender, RoutedEventArgs e)
{
WindowInteropHelper helper = new WindowInteropHelper(this);
IntPtr callingWindow = helper.Handle;
IntPtr wMenu = GetSystemMenu(callingWindow, false);
// Display the menu
if (this.WindowState == System.Windows.WindowState.Maximized)
{
EnableMenuItem(wMenu, SC_MAXIMIZE, MF_GRAYED);
}
else
{
EnableMenuItem(wMenu, SC_MAXIMIZE, MF_ENABLED);
}

int command = TrackPopupMenuEx(wMenu, TPM_LEFTALIGN | TPM_RETURNCMD, 100, 100, callingWindow, IntPtr.Zero);
if (command == 0)
return;

PostMessage(callingWindow, WM_SYSCOMMAND, new IntPtr(command), IntPtr.Zero);
}

Standard Windows menu bars in Windows Forms

Go to your Toolbox, right click anywhere inside and select "Choose Items".

When the dialog loads and appears, scroll down til you see MainMenu. Add that to the toolbox, and you've got yourself a native menu bar!



Related Topics



Leave a reply



Submit