選單系統架構
Windows 選單分為主選單列 (附著於視窗頂列) 與彈出式選單 (下拉/右鍵/子選單)。所有選單本質皆為 HMENU 控制代碼,操作函式統一適用。現代應用多採用資源腳本 (.rc) 定義選單結構,再以 LoadMenu 載入,但動態建立/修改選單仍需精通核心 API。
核心函式分類表
1. 建立與銷毀
| 函式 | 用途 | 備註 |
| CreateMenu | 建立空主選單 | 須配合 AppendMenu/InsertMenuItem 填充 |
| CreatePopupMenu | 建立空彈出式選單 | 用於子選單、右鍵選單 |
| LoadMenu / LoadMenuIndirect | 從資源/記憶體模板載入 | Indirect 版接受記憶體 MENUITEMTEMPLATE |
| DestroyMenu | 銷毀選單並釋放資源 | 必須先從視窗移除 (SetMenu NULL) |
2. 項目增刪與修改
| 函式 | 用途 | 關鍵參數 |
| AppendMenu | 尾端附加項目 | uFlags: MF_STRING, MF_POPUP, MF_SEPARATOR, MF_BITMAP, MF_OWNERDRAW |
| InsertMenu / InsertMenuItem | 指定位置插入 | InsertMenuItem 支援完整 MENUITEMINFO 結構 (現代首選) |
| DeleteMenu | 依位置/命令 ID 刪除 | uFlags: MF_BYPOSITION / MF_BYCOMMAND |
| RemoveMenu | 刪除但保留彈出子選單控制代碼 | 配合 DestroyMenu 釋放子選單 |
| ModifyMenu | 修改現有項目屬性 | 舊版 API,建議改用 SetMenuItemInfo |
| SetMenuItemInfo / GetMenuItemInfo | 完整屬性設定/查詢 | 支援位圖、檢查標記、快速鍵、子選單等所有屬性 |
3. 狀態控制 (啟用/檢查/預設/加亮)
| 函式 | 用途 |
| EnableMenuItem | MF_ENABLED / MF_GRAYED / MF_DISABLED |
| CheckMenuItem | MF_CHECKED / MF_UNCHECKED |
| CheckMenuRadioItem | 群組單選效果 (自動取消同群組其他項) |
| SetMenuDefaultItem / GetMenuDefaultItem | 雙擊標題列/Enter 鍵觸發的預設命令 |
| HiliteMenuItem | 主選單列項目加亮/取消 (WM_MENUSELECT 內部使用) |
4. 資訊查詢
| 函式 | 用途 |
| GetMenuItemCount | 項目總數 (含分隔線) |
| GetMenuItemID | 依位置取得命令 ID |
| GetMenuString | 取得項目文字 |
| GetMenuState | 取得項目狀態旗標組合 |
| GetSubMenu | 依位置取得子選單 HMENU |
| GetMenuItemRect | 取得項目螢幕矩形 (Owner-draw 定位用) |
| MenuItemFromPoint | 依螢幕點擊座標反查項目 |
5. 系統選單與關聯
| 函式 | 用途 |
| GetSystemMenu | 取得視窗系統選單副本 (可自訂關閉/最大化等項目) |
| SetMenu / GetMenu | 視窗主選單綁定/取得 |
| DrawMenuBar | 主選單列變更後強制重繪 |
6. 追蹤彈出式選單 (右鍵/按鍵觸發)
| 函式 | 用途 |
| TrackPopupMenu | 顯示彈出選單並阻塞直到選取/取消 |
| TrackPopupMenuEx | 支援 TPM_RECURSE (巢狀彈出) 等進階旗標 |
7. 視覺客製化
| 函式 | 用途 |
| SetMenuItemBitmaps | 以位圖取代核取方塊/圓形按鈕 |
| SetMenuContextHelpId / GetMenuContextHelpId | 說明內容 ID 關聯 |
實務開發模式範例
動態建立右鍵選單
HMENU hPopup = CreatePopupMenu();
AppendMenu(hPopup, MF_STRING, ID_COPY, L"複製 (&C)");
AppendMenu(hPopup, MF_STRING, ID_PASTE, L"貼上 (&P)");
AppendMenu(hPopup, MF_SEPARATOR, 0, NULL);
AppendMenu(hPopup, MF_STRING, ID_DELETE, L"刪除 (&D)");
POINT pt; GetCursorPos(&pt);
TrackPopupMenu(hPopup, TPM_LEFTALIGN | TPM_RIGHTBUTTON, pt.x, pt.y, 0, hWnd, NULL);
DestroyMenu(hPopup); // TrackPopupMenu 返回後立即銷毀
執行時切換主選單
HMENU hNewMenu = LoadMenu(hInst, MAKEINTRESOURCE(IDR_MENU_ADMIN));
SetMenu(hWnd, hNewMenu);
DrawMenuBar(hWnd); // 必須呼叫以更新標題列
Owner-Draw 選單項目測量與繪製
設定 MF_OWNERDRAW 旗標,視窗程序處理 WM_MEASUREITEM (回傳寬高) 與 WM_DRAWITEM (繪製邏輯),配合 GetMenuItemRect 定位圖示/文字區域。
常見陷阱與最佳實務
- 資源洩漏:動態建立的選單 (CreateMenu/Popup) 必須配對
DestroyMenu;從資源載入 (LoadMenu) 的選單在視窗銷毀時由系統自動釋放,但若 SetMenu(hWnd, NULL) 卸載後重新載入,舊選單需手動 DestroyMenu。
- 命令 ID 衝突:主選單、彈出選單、工具列、快速鍵共用命令 ID 空間,請統一規劃資源 ID 範圍。
- DPI 縮放:Owner-draw 選單需自行處理
GetDpiForWindow 縮放圖示/字型大小。
- 無障礙:
MF_OWNERDRAW 項目必須支援 WM_GETOBJECT 回傳 IAccessible 供螢幕閱讀器使用。
結語
選單 API 雖歷經數十年演進仍保持向下相容,現代開發建議:靜態結構用資源腳本 + LoadMenu,動態調整用 InsertMenuItem/SetMenuItemInfo,彈出選單用 TrackPopupMenuEx。深入理解 MENUITEMINFO 結構的 fMask、fType、fState、hbmpItem/hbmpChecked/hbmpUnchecked 等欄位,即可掌握所有選單客製化需求。