早期自繪選單方案需手動映射 WM_DRAWITEM/WM_MEASUREITEM,BCMenu 則宣稱無需映射即可自繪。本文深入分析 MFC 框架機制,發現關鍵在於「子選單必須為彈出式選單(CreatePopupMenu)框架才會自動呼叫 MeasureItem/DrawItem」。據此實作 CMenuEx::LoadMenu 重構整個選單樹,將所有子選單建立為彈出式選單並關聯 CMenuEx 實例,達成零訊息映射自繪。
MFC CWnd::OnMeasureItem 內部邏輯:
pMenu = GetMenu(); // 取得主選單
pMenu = _AfxFindPopupMenuFromID(pMenu, itemID); // 尋找對應彈出選單
if (pMenu) pMenu->MeasureItem(lpMIS); // 呼叫虛擬函數
只有彈出式選單(MF_POPUP)才會被 _AfxFindPopupMenuFromID 找到並觸發虛擬函數呼叫。因此 CMenuEx::LoadMenu 採用「讀取資源 → 遞迴重建」策略:
CreateMenu。CreatePopupMenu 建立,插入父選單時帶 MF_POPUP。ChangeMenuStyle 將所有項目設為 MF_OWNERDRAW,並將項目資料指標(LPCTSTR)指向自訂 MENUITEM 結構。BOOL CMenuEx::LoadMenu(UINT uMenu) {
HMENU hMenu = ::CreateMenu(); Attach(hMenu);
CMenu tmp; tmp.LoadMenu(uMenu);
for (int i=0; iAttach(hDest); m_SubMenuArr.Add(pNew);
for (int i=0; iGetMenuItemCount(); ++i) {
UINT uID = pSrc->GetMenuItemID(i);
MENUITEM* pItem = new MENUITEM; // 填充文字、ID、圖示索引等
if (uID == 0) { // 分隔線
AppendMenu(hDest, MF_SEPARATOR, 0, NULL);
ModifyMenu(hDest, i, MF_BYPOSITION|MF_OWNERDRAW, -1, (LPCTSTR)pItem);
} else if ((int)uID == -1) { // 子選單
// 建立巢狀彈出選單、遞迴
} else { // 一般項目
AppendMenu(hDest, MF_OWNERDRAW|MF_BYCOMMAND|state, uID, (LPCTSTR)pItem);
}
}
}
LoadToolBar(UINT uToolBar, UINT uFace):從工具列資源載入真彩圖示(uFace 為外部編輯的真彩位圖 ID),解決 VC6 難以直接編輯真彩工具列的問題。ModifyMenuEx/AppendMenuEx/RemoveMenuEx:自動處理 MF_OWNERDRAW 風格的選單動態修改介面。MenuEx.h/.cpp。CMenuEx m_Menu 成員。m_Menu.LoadMenu(IDR_MENU1) 載入選單資源。m_Menu.LoadToolBar(...)。MeasureItem/DrawItem 應盡量從 MENUITEM 結構取得所有繪製資訊,避免依賴 CMenuEx 成員變數,確保邏輯獨立、可重用。範例程式展示主選單與彈出選單自繪效果,含圖示、選取態、分隔線等完整 UI。