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

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何自定义Windows窗体的系统菜单?相关的知识,希望对你有一定的参考价值。

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

答案

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

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

以下是表单的完整代码,该表单在其系统菜单(也称为窗口菜单)的底部添加了分隔线和“关于”项:

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

    }
}

这就是成品的样子:

另一答案

我已经将Cody Gray的解决方案更进一步,并从中获得了可重复使用的课程。它是我的应用程序日志提交工具的一部分,应该在系统菜单中隐藏其关于信息。

https://github.com/ygoe/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");
    }
}
另一答案

对于你需要的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)
                {
                    Appe

以上是关于如何自定义Windows窗体的系统菜单?的主要内容,如果未能解决你的问题,请参考以下文章

(VB6)窗体菜单项怎么自定义背景色和字体颜色(含子菜单)

如何在windows窗体里面添加窗口

如何使用windows优化大师进行右键新建word

如何在Pycharm中运用PyQT5创建Windows窗体

自定义标题栏右键菜单

使用 Windows 窗体应用程序时如何将鼠标光标更改为自定义光标?