48

古いバージョンの About メニュー項目をアプリケーションに追加したいと考えています。アプリケーションの「システムメニュー」(左上隅にあるアプリケーションアイコンをクリックするとポップアップするもの)に追加したいと思います。では、どうすれば.NETでそれを行うことができますか?

4

5 に答える 5

99

GetSystemMenuWindowsでは、関数を使用してカスタマイズするために、フォームのシステムメニューのコピーを簡単に取得できます。難しいのは、Win32 APIに対して直接プログラミングする場合と同じようAppendMenuに、、などの関数を使用して、返されるメニューに適切な変更を自分で実行することです。InsertMenuDeleteMenu

ただし、単純なメニュー項目を追加するだけであれば、それほど難しいことではありません。たとえばAppendMenu、メニューの最後に1つまたは2つの項目を追加するだけなので、この関数を使用するだけで済みます。より高度な操作(メニューの中央に項目を挿入する、メニュー項目にビットマップを表示する、チェックされているメニュー項目を表示する、デフォルトのメニュー項目を設定するなど)を行うには、もう少し作業が必要です。しかし、それがどのように行われるかを知ったら、あなたはワイルドになることができます。メニュー関連の機能に関するドキュメントがすべてを物語っています。

システムメニュー(ウィンドウメニューとも呼ばれます)の下部に区切り線と「バージョン情報」項目を追加するフォームの完全なコードは次のとおりです。

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

    }
}

そして、完成品は次のようになります。

  カスタムシステムメニュー付きフォーム

于 2011-01-06T15:37:35.430 に答える
8

この回答が古いことは知っていますが、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
于 2015-07-03T12:08:05.027 に答える
6

必要なピンボークの量に対して、付加価値はかなり小さいです。しかし、それは可能です。GetSystemMenu()を使用して、システムメニューハンドルを取得します。次に、InsertMenuItemを使用してエントリを追加します。これは、OnHandleCreated()のオーバーライドで行う必要があるため、ウィンドウが再作成されたときにメニューを再作成します。

WndProc()をオーバーライドして、ユーザーがクリックしたときに生成されるWM_SYSCOMMANDメッセージを認識します。必要なpinvoke宣言については、pinvoke.netにアクセスしてください。

于 2011-01-06T14:49:08.927 に答える
2

受け入れられた回答の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
于 2016-01-27T10:17:16.210 に答える