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

MFC 類庫擴展實戰:打造專屬界面控件庫 (按鈕篇)

     編輯:VC++

前言:為什麼要擴展 MFC 控件?

標準 MFC 控件 (如 CButton) 功能基礎,外觀固定。現代應用需要扁平化、圖標化、自繪、狀態感知等高級 UI 效果。通過繼承 MFC 類層次結構,利用「自繪」機制,我們可以構建功能豐富、外觀可定制的專屬控件庫。

MFC 按鈕類繼承體系

CObject
 └─ CCmdTarget
      └─ CWnd
           └─ CButton        // 標準按鈕、複選框、單選框
                └─ CBitmapButton // 位圖按鈕 (支持上/下/聚焦/禁用四態)

核心技術:Owner-Draw (自繪) 機制

設置按鈕風格為 BS_OWNERDRAW,系統將繪製責任轉交給應用程序。MFC 通過虛函數 DrawItem 實現:

virtual void DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct);

父窗口收到 WM_DRAWITEM 消息後,MFC 框架分發調用該控件的 DrawItem。所有繪製所需信息封裝在 DRAWITEMSTRUCT 中。

DRAWITEMSTRUCT 結構深度解析

成員類型含義
CtlTypeUINT控件類型:ODT_BUTTON, ODT_LISTBOX, ODT_COMBOBOX, ODT_MENU...
CtlIDUINT控件 ID (菜單項無效)
itemIDUINT菜單項 ID 或列表/組合框索引
itemActionUINT繪製動作:ODA_DRAWENTIRE(全繪), ODA_FOCUS(焦點), ODA_SELECT(選中)
itemStateUINT狀態標志:ODS_SELECTED, ODS_FOCUS, ODS_DISABLED, ODS_CHECKED...
hwndItemHWND控件窗口句柄 (菜單為 HMENU)
hDCHDC繪製設備上下文
rcItemRECT控件矩形區域
itemDataDWORD應用程序自定義數據 (如 SetItemData 設置)

CButton 關鍵成員函數速查表

函數用途關鍵參數/返回值
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)、勾選狀態

實戰:構建扁平化圖標按鈕類 CFlatIconButton

1. 類聲明

class 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()
};

2. 狀態跟蹤與消息處理

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);
}

3. 核心繪製邏輯 DrawItem

void 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) 繪製系統原生風格
  • DPI 適配: 使用 GetDpiForWindow 縮放圖標/邊距,支持 Per-Monitor DPI
  • 動畫效果: 集成 CAnimationController 實現狀態切換過渡動畫
  • 模板化: 將繪製邏輯提取為 Policy 類,實現 CButtonT 靜態多態

總結

掌握 BS_OWNERDRAW + DrawItem + DRAWITEMSTRUCT 這套 MFC 自繪核心機制,結合鼠標/鍵盤消息跟蹤狀態,即可構建任意外觀、行為的專業級按鈕控件。這正是從「調用框架」進化為「擴展框架」的關鍵一步。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved