如何自定义Windows窗体的系统菜单?
-
26-09-2019 - |
题
我要古老的关于菜单项添加到我的应用程序。我想将它添加到应用程序(即弹出,当我们点击左上角的应用程序图标的那个)的“系统菜单”。所以,我怎么能做到这一点的.NET?
解决方案
视窗使得它很容易得到一个处理定制的目的窗体的系统菜单中的复制与的 GetSystemMenu
功能。难的是,你对你自己进行适当的修改,以使其返回,使用功能,如的 AppendMenu
, InsertMenu
,和 DeleteMenu
就像你如果你是直接针对Win32 API的编程
然而,如果你想要做的就是添加一个简单的菜单项,这真的不是所有的困难。例如,你只需要使用AppendMenu
功能,因为所有你想要做的就是添加一个或两个项目到菜单的末尾。做任何事情更先进的(如在菜单的中间插入一个项目,该菜单项显示位图,显示的菜单项检查,设置一个默认的菜单项等)需要更多的工作。但是,一旦你知道它是如何做的,你可以去野外。上的菜单相关的功能告诉所有
下面是一种形式的完整代码,增加了一个分离器线和“关于”项目到其系统菜单的底部(也称为窗口菜单):
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");
}
}
}
这就是成品的样子:
其他提示
我已经进一步采取科迪格雷的溶液一个步骤和由可重复使用的类出来。这是我的应用程序日志的一部分提交工具,它应该隐藏在系统菜单中的关于信息。
https://github.com/ygoe/ FieldLog /斑点/主/ LogSubmit /未归类/ 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");
}
}
在增值服务是相当小的的PInvoke的,你需要的金额。但它是可能的。使用GetSystemMenu()来检索系统菜单句柄。然后InsertMenuItem添加一个条目。你必须为此在OnHandleCreated()的重写,所以你重新创建菜单时,窗口被重新创建。
覆盖的WndProc()以认识到,当用户点击它产生的WM_SYSCOMMAND消息。访问pinvoke.net为你需要的PInvoke的声明。
我知道这个答案是旧的,但我真的很喜欢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