Как я могу настроить системное меню формы Windows?
Я хочу добавить старый пункт меню About в свое приложение. Я хочу добавить его в "системное меню" приложения (которое появляется, когда мы щелкаем значок приложения в верхнем левом углу). Итак, как я могу сделать это в.NET?
5 ответов
Windows позволяет довольно легко получить ручку для копии системного меню формы в целях настройки с помощью 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/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
Добавленная стоимость довольно мала для того количества пинвока, которое вам нужно. Но это возможно. Используйте GetSystemMenu(), чтобы получить дескриптор системного меню. Затем InsertMenuItem, чтобы добавить запись. Вы должны сделать это в переопределении OnHandleCreated(), чтобы заново создать меню, когда окно будет воссоздано.
Переопределите WndProc(), чтобы распознать сообщение WM_SYSCOMMAND, которое генерируется, когда пользователь щелкает его. Посетите pinvoke.net для объявлений pinvoke, которые вам понадобятся.
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