¿Cómo puedo personalizar el menú del sistema de Windows Form?

Deseo agregar el antiguo elemento de menú Acerca de a mi aplicación. Quiero agregarlo al ‘menú del sistema’ de la aplicación (el que aparece cuando hacemos clic en el ícono de la aplicación en la esquina superior izquierda). Entonces, ¿cómo puedo hacerlo en .NET?

Windows hace que sea bastante fácil acceder a una copia del menú del sistema del formulario para fines de personalización con la función GetSystemMenu . La parte difícil es que usted está solo para realizar las modificaciones apropiadas en el menú que devuelve, usando funciones como AppendMenu , InsertMenu y DeleteMenu tal como lo haría si estuviese progtwigndo directamente en contra de la API de Win32.

Sin embargo, si todo lo que quiere hacer es agregar un elemento de menú simple, realmente no es tan difícil. Por ejemplo, solo necesitaría usar la función AppendMenu porque todo lo que quiere hacer es agregar un elemento o dos al final del menú. Hacer algo más avanzado (como insertar un elemento en el medio del menú, mostrar un bitmap en el elemento del menú, mostrar los elementos del menú marcados, configurar un elemento de menú predeterminado, etc.) requiere un poco más de trabajo. Pero una vez que sabes cómo se hace, puedes volverte loco. La documentación sobre las funciones relacionadas con el menú lo dice todo.

Aquí está el código completo para un formulario que agrega una línea separadora y un elemento “Acerca de” al final de su menú del sistema (también llamado menú de la ventana):

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

Y así es como se ve el producto terminado:

Formulario con menú de sistema personalizado

He llevado la solución de Cody Grey un paso más allá e hice una clase reutilizable. Es parte de la herramienta de envío del registro de la aplicación que debe ocultar su información acerca del menú del sistema.

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

Se puede usar fácilmente así:

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

El valor agregado es bastante pequeño para la cantidad de pinvoke que necesitará. Pero es posible. Use GetSystemMenu () para recuperar el identificador del menú del sistema. Luego InsertMenuItem para agregar una entrada. Tienes que hacer esto en una anulación de OnHandleCreated () para que vuelvas a crear el menú cuando se recrea la ventana.

Sustituya WndProc () para reconocer el mensaje WM_SYSCOMMAND que se genera cuando el usuario hace clic en él. Visite pinvoke.net para obtener las declaraciones pinvoke que necesitará.

Sé que esta respuesta es antigua, pero realmente me gustó la respuesta de LonelyPixel. Sin embargo, necesitaba algo de trabajo para funcionar correctamente con WPF. Debajo hay una versión de WPF que escribí, así que no tienes que :).

 ///  /// 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 

Versión VB.NET de la respuesta aceptada:

 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