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

三個函數搞定 MFC 框架選單自繪:告別派生類,輕松打造酷炫選單風格

     編輯:VC 教學

網路上許多 MFC 自繪選單教學都要求派生新類別,其實大材小用。利用 MFC 內建的 Owner-Draw 機制,只需在 CMainFrame 中處理三個核心訊息,貼上三個函數,即可立刻擁有漂亮的自繪選單。本文提供開箱即用的完整代碼,並解析關鍵結構體用法。

核心原理

若選單項擁有 MF_OWNERDRAW 標誌,MFC 框架會自動呼叫:

  • OnMeasureItem:決定選單項寬高
  • OnDrawItem:繪製選單項外觀 (背景、文字、圖示、分隔線)

我們只需在 OnInitMenu 中為所有選單項加上此標誌即可啟動自繪流程。

三大核心函數完整代碼

1. OnMeasureItem:精確控制尺寸

void CMainFrame::OnMeasureItem(int nIDCtl, LPMEASUREITEMSTRUCT lpMeasureItemStruct)
{
    LPMEASUREITEMSTRUCT& lpM = lpMeasureItemStruct;
    if (lpM->CtlType == ODT_MENU) // 確認為選單自繪
    {
        if (lpM->itemID != ID_SEPARATOR) // 一般選單項
        {
            lpM->itemHeight = 20;
            lpM->itemWidth = 150;
        }
        else // 分隔線
        {
            lpM->itemHeight = 1; // 極細分隔線
            lpM->itemWidth = 150;
        }
    }
}

2. OnDrawItem:繪製視覺風格 (漸層背景、選中態、失效態、文字置中)

void CMainFrame::OnDrawItem(int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct)
{
    LPDRAWITEMSTRUCT& lpD = lpDrawItemStruct;
    if (nIDCtl == 0) // 0 代表選單 (按鈕自繪也會進來,需區分)
    {
        CDC* pDC = CDC::FromHandle(lpD->hDC);
        pDC->SetBkMode(TRANSPARENT);

        CMenu menu;
        menu.Attach((HMENU)lpD->hwndItem); // 取得選單物件
        CRect rcItem(lpD->rcItem); // 當前項目矩形
        CRect rcLeft(rcItem.left, rcItem.top, 20, rcItem.bottom); // 左側圖示區 (寬 20px)

        // 定義三種狀態筆刷與文字顏色
        CBrush brNormal(RGB(250, 250, 250)); // 一般背景
        CBrush brLeft(RGB(230, 230, 230));   // 左側欄背景
        CBrush brSel(RGB(148, 170, 214));    // 選中藍

        CString strText;
        menu.GetMenuString(lpD->itemID, strText, MF_BYCOMMAND);

        if (lpD->itemID != ID_SEPARATOR) // 一般項目繪製
        {
            // --- 選中/高亮狀態 ---
            if (lpD->itemAction & ODA_SELECT)
            {
                pDC->FillRect(rcItem, &brSel);
                if (lpD->itemState & ODS_GRAYED) pDC->SetTextColor(RGB(194, 194, 194)); // 失效灰
                else if (lpD->itemState & ODS_SELECTED) pDC->SetTextColor(RGB(250, 250, 250)); // 選中白
            }
            // --- 一般/非選中狀態 ---
            if (!((lpD->itemAction & ODA_SELECT) && (lpD->itemState & ODS_SELECTED)))
            {
                pDC->FillRect(rcItem, &brNormal);
                pDC->FillRect(rcLeft, &brLeft); // 畫左側灰欄
                if (lpD->itemState & ODS_GRAYED) pDC->SetTextColor(RGB(194, 194, 194));
                else pDC->SetTextColor(RGB(66, 110, 180)); // 一般深藍字
            }
            // 文字輸出 (左邊留 30px 給圖示區)
            pDC->TextOut(rcItem.left + 30, rcItem.top + 5, strText);
        }
        else // 分隔線繪製
        {
            pDC->FillRect(rcItem, &brLeft);
        }
        menu.Detach(); // 必須分離,否則資源洩漏
    }
}

3. OnInitMenu:一鍵開啟全選單 Owner-Draw 模式

void CMainFrame::OnInitMenu(CMenu* pMenu)
{
    CMenu* pSubMenu;
    UINT nCount = pMenu->GetMenuItemCount();
    for (UINT i = 0; i < nCount; ++i)
    {
        pSubMenu = pMenu->GetSubMenu(i);
        if (pSubMenu)
        {
            UINT nSubCount = pSubMenu->GetMenuItemCount();
            for (UINT j = 0; j < nSubCount; ++j)
            {
                UINT nID = pSubMenu->GetMenuItemID(j);
                // 關鍵:修改風格加入 MF_OWNERDRAW
                pSubMenu->ModifyMenu(j, MF_BYPOSITION | MF_OWNERDRAW, nID);
            }
        }
    }
}

關鍵結構體速查表

結構體關鍵成員說明
MEASUREITEMSTRUCTCtlType, itemID, itemWidth, itemHeight用於 OnMeasureItem,告知項目類型與尺寸
DRAWITEMSTRUCTCtlType, itemID, itemAction, itemState, hwndItem, hDC, rcItem, itemData用於 OnDrawItem,包含繪圖 DC、狀態、矩形、自訂資料指標

已知限制與優化建議

  • 效能優化: OnInitMenu 每次彈出選單都會觸發,建議加入 bool m_bMenuStyled 標誌,避免重複 ModifyMenu;或在 OnCreate 一次性完成。
  • 子選單支援: 目前代碼僅處理第一層子選單,多層巢狀選單需遞迴遍歷。
  • 「最近使用檔案」無效: 該動態選單項特殊處理,需額外攔截 WM_INITMENUPOPUP 處理。

這套「三函數走天下」方案,極適合快速原型開發或維護舊專案,讓單調的灰色選單瞬間煥然一新!

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