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

WTL 自繪按鈕實作全解析:結合 COwnerDraw 與 CBitmapButtonImpl

     編輯:VC 教學

設計思路:多重繼承整合自繪與位圖按鈕功能

Windows 提供 Owner-Draw 機制讓控件自管外觀。WTL COwnerDraw<T> 模板支援自繪操作,CBitmapButtonImpl<T> 提供位圖按鈕、ToolTip、文字疊加等功能。透過多重繼承融合兩者:

class COwnerDrawButton : public CBitmapButtonImpl<COwnerDrawButton>,
                         public COwnerDraw<COwnerDrawButton>
{
    // 同時擁有自繪能力與位圖按鈕特性
};

COwnerDraw 模板核心機制

訊息分割與反射

ATL/WTL 採用 Alternate Message Maps 解決多重繼承訊息分派衝突。COwnerDraw 定義兩個訊息映射區域:


BEGIN_MSG_MAP(COwnerDraw<T>)
    MESSAGE_HANDLER(WM_DRAWITEM, OnDrawItem)   // 區域 0
    ALT_MSG_MAP(1)
    MESSAGE_HANDLER(OCM_DRAWITEM, OnDrawItem)  // 區域 1 (反射訊息)
END_MSG_MAP()

子類透過 CHAIN_MSG_MAP_ALT(COwnerDraw<T>, 1) 連結至區域 1 處理 OCM_DRAWITEM (父視窗反射的 WM_DRAWITEM),其餘訊息連結至 CBitmapButtonImpl。

訊息反射流程

  1. 自繪按鈕發送 WM_DRAWITEM 給父視窗。
  2. 父視窗訊息映射含 REFLECT_NOTIFICATIONS() (必須置於映射表末尾),將訊息反射回按鈕。
  3. 按鈕在區域 1 找到 OCM_DRAWITEM 對應 OnDrawItem。
  4. OnDrawItem 靜態轉型呼叫派生類 DrawItem(LPDRAWITEMSTRUCT) 完成實際繪製。

COwnerDraw 不實作 DrawItem,派生類必須覆寫,否則觸發 ATL Assert。

CBitmapButtonImpl 整合關鍵:屏蔽預設繪製訊息

CBitmapButtonImpl 預設處理 WM_PAINT/WM_PRINTCLIENT/WM_ERASEBKGND 並呼叫 DoPaint。自繪按鈕所有繪製應集中在 DrawItem,因此必須屏蔽這些訊息:


// 在 COwnerDrawButton 訊息映射中複製 CBitmapButtonImpl 映射並刪除:
MESSAGE_HANDLER(WM_ERASEBKGND, OnEraseBackground)
MESSAGE_HANDLER(WM_PAINT, OnPaint)
MESSAGE_HANDLER(WM_PRINTCLIENT, OnPaint)
// 加入:
CHAIN_MSG_MAP_ALT(COwnerDraw<COwnerDrawButton>, 1)

若 m_ImageList 含圖片,在 DrawItem 中呼叫 DoPaint(CDCHandle) 繪製位圖,再自行排版文字。

擴充功能:圖片位置樣式與資源載入

五種圖片位置常數


const UINT IMAGEPOS_TOP = 1;
const UINT IMAGEPOS_BOTTOM = 2;
const UINT IMAGEPOS_LEFT = 3;
const UINT IMAGEPOS_RIGHT = 4;
const UINT IMAGEPOS_CENTER = 5;

生效條件:m_ImageList 有圖、非 BMPBTN_AUTOSIZE、圖片小於客戶區。重載 DoPaint 比較尺寸、依 m_uBmpPosStyle 繪製位圖、裁剪 m_ClientRect 供文字配版。

統一資源載入介面


typedef struct _IMAGELISTINFOSTRUCT {
    int Normal, Pushed, Hover, Disabled; // 狀態對應索引
    int cx, cy;                          // 圖片尺寸
    UINT flags;                          // ILC_COLOR24|ILC_MASK 等
    int cInitial, cGrow;                 // 初始/增長數量
} IMGINFOS;

BOOL LoadImageFromID(UINT IDBitmap, UINT IDMask, const IMGINFOS& imgno) {
    if (!m_ImageList.Create(imgno.cx, imgno.cy, imgno.flags, imgno.cInitial, imgno.cGrow))
        return FALSE;
    CBitmap bmp, mask;
    if (!bmp.LoadBitmap(IDBitmap) || !mask.LoadBitmap(IDMask)) return FALSE;
    if (m_ImageList.Add(&bmp, &mask) == -1) return FALSE;
    SetImages(imgno.Normal, imgno.Pushed, imgno.Hover, imgno.Disabled);
    return TRUE;
}

範例:建立 Owner-Draw 按鈕

  1. 建立 WTL 對話方塊專案 OwnerDrawDemo。
  2. 資源編輯器加入按鈕 IDC_BUTTON1 (Caption: Help),匯入位圖 IDB_BUTTON、遮罩 IDB_MASK。
  3. MainDlg.h 加入 COwnerDrawButton m_Button。
  4. OnInitDialog 初始化:
    
    DWORD style = BMPBTN_AUTO3D_SINGLE | BMPBTN_SHAREIMAGELISTS | BMPBTN_HOVER;
    IMGINFOS imgis = {0, 1, 1, -1, 30, 30, ILC_COLOR24|ILC_MASK, 0, 2};
    if (m_Button.LoadImageFromID(IDB_BUTTON, IDB_MASK, imgis)) {
        m_Button.SetBitmapButtonExtendedStyle(style);
        m_Button.SetBitmapPosStyle(IMAGEPOS_TOP); // 圖上文字下
    }
    m_Button.SubclassWindow(GetDlgItem(IDC_BUTTON1));
    m_Button.SetToolTipText(_T("WTL_OwnerDrawButton!"));
    
  5. 編譯執行 (F7 / Ctrl+F5)。

總結

本文深入剖析 WTL 自繪按鈕實作核心:訊息分割解決多重繼承分派、訊息反射實現 OO 封裝、屏蔽基類預設繪製避免衝突、擴充圖片位置與資源載入。掌握此模式可快速打造專業級自訂外觀按鈕。

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