Windows 選單項目設定 MF_OWNERDRAW 旗標後,系統不再繪製預設外觀,改由擁有者視窗處理 WM_MEASUREITEM (計算尺寸) 和 WM_DRAWITEM (繪製內容) 訊息。MFC 將此封裝為 CWnd::OnMeasureItem/OnDrawItem 虛擬函式。
CCustomMenu 類別設計
class CCustomMenu : public CMenu {
public:
struct MENUDATA {
CString menuText; // 顯示文字
UINT menuIconNormal; // 正常狀態圖示資源 ID
UINT menuIconSelected; // 選中狀態圖示資源 ID (可選)
};
// 樣式設定
void SetIconSize(int cx, int cy); // 圖示尺寸 (一次設定,改變需重建選單)
void SetHighlightStyle(UINT nStyle); // Normal / TextOnly / ...
void SetTextColor(COLORREF clr);
void SetBackColor(COLORREF clr);
void SetHighlightColor(COLORREF clr);
// 新增項目 (覆寫 AppendMenu/InsertMenu 接受 MENUDATA*)
BOOL AppendMenu(UINT nFlags, UINT nIDNewItem, const MENUDATA* pData);
BOOL InsertMenu(UINT nPosition, UINT nFlags, UINT nIDNewItem, const MENUDATA* pData);
protected:
// 內部繪製邏輯
virtual void DrawItem(LPDRAWITEMSTRUCT lpDIS);
virtual void MeasureItem(LPMEASUREITEMSTRUCT lpMIS);
};
// 1. 定義成員
CCustomMenu m_customMenu;
CCustomMenu::MENUDATA menuData[3];
// 2. 初始化 (如 OnCreate 或 OnInitMenuPopup)
m_customMenu.CreateMenu();
m_customMenu.SetIconSize(16, 16);
m_customMenu.SetHighlightStyle(CCustomMenu::Normal); // 或 TextOnly
m_customMenu.SetTextColor(RGB(0, 0, 0));
m_customMenu.SetBackColor(RGB(255, 255, 255));
m_customMenu.SetHighlightColor(RGB(0, 0, 255));
// 3. 填充資料並新增項目
menuData[0].menuText = _T("開啟檔案(&O)");
menuData[0].menuIconNormal = IDI_ICON_OPEN;
m_customMenu.AppendMenu(MF_OWNERDRAW, ID_FILE_OPEN, &menuData[0]);
menuData[1].menuText = _T("儲存(&S)");
menuData[1].menuIconNormal = IDI_ICON_SAVE;
m_customMenu.AppendMenu(MF_OWNERDRAW, ID_FILE_SAVE, &menuData[1]);
// 4. 指派給視窗
SetMenu(&m_customMenu);
WM_MEASUREITEM
void CMyView::OnMeasureItem(int nIDCtl, LPMEASUREITEMSTRUCT lpMIS) {
if (lpMIS->CtlType == ODT_MENU && m_customMenu.IsMenu((HMENU)lpMIS->itemID)) {
m_customMenu.MeasureItem(lpMIS); // 委託給 CCustomMenu
} else {
CView::OnMeasureItem(nIDCtl, lpMIS);
}
}
void CMyView::OnDrawItem(int nIDCtl, LPDRAWITEMSTRUCT lpDIS) {
if (lpDIS->CtlType == ODT_MENU && m_customMenu.IsMenu((HMENU)lpDIS->itemID)) {
m_customMenu.DrawItem(lpDIS);
} else {
CView::OnDrawItem(nIDCtl, lpDIS);
}
}
DrawItem 關鍵繪製邏輯MENUDATA* 從 lpDIS->itemData。lpDIS->itemState (ODS_SELECTED、ODS_GRAYED、ODS_DISABLED、ODS_CHECKED) 決定顏色、筆刷、圖示。FillRect 使用選中/正常背景色。DrawIconEx 或 DrawState (支援灰階停用態)。DrawText 計算文字區域 (扣除圖示寬度),支援 & 加速鍵底線。Windows 系統字型 Marlett 內建標準 UI 符號:
0x30 (字元 '0') → 最大化方塊0x31 ('1') → 最小化線0x32 ('2') → 還原重疊方塊0x33 ('3') → 關閉叉叉0x34 ('4') → 向下箭頭 (下拉選單)0x35 ('5') → 向右箭頭 (子選單)使用方式:CFont font; font.CreateFont(16, 0, 0, 0, FW_NORMAL, 0, 0, 0, DEFAULT_CHARSET, 0, 0, 0, 0, _T("Marlett")); 選入 DC 後 TextOut 對應字元即可畫出完美縮放的系統符號。
CCustomMenu 解構時自動 DestroyMenu,若手動 Detach 需自行管理。IDI_ICON_*) 需在專案資源中定義,大小匹配 SetIconSize。LoadImage 配合 LR_DEFAULTSIZE。