想在程式中展現獨特視覺風格,標準控制項遠遠不夠。本文將帶你從零設計一個 CXPButton 類別,完整涵蓋:**五大狀態視覺設計、Owner-Draw 啟用、滑鼠懸停/離開追蹤 (WM_MOUSEHOVER/LEAVE)、DrawItem 核心繪製邏輯、非客戶區繪製細節** 等自繪按鈕開發的完整知識體系。
動碼前先設計,參考 Windows XP 原生按鈕風格定義:
DrawFocusRect)。COLOR_GRAYTEXT)。
設計草圖:五大狀態視覺規範
ClassView 右鍵 Add Class,派生 CButton,命名 CXPButton。成員變量集中於繪圖資源 (筆、刷、字體、區域) 於建構函數建立、解構函數釋放,詳見附件源碼。
PreSubclassWindow:啟用 Owner-Draw 屬性void CXPButton::PreSubclassWindow()
{
CButton::PreSubclassWindow();
ModifyStyle(0, BS_OWNERDRAW); // 激活自繪,框架將呼叫 DrawItem
}
WM_MOUSEMOVE 觸發 TrackMouseEvent 獲取 HOVER/LEAVEvoid CXPButton::OnMouseMove(UINT nFlags, CPoint point)
{
if (!m_bTracking) // 避免重複註冊
{
TRACKMOUSEEVENT tme = { sizeof(tme), TME_LEAVE | TME_HOVER, m_hWnd, 1 }; // 1ms 延遲觸發 HOVER
m_bTracking = _TrackMouseEvent(&tme);
}
CButton::OnMouseMove(nFlags, point);
}
// 手動添加訊息映射 (ClassWizard 不支援):
// ON_MESSAGE(WM_MOUSELEAVE, OnMouseLeave)
// ON_MESSAGE(WM_MOUSEHOVER, OnMouseHover)
afx_msg LRESULT OnMouseLeave(WPARAM, LPARAM);
afx_msg LRESULT OnMouseHover(WPARAM, LPARAM);
於 OnMouseLeave 設 m_bOver=FALSE、Invalidate;OnMouseHover 設 m_bOver=TRUE、Invalidate。MouseOver() 供 DrawItem 查詢。
DrawItem:自繪核心,狀態機驅動視覺渲染此函數為虛函數,ClassView → 右鍵類別 → Add Virtual Function → 選 DrawItem 添加。核心邏輯:
CDC*、CRect、itemState。ODS_SELECTED, ODS_HOTLIGHT, ODS_DISABLED, ODS_FOCUS) 選擇筆刷、顏色、文字偏移。SelectObject 舊筆/刷/字體)。完整代碼較長,請參考附件源碼 XPButton.cpp。
WM_NCPAINT 實現邊框陰影等效果若需在標準按鈕邊框外繪製 (如外發光、陰影),需重寫 OnNcPaint 並攔截 WM_NCPAINT。本範例專注客戶區繪製,非客戶區保留系統默認。
| 難點 | 解決方案 |
|---|---|
| WM_MOUSELEAVE/HOVER 非標準訊息,ClassWizard 無法添加 | 在 WM_MOUSEMOVE 中呼叫 TrackMouseEvent(TME_LEAVE|TME_HOVER) 由系統產生;訊息映射手寫 ON_MESSAGE(WM_MOUSELEAVE, OnMouseLeave)。 |
| DRAWITEMSTRUCT 結構體資訊解讀複雜 | 重點欄位:hDC (繪圖 DC)、rcItem (項目矩形)、itemState (狀態旗標組合)、itemAction (繪製動作:ODA_DRAWENTIRE/SELECT/FOCUS)、itemID (控制項 ID)。 |

Add Class 對話框設定

Add Virtual Function 添加 DrawItem
CXPButton 完整展示了 MFC 自繪按鈕的標準開發流程:設計狀態 → 啟用 OwnerDraw → 追蹤滑鼠狀態 → DrawItem 狀態機渲染 → 資源管理。源碼結構清晰,可直接整合至專案作為基礎按鈕類別延伸開發 (如加入圖示、動畫、主題支援)。