首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

C# 調用 Windows API 操作系統選單

     編輯:關於C語言

前言

.NET Framework 未直接提供系統選單操作相關屬性或方法(如 SystemMenu、GetSystemMenu),本文通過 P/Invoke 調用 Windows API,封裝出一個 SystemMenu 類,實現對系統選單的完整控制:添加自定義項、禁用/移除內建項(如關閉、最大化、最小化)、響應自定義項點擊事件。

系統選單基礎知識

  • 點擊視窗圖標或右鍵標題欄彈出
  • 包含視窗預設行為(還原、移動、大小、最小化、最大化、關閉)
  • 修改系統選單會同步影響右上角三大按鈕狀態
  • 通過 GetSystemMenu 獲取選單句柄副本,配合 AppendMenu、InsertMenu 等 API 實現控制

核心類設計:SystemMenu

關鍵枚舉定義

public enum ItemFlags
{
    mfString = 0x00000000,      // 顯示字符串
    mfSeparator = 0x00000800,   // 分隔線
    mfChecked = 0x00000008,     // 打勾
    mfDisabled = 0x00000002,    // 禁用
    mfGrayed = 0x00000001,      // 灰色
    mfPopup = 0x00000010,       // 彈出子選單
    mfByPosition = 0x00000400,  // 按位置索引
    mfByCommand = 0x00000000,   // 按命令 ID
    mfBarBreak = 0x00000020,    // 欄分隔
    mfBreak = 0x00000040        // 換行/換列
}

public enum WindowMessages
{
    wmSysCommand = 0x0112
}

P/Invoke 簽名

[DllImport("USER32", EntryPoint = "GetSystemMenu", SetLastError = true, 
    CharSet = CharSet.Unicode, ExactSpelling = true, 
    CallingConvention = CallingConvention.Winapi)]
private static extern IntPtr apiGetSystemMenu(IntPtr WindowHandle, int bReset);

[DllImport("USER32", EntryPoint = "AppendMenuW", SetLastError = true, 
    CharSet = CharSet.Unicode, ExactSpelling = true, 
    CallingConvention = CallingConvention.Winapi)]
private static extern int apiAppendMenu(IntPtr MenuHandle, int Flags, int NewID, String Item);

[DllImport("USER32", EntryPoint = "InsertMenuW", SetLastError = true, 
    CharSet = CharSet.Unicode, ExactSpelling = true, 
    CallingConvention = CallingConvention.Winapi)]
private static extern int apiInsertMenu(IntPtr hMenu, int Position, int Flags, int NewId, String Item);

核心方法

  • FromForm(Form):靜態工廠方法,獲取視窗系統選單句柄,失敗拋 NoSystemMenuException
  • AppendMenu / InsertMenu:添加/插入選單項,支援分隔線、打勾、禁用等標志組合
  • AppendSeparator / InsertSeparator:便捷分隔線方法
  • ResetSystemMenu(Form):還原預設系統選單
  • VerifyItemID(int):驗證 ID 範圍(1 ~ 0xEFFF),避免與系統命令衝突

實戰使用範例

表單載入時初始化

private SystemMenu m_SystemMenu = null;
private const int m_AboutID = 0x100;
private const int m_ResetID = 0x101;

private void frmMain_Load(object sender, EventArgs e)
{
    try
    {
        m_SystemMenu = SystemMenu.FromForm(this);
        
        // 底部添加分隔線 + "關於" 項
        m_SystemMenu.AppendSeparator();
        m_SystemMenu.AppendMenu(m_AboutID, "關於");
        
        // 頂部插入分隔線 + "重置系統選單" 項
        m_SystemMenu.InsertSeparator(0);
        m_SystemMenu.InsertMenu(0, m_ResetID, "重置系統選單");
    }
    catch (NoSystemMenuException)
    {
        // 錯誤處理
    }
}

攔截 WM_SYSCOMMAND 響應自定義項

protected override void WndProc(ref Message msg)
{
    base.WndProc(ref msg); // 必須調用基類實現
    
    if (msg.Msg == (int)WindowMessages.wmSysCommand)
    {
        switch (msg.WParam.ToInt32())
        {
            case m_ResetID:
                if (MessageBox.Show(this, "\t確定要重置?", "提問", 
                    MessageBoxButtons.YesNo) == DialogResult.Yes)
                {
                    SystemMenu.ResetSystemMenu(this);
                }
                break;
            case m_AboutID:
                MessageBox.Show(this, "作者:朱先中 \n\n e-mail: sdmyzxz@163.com", "關於");
                break;
        }
    }
}

進階思考

  • 可進一步封裝為事件模式:定義 OnSysCommand 事件,WndProc 中觸發,業務邏輯通過事件訂閱處理,解耦更徹底
  • ID 衝突風險:建議統一常量管理,或使用 VerifyItemID 防禦性檢查
  • 多視窗應用中,每個 Form 需獨立 SystemMenu 實例

總結

本文通過 SystemMenu 類完整演示了 .NET 平台調用非托管 DLL 的標準流程:DllImport 簽名 → IntPtr 句柄操作 → 靜態工廠封裝 → 訊息迴路攔截。源碼在 Windows 2000 Server / VS .NET 2003 下調試通過,核心原理至今仍適用於 .NET 6/7/8 的 Windows Forms 應用。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved