Como posso personalizar o menu do sistema de um formulário do Windows?

Eu quero adicionar o item de menu antigo sobre o meu aplicativo. Eu quero adicioná-lo ao ‘menu do sistema’ do aplicativo (aquele que aparece quando clicamos no ícone do aplicativo no canto superior esquerdo). Então, como posso fazer isso no .NET?

O Windows torna bastante fácil obter um identificador para uma cópia do menu do sistema do formulário para fins de personalização com a function GetSystemMenu . A parte difícil é que você está sozinho para executar as modificações apropriadas no menu que ele retorna, usando funções como AppendMenu , InsertMenu e DeleteMenu da mesma forma que faria se estivesse programando diretamente contra a API do Win32.

No entanto, se tudo o que você quer fazer é adicionar um item de menu simples, não é tão difícil assim. Por exemplo, você só precisaria usar a function AppendMenu porque tudo o que você quer fazer é adicionar um item ou dois ao final do menu. Fazer algo mais avançado (como inserir um item no meio do menu, exibir um bitmap no item de menu, mostrar itens de menu marcados, definir um item de menu padrão, etc.) requer um pouco mais de trabalho. Mas uma vez que você saiba como é feito, você pode ficar louco. A documentação das funções relacionadas ao menu informa tudo.

Aqui está o código completo para um formulário que adiciona uma linha separadora e um item “Sobre” à parte inferior do menu do sistema (também chamado de menu de janela):

 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"); } } } 

E aqui está o que o produto acabado parece:

Formulário com menu personalizado do sistema

Eu tomei a solução de Cody Gray um passo além e fiz dela uma class reutilizável. Faz parte da minha ferramenta de envio de log do aplicativo que deve ocultar sua informação Sobre no menu do sistema.

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

Pode ser facilmente usado assim:

 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"); } } 

O valor agregado é pequeno para a quantidade de pinvoke que você precisa. Mas é possível. Use GetSystemMenu () para recuperar o identificador de menu do sistema. Em seguida, InsertMenuItem para adicionar uma input. Você tem que fazer isso em uma substituição de OnHandleCreated () para recriar o menu quando a janela é recriada.

Substituir WndProc () para reconhecer a mensagem WM_SYSCOMMAND que é gerada quando o usuário clica nele. Visite pinvoke.net para as declarações do pinvoke que você precisará.

Eu sei que esta resposta é antiga, mas eu realmente gostei da resposta da LonelyPixel. No entanto, precisava de algum trabalho para funcionar corretamente com o WPF. Abaixo está uma versão do WPF que eu escrevi, então você não precisa :).

 ///  /// Extends the system menu of a window with additional commands. /// Adapted from: /// https://github.com/dg9ngf/FieldLog/blob/master/LogSubmit/Unclassified/UI/SystemMenu.cs ///  public class SystemMenuExtension { #region Native methods private const int WM_SYSCOMMAND = 0x112; private const int MF_STRING = 0x0; private const int MF_SEPARATOR = 0x800; [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); #endregion Native methods #region Private data private Window window; private IntPtr hSysMenu; private int lastId = 0; private List actions = new List(); private List pendingCommands; #endregion Private data #region Constructors ///  /// Initialises a new instance of the  class for the specified /// . ///  /// The window for which the system menu is expanded. public SystemMenuExtension(Window window) { this.window = window; if(this.window.IsLoaded) { WindowLoaded(null, null); } else { this.window.Loaded += WindowLoaded; } } #endregion Constructors #region Public methods ///  /// Adds a command to the system menu. ///  /// The displayed command text. /// The action that is executed when the user clicks on the command. /// Indicates whether a separator is inserted before the command. public void AddCommand(string text, Action action, bool separatorBeforeCommand) { int id = ++this.lastId; if (!this.window.IsLoaded) { // The window is not yet created, queue the command for later addition if (this.pendingCommands == null) { this.pendingCommands = new List(); } this.pendingCommands.Add(new CommandInfo { Id = id, Text = text, Action = action, Separator = separatorBeforeCommand }); } else { // The form is created, add the command now if (separatorBeforeCommand) { AppendMenu(this.hSysMenu, MF_SEPARATOR, 0, ""); } AppendMenu(this.hSysMenu, MF_STRING, id, text); } this.actions.Add(action); } #endregion Public methods #region Private methods private void WindowLoaded(object sender, RoutedEventArgs e) { var interop = new WindowInteropHelper(this.window); HwndSource source = PresentationSource.FromVisual(this.window) as HwndSource; source.AddHook(WndProc); this.hSysMenu = GetSystemMenu(interop.EnsureHandle(), false); // Add all queued commands now if (this.pendingCommands != null) { foreach (CommandInfo command in this.pendingCommands) { if (command.Separator) { AppendMenu(this.hSysMenu, MF_SEPARATOR, 0, ""); } AppendMenu(this.hSysMenu, MF_STRING, command.Id, command.Text); } this.pendingCommands = null; } } private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) { if (msg == WM_SYSCOMMAND) { if ((long)wParam > 0 && (long)wParam <= lastId) { this.actions[(int)wParam - 1](); } } return IntPtr.Zero; } #endregion Private methods #region Classes private class CommandInfo { public int Id { get; set; } public string Text { get; set; } public Action Action { get; set; } public bool Separator { get; set; } } #endregion Classes 

Versão VB.NET da resposta aceita:

 Imports System.Windows.Forms Imports System.Runtime.InteropServices Public Class CustomForm Inherits Form ' P/Invoke constants Private Const WM_SYSCOMMAND As Integer = &H112 Private Const MF_STRING As Integer = &H0 Private Const MF_SEPARATOR As Integer = &H800 ' P/Invoke declarations  _ Private Shared Function GetSystemMenu(hWnd As IntPtr, bRevert As Boolean) As IntPtr End Function  _ Private Shared Function AppendMenu(hMenu As IntPtr, uFlags As Integer, uIDNewItem As Integer, lpNewItem As String) As Boolean End Function  _ Private Shared Function InsertMenu(hMenu As IntPtr, uPosition As Integer, uFlags As Integer, uIDNewItem As Integer, lpNewItem As String) As Boolean End Function ' ID for the About item on the system menu Private SYSMENU_ABOUT_ID As Integer = &H1 Public Sub New() End Sub Protected Overrides Sub OnHandleCreated(e As EventArgs) MyBase.OnHandleCreated(e) ' Get a handle to a copy of this form's system (window) menu Dim hSysMenu As IntPtr = GetSystemMenu(Me.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…") End Sub Protected Overrides Sub WndProc(ByRef m As Message) MyBase.WndProc(m) ' Test if the About item was selected from the system menu If (m.Msg = WM_SYSCOMMAND) AndAlso (CInt(m.WParam) = SYSMENU_ABOUT_ID) Then MessageBox.Show("Custom About Dialog") End If End Sub End Class