拒絕 MFC 膨脹,回歸 Win32 API 核心。本文分享一個純 API 實作的自繪按鈕類別 CWINButton,封裝子類化、Thunk 技術、多狀態繪製 (正常/游標懸停/按下/禁用)、圖示/文字/提示/不規則區域/拖曳移動等功能,編譯為動態連結庫 (DLL) 供 MFC/非 MFC 專案共用。
Win32 視窗程序必須是靜態/全域函式,無法直接指向類別成員函式。採用 Thunk 機制 (或 SetProp/GetProp) 將 this 指標與視窗句柄關聯,靜態回調函式內取得 this 再轉呼叫實例成員函式。
// 類別宣告節錄
class DLLPORT CWINButton
{
public:
BOOL GetItemhWnd(HWND hWnd); // 初始化:子類化目標按鈕
BOOL Restore(); // 還原原始視窗程序
BOOL SetDrag(BOOL bEnable); // 啟用拖曳移動
BOOL SetIcon(HICON hIcon);
BOOL SetText(LPCTSTR pszText, HFONT hFont = NULL);
BOOL SetBackPic(HBITMAP hBmp, BOOL bResize); // 背景圖 (可依圖調整按鈕大小)
BOOL SetHoverPic(HBITMAP hBmp); // 懸停狀態圖
BOOL SetPressPic(HBITMAP hBmp); // 按下狀態圖
BOOL SetDisablePic(HBITMAP hBmp); // 禁用狀態圖
BOOL SetToolTip(LPCTSTR pszText);
BOOL SetupRegion(COLORREF clrTransparent); // 不規則區域 (透明色)
// ...
private:
static LRESULT WINAPI StdProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); // 靜態回調入口
LRESULT CALLBACK WndProc(UINT uMsg, WPARAM wParam, LPARAM lParam); // 實例視窗程序
WNDPROC CreateThunk(); // 建立 Thunk 將 this 綁定到 StdProc
// 繪圖輔助
BOOL DrawFlat(HDC hdc, RECT* prc);
BOOL DrawInsideBorder(HDC hdc, RECT* prc);
// 狀態旗標
BOOL m_bMouseTracking, m_bPress, m_bFocus, m_bEnable, m_bOwnerDraw, m_bDrag, m_bDragEnable;
// 視覺資源
HICON m_hIcon; HBITMAP m_hBack, m_hHover, m_hPress, m_hDisable;
HFONT m_hFont; COLORREF m_clrText;
// ...
};
BOOL CWINButton::GetItemhWnd(HWND hWnd)
{
m_hWnd = hWnd;
m_hWndParent = GetParent(hWnd);
// 將 this 存入視窗屬性
if (SetProp(m_hWnd, _T("CWINBUTTON"), (HANDLE)this) == 0)
return FALSE;
// 替換視窗程序為靜態 StdProc
m_OldProc = SetWindowLong(m_hWnd, GWL_WNDPROC, (LONG)StdProc);
return TRUE;
}
LRESULT WINAPI CWINButton::StdProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
CWINButton* pThis = (CWINButton*)GetProp(hWnd, _T("CWINBUTTON"));
if (pThis) return pThis->WndProc(uMsg, wParam, lParam);
return DefWindowProc(hWnd, uMsg, wParam, lParam);
}
所有視覺呈現集中在實例 WndProc 的 WM_PAINT 處理:
HDC (BeginPaint 或 WM_PRINTCLIENT 傳入)。m_bPress, m_bMouseTracking, m_bFocus, m_bEnable) 選擇背景圖 (m_hBack/m_hHover/m_hPress/m_hDisable) 或純色填充。DrawIconEx) 與文字 (DrawText / TextOut),支援置中/偏移排版。m_bOwnerDraw (使用者設定圖片),依圖片透明色建立不規則區域 (SetupRegion)。| 訊息 | 處理目的 |
|---|---|
WM_MOUSEMOVE | TrackMouseEvent(TME_LEAVE) 追蹤游標離開;設 m_bMouseTracking=TRUE,Invalidate() 重繪懸停狀態。 |
WM_MOUSELEAVE | 清 m_bMouseTracking,重繪恢復正常狀態。 |
WM_LBUTTONDOWN | 設 m_bPress=TRUE,SetCapture(),重繪按下狀態;若啟用拖曳 (m_bDragEnable) 記錄起始座標。 |
WM_LBUTTONUP | 清 m_bPress,ReleaseCapture();若在拖曳模式計算位移 MoveWindow,否則發送 BN_CLICKED 通知父視窗。 |
WM_SETFOCUS / WM_KILLFOCUS | 設/清 m_bFocus,重繪焦點虛線框。 |
WM_ENABLE | 同步 m_bEnable,重繪禁用狀態。 |
WM_KEYDOWN (VK_SPACE) | 鍵盤空白鍵模擬點擊,觸發 BM_CLICK。 |
DLL 版本按鈕類別會自行處理 WM_PAINT,不需 父視窗處理 WM_DRAWITEM。若父視窗為 MFC CDialog/CWnd,請勿在 OnDrawItem 中呼叫基類實作,直接回傳 TRUE 或完全不處理:
void CMyDialog::OnDrawItem(int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct)
{
// 不要呼叫 CDialog::OnDrawItem!
// 我們的按鈕已在子類化中自行繪製
}
否則程式關閉時可能觸發非法操作 (雙重銷毀/重繪衝突)。
CWINButton.dll + .lib,專案只需包含標頭檔連結即可。完整原始碼 (含 Thunk 組合語言實作 x86/x64) 可從 VC 知識庫下載。歡迎技術交流。