我如何自定义Windows窗体的系统菜单?

我想添加旧的关于菜单项到我的应用程序。 我想将它添加到应用程序的“系统菜单”(当我们点击左上angular的应用程序图标时popup的那个)。 那么,我怎样才能在.NET中做到这一点?

Windows使用GetSystemMenu函数可以很容易地获得表单系统菜单副本的一个副本,以进行自定义。 最难的部分就是你自己去使用AppendMenuInsertMenuDeleteMenu等函数对返回的菜单进行适当的修改,就像直接针对Win32 API编程一样。

但是,如果你想要做的只是添加一个简单的菜单项,这并不是那么困难。 例如,您只需要使用AppendMenu函数,因为您只需在菜单的末尾添加一个或两个项目即可。 做更高级的事情(比如在菜单中间插入一个项目,在菜单项上显示一个位图,显示菜单项被选中,设置一个默认的菜单项,等等)需要更多的工作。 但一旦你知道如何完成,你可以疯狂。 菜单相关function的文档告诉所有人。

下面是一个表格的完整代码,它在系统菜单(也称为窗口菜单)的底部添加分隔线和“关于”项目:

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

以下是成品的外观:

与自定义系统菜单的形式

增加的价值是相当小的,以满足您的需求。 但是这是可能的。 使用GetSystemMenu()来检索系统菜单句柄。 然后InsertMenuItem添加一个条目。 你必须在OnHandleCreated()的重写中做到这一点,所以你重新创build窗口时重新创build菜单。

重写WndProc()以识别用户单击时生成的WM_SYSCOMMAND消息。 请访问pinvoke.net,了解您需要的pinvoke声明。

我已经把科迪·格雷的解决scheme更进了一步,并且把可重用的课程从中解放出来。 这是我的应用程序日志提交工具的一部分,应该隐藏其在系统菜单中的关于信息。

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

它可以很容易地使用这样的:

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

我知道这个答案是旧的,但我真的很喜欢LonelyPixel的答案。 但是,它需要一些工作才能正确使用WPF。 下面是我写的WPF版本,所以你不必:)。

 /// <summary> /// Extends the system menu of a window with additional commands. /// Adapted from: /// https://github.com/dg9ngf/FieldLog/blob/master/LogSubmit/Unclassified/UI/SystemMenu.cs /// </summary> 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<Action> actions = new List<Action>(); private List<CommandInfo> pendingCommands; #endregion Private data #region Constructors /// <summary> /// Initialises a new instance of the <see cref="SystemMenu"/> class for the specified /// <see cref="Form"/>. /// </summary> /// <param name="window">The window for which the system menu is expanded.</param> public SystemMenuExtension(Window window) { this.window = window; if(this.window.IsLoaded) { WindowLoaded(null, null); } else { this.window.Loaded += WindowLoaded; } } #endregion Constructors #region Public methods /// <summary> /// Adds a command to the system menu. /// </summary> /// <param name="text">The displayed command text.</param> /// <param name="action">The action that is executed when the user clicks on the command.</param> /// <param name="separatorBeforeCommand">Indicates whether a separator is inserted before the command.</param> 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<CommandInfo>(); } 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 

VB.NET版本接受的答案:

 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 <DllImport("user32.dll", CharSet := CharSet.Auto, SetLastError := True)> _ Private Shared Function GetSystemMenu(hWnd As IntPtr, bRevert As Boolean) As IntPtr End Function <DllImport("user32.dll", CharSet := CharSet.Auto, SetLastError := True)> _ Private Shared Function AppendMenu(hMenu As IntPtr, uFlags As Integer, uIDNewItem As Integer, lpNewItem As String) As Boolean End Function <DllImport("user32.dll", CharSet := CharSet.Auto, SetLastError := True)> _ 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