首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> VC >> VC 教學

VC++ Win32 API 實現自繪按鈕:子類化 + Thunk 技術

     編輯:VC 教學

為何不用 MFC/WTL?

純 Win32 API 程式體積小、無執行階段依賴、適合驅動級/嵌入式/安全軟體。自繪按鈕核心在於子類化按鈕視窗程序,攔截 WM_PAINT、WM_MOUSEMOVE、WM_LBUTTONDOWN 等訊息,完全掌控繪製與狀態機。

核心架構

class CWINButton {
    HWND m_hBtn;          // 按鈕句柄
    HWND m_hParent;       // 父視窗
    WNDPROC m_oldProc;    // 原視窗程序
    WNDPROC m_thunk;      // Thunk 存根 (實現 this 呼叫)
    // 狀態旗標
    BOOL m_bMouseOver, m_bPressed, m_bFocused, m_bEnabled;
    // 外觀資源
    HBITMAP m_bmpNormal, m_bmpHover, m_bmpPressed, m_bmpDisabled;
    HICON   m_hIcon;
    HFONT   m_hFont;
    COLORREF m_clrText;
    // Tooltip
    HWND m_hTip;
    TOOLINFO m_ti;
public:
    BOOL Init(HWND hBtn);          // 子類化進入點
    BOOL Restore();                // 還原原視窗程序
    void SetBitmaps(HBMP normal, HBMP hover, HBMP pressed, HBMP disabled);
    void SetIcon(HICON); void SetFont(HFONT); void SetTextColor(COLORREF);
    void SetTooltip(LPCTSTR);
    // 靜態視窗程序 -> Thunk -> 實例成員函數
    static LRESULT CALLBACK StaticProc(HWND, UINT, WPARAM, LPARAM);
    LRESULT InstanceProc(UINT, WPARAM, LPARAM);
};

Thunk 技術實現 this 傳遞

靜態視窗程序無法直接存取實例成員。Thunk 是一小段機器碼,將 HWND 映射到 CWINButton* (透過 SetProp/GetProp 或 SetWindowLongPtr GWLP_USERDATA),並調整 this 指標跳轉到成員函數。

// 簡易版:使用 GWLP_USERDATA (僅支援單實例/單執行緒)
BOOL CWINButton::Init(HWND hBtn)
{
    m_hBtn = hBtn; m_hParent = GetParent(hBtn);
    SetWindowLongPtr(hBtn, GWLP_USERDATA, (LONG_PTR)this);
    m_oldProc = (WNDPROC)SetWindowLongPtr(hBtn, GWLP_WNDPROC, (LONG_PTR)StaticProc);
    return TRUE;
}

LRESULT CALLBACK CWINButton::StaticProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp)
{
    CWINButton* pThis = (CWINButton*)GetWindowLongPtr(hWnd, GWLP_USERDATA);
    return pThis ? pThis->InstanceProc(msg, wp, lp) : DefWindowProc(hWnd, msg, wp, lp);
}

多實例/多執行緒安全版:使用 SetProp(hWnd, _T("CWINButton"), this) + 動態生成 Thunk 代碼 (x86: MOV ECX, pThis; JMP InstanceProc),參考 ATL::_AtlCreateWndProcThunk。

關鍵訊息處理

訊息處理重點
WM_PAINT雙緩衝繪製:背景 → 邊框 → 圖示/文字 → 焦點虛線框。根據狀態旗標選擇位圖/顏色。
WM_MOUSEMOVETrackMouseEvent(TME_LEAVE) 追蹤離開,設定 m_bMouseOver,InvalidateRect 重繪。
WM_MOUSELEAVE清除 m_bMouseOver,重繪。
WM_LBUTTONDOWN/UP設定/清除 m_bPressed,SetCapture/ReleaseCapture,釋放時發送 BN_CLICKED 給父視窗。
WM_SETFOCUS/KILLFOCUS更新 m_bFocused,重繪焦點框。
WM_ENABLE同步 m_bEnabled,重繪灰態。
WM_DESTROY還原 m_oldProc,釋放 GDI 物件,RemoveProp。

MFC 專案中使用注意

MFC 會在父視窗處理 WM_DRAWITEM 繪製子控件。若父視窗為 CDialog,需在 OnDrawItem 中直接返回,不呼叫 Default(),否則雙重繪製導致關閉時 Crash。

void CMyDialog::OnDrawItem(int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct)
{
    // 若控件已被 CWINButton 子類化,這裡不做任何事
    // CDialog::OnDrawItem(nIDCtl, lpDrawItemStruct); // 註釋掉!
}
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved