想擁有 XP 風格按鈕 (滑鼠經過變藍、按下凹陷、焦點虛線框),但不想依賴第三方巨型類別庫?本文教你從 CButton 派生 CButtonXp,僅用 WM_MOUSEMOVE + TrackMouseEvent + DrawItem 三大核心,完整實現專業級 XP 風格按鈕。

// CButtonXp.h
class CButtonXp : public CButton
{
BOOL m_bOver = FALSE; // 滑鼠是否在按鈕區域
protected:
afx_msg void OnMouseMove(UINT nFlags, CPoint point);
afx_msg LRESULT OnMouseOut(WPARAM, LPARAM); // WM_MOUSELEAVE
virtual void DrawItem(LPDRAWITEMSTRUCT lpDIS);
DECLARE_MESSAGE_MAP()
};
// CButtonXp.cpp
void CButtonXp::PreSubclassWindow()
{
ModifyStyle(0, BS_OWNERDRAW); // 關鍵:啟用自繪
CButton::PreSubclassWindow();
}
TrackMouseEvent 實現 WM_MOUSELEAVEMFC 無內建 WM_MOUSELEAVE,需手動註冊追蹤:
void CButtonXp::OnMouseMove(UINT nFlags, CPoint point)
{
if (!m_bOver) // 首次進入
{
m_bOver = TRUE;
InvalidateRect(NULL, FALSE); // 觸發重繪 (進入態)
// 註冊離開追蹤
TRACKMOUSEEVENT tme = { sizeof(TRACKMOUSEEVENT), TME_LEAVE, m_hWnd, 0 };
::TrackMouseEvent(&tme); // 需 #include 與 extern "C" 宣告
}
CButton::OnMouseMove(nFlags, point);
}
// 訊息映射需手動添加:ON_MESSAGE(WM_MOUSELEAVE, OnMouseOut)
LRESULT CButtonXp::OnMouseOut(WPARAM, LPARAM)
{
m_bOver = FALSE;
InvalidateRect(NULL, FALSE); // 觸發重繪 (離開態)
return 0;
}
BOOL CButtonXp::MouseOver() const { return m_bOver; }
DrawItem:五大狀態完整邏輯void CButtonXp::DrawItem(LPDRAWITEMSTRUCT lpDIS)
{
CDC* pDC = CDC::FromHandle(lpDIS->hDC);
CRect rc(lpDIS->rcItem);
UINT state = lpDIS->itemState;
// 字體與焦點矩形調整
HFONT hOldFont = (HFONT)pDC->SelectObject((HFONT)::GetStockObject(DEFAULT_GUI_FONT));
if (state & ODS_FOCUS) rc.DeflateRect(1, 1);
CPen pen, *pOldPen; CBrush brush, *pOldBrush;
CString strText; GetWindowText(strText);
// --- 狀態判斷與筆刷/筆/文字顏色設定 ---
if ((state & ODS_DISABLED) || (!MouseOver() && !(state & ODS_SELECTED)))
{
// 一般/禁用/焦點非選中:灰邊框、淺灰背景
pen.CreatePen(PS_SOLID, 1, ::GetSysColor(COLOR_3DSHADOW));
brush.CreateSolidBrush(HLS_TRANSFORM(::GetSysColor(COLOR_3DFACE), -10, 0));
pDC->SetTextColor(state & ODS_DISABLED ? ::GetSysColor(COLOR_GRAYTEXT) : ::GetSysColor(COLOR_BTNTEXT));
}
else
{
// 滑鼠進入 或 按下:藍邊框 (系統高亮色)
COLORREF crBorder = ::GetSysColor(COLOR_HIGHLIGHT);
pen.CreatePen(PS_SOLID, 1, crBorder);
if (state & ODS_SELECTED) // 按下態
{
brush.CreateSolidBrush(HLS_TRANSFORM(crBorder, +50, -50)); // 深藍背景
pDC->SetTextColor(RGB(240, 240, 240)); // 白字
}
else // 滑鼠進入態
{
brush.CreateSolidBrush(HLS_TRANSFORM(crBorder, +80, -66)); // 淺藍背景
pDC->SetTextColor(::GetSysColor(COLOR_BTNTEXT)); // 黑字
}
}
// --- 繪製流程 ---
pDC->SelectObject(&brush); pDC->SelectObject(&pen);
pDC->Rectangle(rc); // 畫圓角矩形 (此處為直角,可改 RoundRect)
pDC->SetBkMode(TRANSPARENT);
pDC->DrawText(strText, rc, DT_SINGLELINE | DT_CENTER | DT_VCENTER);
// 焦點虛線框
if (state & ODS_FOCUS) { rc.DeflateRect(3, 3); pDC->DrawFocusRect(rc); }
// 還原
pDC->SelectObject(pOldPen); pDC->SelectObject(pOldBrush); pDC->SelectObject(hOldFont);
}
HLS_TRANSFORM代碼中使用的 HLS_TRANSFORM(color, hueShift, lumShift, satShift) 為自定義宏/函數,將 RGB 轉 HLS 調整後再轉回 RGB,用於動態生成按下/懸停態顏色。若無此函數,可直接指定具體 RGB 值 (如按下態 RGB(0, 0, 180)),略失動態適應主題能力。
stdafx.h 或 .cpp 頂部加入:#include 與 extern "C" WINUSERAPI BOOL WINAPI TrackMouseEvent(LPTRACKMOUSEEVENT);。TrackMouseEvent 在 Win98/NT4 需安裝 IE4+ 或單獨更新 DLL,Win2000+ 內建。TME_HOVER 旗標與 WM_MOUSEHOVER 處理,實現延遲進入效果。RoundRect 或 GraphicsPath 繪製真圓角矩形。DrawState 或 CImageList::Draw)。CButton, CBitmapButton 等基類。這個 CButtonXp 雖簡潔,卻包含了自繪按鈕的所有核心要素:狀態機、滑鼠追蹤、系統色彩適應、GDI 繪圖流程,是學習 MFC 自繪控制項的極佳入門範例。