網路上雖有現成的 XP 風格按鈕類別庫,但自己動手實作才能真正掌握核心技術。本文詳細記錄從 CButton 衍生 CButtonXp 的完整過程,實現含滑鼠熱追蹤、按下效果、焦點框、停用狀態的完整按鈕行為。
void CButtonXp::PreSubclassWindow() {
CButton::PreSubclassWindow();
ModifyStyle(0, BS_OWNERDRAW); // 關鍵:啟用 Owner Draw
}
使用 TrackMouseEvent() API 實現滑鼠進入/離開偵測:
void CButtonXp::OnMouseMove(UINT nFlags, CPoint point) {
if (m_bOver == FALSE) {
m_bOver = TRUE;
InvalidateRect(NULL, FALSE); // 觸發重繪
TRACKMOUSEEVENT tme;
tme.cbSize = sizeof(TRACKMOUSEEVENT);
tme.dwFlags = TME_LEAVE;
tme.dwHoverTime = 0;
tme.hwndTrack = m_hWnd;
::TrackMouseEvent(&tme); // 註冊 WM_MOUSELEAVE
}
CButton::OnMouseMove(nFlags, point);
}
// WM_MOUSELEAVE 處理
void CButtonXp::OnMouseOut() {
m_bOver = FALSE;
InvalidateRect(NULL, FALSE);
}
需在訊息對應表加入:ON_MESSAGE(WM_MOUSELEAVE, OnMouseOut)
BOOL CButtonXp::MouseOver() { return m_bOver; }
void CButtonXp::DrawItem(LPDRAWITEMSTRUCT lpDIS) {
CDC *pDC = CDC::FromHandle(lpDIS->hDC);
CRect rtControl(lpDIS->rcItem);
CPen pen; CBrush brush; CString strText;
HFONT hOldFont = (HFONT)pDC->SelectObject((HFONT)::GetStockObject(DEFAULT_GUI_FONT));
UINT state = lpDIS->itemState;
if (state & ODS_FOCUS) rtControl.DeflateRect(1,1); // 焦點時內縮
// 狀態判斷與顏色設定
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));
} 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));
}
}
// 文字顏色微調
if (state & ODS_DISABLED) pDC->SetTextColor(::GetSysColor(COLOR_GRAYTEXT));
else if (state & ODS_SELECTED) pDC->SetTextColor(RGB(240,240,240));
else if (MouseOver()) pDC->SetTextColor(0);
else pDC->SetTextColor(::GetSysColor(COLOR_BTNTEXT));
// 實際繪製
CBrush* old_brush = pDC->SelectObject(&brush);
CPen* old_pen = pDC->SelectObject(&pen);
pDC->Rectangle(rtControl);
pDC->SetBkMode(TRANSPARENT);
GetWindowText(strText);
pDC->DrawText(strText, rtControl, DT_SINGLELINE|DT_CENTER|DT_VCENTER);
// 焦點虛線框
if (state & ODS_FOCUS) {
rtControl.DeflateRect(3,3);
pDC->DrawFocusRect(rtControl);
}
// 還原 GDI 物件
pDC->SelectObject(old_pen);
pDC->SelectObject(old_brush);
pDC->SelectObject(hOldFont);
}
#include <winuser.h>
extern "C" WINUSERAPI BOOL WINAPI TrackMouseEvent(LPTRACKMOUSEEVENT lpEventTrack);
Windows 98 + Visual C++ 6.0 環境下編譯通過。執行效果展示 Normal、Over、Down、Focus、Disable 五種狀態完整切換,視覺貼近 Windows XP 原生風格。
CButtonXp 展示了自繪按鈕的最小完整實現,程式碼精簡易懂,適合作為客製化按鈕開發的基礎範本。原始碼可直接整合專案使用。