標準 MFC 控件 (如 CButton) 功能基礎,外觀固定。現代應用需要扁平化、圖標化、自繪、狀態感知等高級 UI 效果。通過繼承 MFC 類層次結構,利用「自繪」機制,我們可以構建功能豐富、外觀可定制的專屬控件庫。
CObject
└─ CCmdTarget
└─ CWnd
└─ CButton // 標準按鈕、複選框、單選框
└─ CBitmapButton // 位圖按鈕 (支持上/下/聚焦/禁用四態)
設置按鈕風格為 BS_OWNERDRAW,系統將繪製責任轉交給應用程序。MFC 通過虛函數 DrawItem 實現:
virtual void DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct);
父窗口收到 WM_DRAWITEM 消息後,MFC 框架分發調用該控件的 DrawItem。所有繪製所需信息封裝在 DRAWITEMSTRUCT 中。
| 成員 | 類型 | 含義 |
|---|---|---|
| CtlType | UINT | 控件類型:ODT_BUTTON, ODT_LISTBOX, ODT_COMBOBOX, ODT_MENU... |
| CtlID | UINT | 控件 ID (菜單項無效) |
| itemID | UINT | 菜單項 ID 或列表/組合框索引 |
| itemAction | UINT | 繪製動作:ODA_DRAWENTIRE(全繪), ODA_FOCUS(焦點), ODA_SELECT(選中) |
| itemState | UINT | 狀態標志:ODS_SELECTED, ODS_FOCUS, ODS_DISABLED, ODS_CHECKED... |
| hwndItem | HWND | 控件窗口句柄 (菜單為 HMENU) |
| hDC | HDC | 繪製設備上下文 |
| rcItem | RECT | 控件矩形區域 |
| itemData | DWORD | 應用程序自定義數據 (如 SetItemData 設置) |
| 函數 | 用途 | 關鍵參數/返回值 |
|---|---|---|
Create | 創建窗口 | 風格組合: BS_PUSHBUTTON, BS_CHECKBOX, BS_RADIOBUTTON, BS_OWNERDRAW, BS_BITMAP, BS_ICON, BS_FLAT... |
DrawItem | 自繪核心 | 重寫此虛函數實現自定義繪製邏輯 |
SetBitmap/GetBitmap | 設置/獲取位圖 | 需風格 BS_BITMAP,位圖句柄生命周期需 ≥ 控件 |
SetIcon/GetIcon | 設置/獲取圖標 | 需風格 BS_ICON |
SetCheck/GetCheck | 設置/獲取勾選狀態 | BST_UNCHECKED(0), BST_CHECKED(1), BST_INDETERMINATE(2) |
SetButtonStyle | 動態修改風格 | 部分風格 (BS_LEFTTEXT, BS_BITMAP 等) 僅創建時有效 |
GetState | 獲取綜合狀態 | 包含焦點(0x0008)、高亮(0x0004)、勾選狀態 |
CFlatIconButtonclass CFlatIconButton : public CButton {
DECLARE_DYNAMIC(CFlatIconButton)
public:
CFlatIconButton();
virtual ~CFlatIconButton();
// 設置四態圖標
void SetIcons(HICON hNormal, HICON hHover, HICON hPressed, HICON hDisabled);
protected:
HICON m_hIconNormal, m_hIconHover, m_hIconPressed, m_hIconDisabled;
BOOL m_bTracking; // 鼠標跟蹤標志
BOOL m_bPressed; // 按下狀態
BOOL m_bHover; // 懸停狀態
// 重寫消息處理
afx_msg void OnMouseMove(UINT nFlags, CPoint point);
afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
afx_msg void OnLButtonUp(UINT nFlags, CPoint point);
afx_msg void OnMouseLeave();
afx_msg void OnKillFocus(CWnd* pNewWnd);
virtual void DrawItem(LPDRAWITEMSTRUCT lpDIS); // 核心繪製
DECLARE_MESSAGE_MAP()
};
BEGIN_MESSAGE_MAP(CFlatIconButton, CButton)
ON_WM_MOUSEMOVE()
ON_WM_LBUTTONDOWN()
ON_WM_LBUTTONUP()
ON_WM_MOUSELEAVE()
ON_WM_KILLFOCUS()
END_MESSAGE_MAP()
void CFlatIconButton::OnMouseMove(UINT nFlags, CPoint point) {
if (!m_bTracking) {
TRACKMOUSEEVENT tme = { sizeof(tme), TME_LEAVE, m_hWnd, HOVER_DEFAULT };
_TrackMouseEvent(&tme);
m_bTracking = TRUE;
}
if (!m_bHover) {
m_bHover = TRUE;
Invalidate(); // 觸發重繪
}
CButton::OnMouseMove(nFlags, point);
}
void CFlatIconButton::OnMouseLeave() {
m_bTracking = FALSE;
m_bHover = FALSE;
Invalidate();
CButton::OnMouseLeave();
}
void CFlatIconButton::OnLButtonDown(UINT nFlags, CPoint point) {
m_bPressed = TRUE;
SetCapture();
Invalidate();
CButton::OnLButtonDown(nFlags, point);
}
void CFlatIconButton::OnLButtonUp(UINT nFlags, CPoint point) {
if (m_bPressed) {
m_bPressed = FALSE;
ReleaseCapture();
Invalidate();
// 矩形內彈起才發送點擊通知
CRect rect; GetClientRect(rect);
if (rect.PtInRect(point)) GetParent()->SendMessage(WM_COMMAND, MAKEWPARAM(GetDlgCtrlID(), BN_CLICKED), (LPARAM)m_hWnd);
}
CButton::OnLButtonUp(nFlags, point);
}
void CFlatIconButton::OnKillFocus(CWnd* pNewWnd) {
m_bPressed = FALSE; m_bHover = FALSE; m_bTracking = FALSE;
Invalidate();
CButton::OnKillFocus(pNewWnd);
}
DrawItemvoid CFlatIconButton::DrawItem(LPDRAWITEMSTRUCT lpDIS) {
CDC* pDC = CDC::FromHandle(lpDIS->hDC);
CRect rect(lpDIS->rcItem);
UINT state = lpDIS->itemState;
// 1. 選擇當前狀態對應圖標
HICON hIcon = m_hIconNormal;
if (state & ODS_DISABLED) hIcon = m_hIconDisabled ? m_hIconDisabled : m_hIconNormal;
else if (m_bPressed) hIcon = m_hIconPressed ? m_hIconPressed : m_hIconNormal;
else if (m_bHover) hIcon = m_hIconHover ? m_hIconHover : m_hIconNormal;
// 2. 繪製背景 (扁平化風格)
if (state & ODS_SELECTED || m_bPressed) {
// 按下/選中:凹陷效果
pDC->Draw3dRect(rect, GetSysColor(COLOR_3DSHADOW), GetSysColor(COLOR_3DHIGHLIGHT));
rect.DeflateRect(1, 1);
pDC->FillSolidRect(rect, GetSysColor(COLOR_3DFACE));
} else if (m_bHover) {
// 懸停:凸起邊框 + 淡藍背景
pDC->Draw3dRect(rect, GetSysColor(COLOR_3DHIGHLIGHT), GetSysColor(COLOR_3DSHADOW));
rect.DeflateRect(1, 1);
CBrush brush(RGB(220, 235, 250)); pDC->FillRect(rect, &brush);
} else {
// 正常:透明背景
pDC->FillSolidRect(rect, GetSysColor(COLOR_3DFACE));
}
// 3. 繪製圖標居中
if (hIcon) {
ICONINFO ii; GetIconInfo(hIcon, &ii);
int x = (rect.Width() - ii.xHotspot*2) / 2 + rect.left;
int y = (rect.Height() - ii.yHotspot*2) / 2 + rect.top;
::DrawIconEx(pDC->GetSafeHdc(), x, y, hIcon, 0, 0, 0, NULL, DI_NORMAL);
}
// 4. 繪製焦點矩形
if (state & ODS_FOCUS) {
rect.DeflateRect(3, 3);
pDC->DrawFocusRect(rect);
}
}
UxTheme API (OpenThemeData, DrawThemeBackground) 繪製系統原生風格GetDpiForWindow 縮放圖標/邊距,支持 Per-Monitor DPICAnimationController 實現狀態切換過渡動畫CButtonT 靜態多態掌握 BS_OWNERDRAW + DrawItem + DRAWITEMSTRUCT 這套 MFC 自繪核心機制,結合鼠標/鍵盤消息跟蹤狀態,即可構建任意外觀、行為的專業級按鈕控件。這正是從「調用框架」進化為「擴展框架」的關鍵一步。