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

自繪選單進階實現:CMenuEx::LoadMenu 重構彈出選單避免手動映射 WM_DRAWITEM/WM_MEASUREITEM

     編輯:VC 教學

使用 MFC 自繪選單通常需在框架類手動映射 WM_DRAWITEM、WM_MEASUREITEM,對話框專案尤覺麻煩。發現 BCMenu 庫無需映射即可自繪,研究發現關鍵在於 **將所有子選單建立為彈出式選單 (CreatePopupMenu)**,框架會自動對彈出選單觸發 MeasureItem/DrawItem 虛函數。本文展示 CMenuEx::LoadMenu 重構標準選單資源為全彈出式結構的完整代碼。

核心洞察

標準選單資源中,子選單為 MF_POPUP 旗標的項目。若在運行時將整個選單樹重建為 **每一層皆為獨立 HMENU (由 CreatePopupMenu 建立) 並關聯 CMenuEx 實例**,MFC 內部機制會自動對這些彈出選單調用虛函數,無需外部訊息映射轉發。

CMenuEx::LoadMenu 完整重構代碼

BOOL CMenuEx::LoadMenu(UINT uMenu)
{
    HMENU hMenu = ::CreateMenu(); // 建立頂層選單
    Attach(hMenu); // 關聯至當前 CMenuEx

    CMenu menuTemp; // 臨時載入原始資源
    menuTemp.LoadMenu(uMenu);

    for (int i = 0; i < (int)menuTemp.GetMenuItemCount(); ++i)
    {
        UINT uID = menuTemp.GetMenuItemID(i);
        if (uID == 0) // 分隔符
        {
            ::AppendMenu(hMenu, MF_SEPARATOR, 0, NULL);
        }
        else if ((int)uID == -1) // 子選單 (MF_POPUP)
        {
            CMenu* pSub = menuTemp.GetSubMenu(i);
            HMENU hSub = ::CreatePopupMenu(); // 關鍵:建立獨立彈出選單句柄
            CString strText; menuTemp.GetMenuString(i, strText, MF_BYPOSITION);
            ::InsertMenu(hMenu, i, MF_BYPOSITION | MF_POPUP | MF_STRING, (UINT)hSub, strText);
            ChangeMenuStyle(pSub, hSub); // 遞迴處理子選單
        }
        else // 一般命令項
        {
            CString strText; menuTemp.GetMenuString(uID, strText, MF_BYCOMMAND);
            AppendMenu(MF_STRING, uID, strText);
        }
    }
    menuTemp.DestroyMenu();
    return TRUE;
}

遞迴重構核心 ChangeMenuStyle

void CMenuEx::ChangeMenuStyle(CMenu* pSrcMenu, HMENU hDestMenu)
{
    // 關聯新 CMenuEx 實例以接收虛函數回調
    CMenuEx* pNewMenu = new CMenuEx;
    pNewMenu->Attach(hDestMenu);
    m_SubMenuArr.Add(pNewMenu); // 成員陣列持有指標,防止過早釋放

    for (int i = 0; i < pSrcMenu->GetMenuItemCount(); ++i)
    {
        UINT uID = pSrcMenu->GetMenuItemID(i);
        MENUITEM* pItem = new MENUITEM; // 自定義結構體保存繪製所需資料
        pItem->pImageList = &m_ImageList; // 共享圖像列表

        if (uID == 0) // 分隔符
        {
            ::AppendMenu(hDestMenu, MF_SEPARATOR, 0, NULL);
            pItem->uID = 0; pItem->uIndex = -1;
            ::ModifyMenu(hDestMenu, i, MF_BYPOSITION | MF_OWNERDRAW, -1, (LPCTSTR)pItem);
        }
        else if (uID == (UINT)-1) // 子選單
        {
            CMenu* pSub = pSrcMenu->GetSubMenu(i);
            HMENU hPop = ::CreatePopupMenu();
            CString str; pSrcMenu->GetMenuString(i, str, MF_BYPOSITION);
            ::InsertMenu(hDestMenu, i, MF_BYPOSITION | MF_POPUP, (UINT)hPop, str);
            pItem->uID = -1; pItem->strText = str; pItem->uIndex = -1;
            ::ModifyMenu(hDestMenu, i, MF_BYPOSITION | MF_OWNERDRAW, -1, (LPCTSTR)pItem);
            ChangeMenuStyle(pSub, hPop); // 遞迴
        }
        else // 命令項
        {
            CString str; pSrcMenu->GetMenuString(uID, str, MF_BYCOMMAND);
            pItem->uID = uID; pItem->strText = str; pItem->uIndex = -1;
            UINT uState = pSrcMenu->GetMenuState(i, MF_BYPOSITION);
            ::AppendMenu(hDestMenu, MF_OWNERDRAW | MF_BYCOMMAND | uState, uID, (LPCTSTR)pItem);
        }
        m_MenuItemArr.Add(pItem); // 統一管理所有項目資料
    }
}

關鍵設計要點

優缺點評估

  • 優點: 徹底免除框架類手動映射 WM_DRAWITEM/MEASUREITEM,對話框專案直接用;封裝性極佳,CMenuEx 內聚繪製邏輯與資料。
  • 缺點: 運行時重建選單結構有微小效能開銷;動態修改選單 (如 EnableMenuItem) 需同步更新內部結構;系統選單 (GetSystemMenu) 不適用此機制。

這種「資源→運行時彈出式樹重構」模式是 BCMenu、CMenuEx 等優秀自繪選單庫的共同核心架構,值得深入掌握。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved
技術點說明
CreatePopupMenu vs CreateMenu頂層用 CreateMenu (水平列),所有子層用 CreatePopupMenu (垂直彈出),這是框架自動回調虛函數的前提。
MF_OWNERDRAW 旗標每個項目 (含分隔符、彈出選單標題) 必須加上此旗標,框架才會嘗試呼叫 MeasureItem/DrawItem。
自定義 MENUITEM 結構利用 AppendMenu/ModifyMenu 最後一個參數 LPCTSTR 傳遞 MENUITEM* 指標,在 DrawItem 中取回 (lpDIS->itemData),攜帶圖示索引、文字、圖像列表指標等繪製資料。
子選單實例管理m_SubMenuArr 陣列持有所有層級 CMenuEx*,確保生命週期覆蓋選單顯示期間,DestroyMenu 時統一清理。