Windows 提供 Owner-Draw 機制讓控件自管外觀。WTL COwnerDraw<T> 模板支援自繪操作,CBitmapButtonImpl<T> 提供位圖按鈕、ToolTip、文字疊加等功能。透過多重繼承融合兩者:
class COwnerDrawButton : public CBitmapButtonImpl<COwnerDrawButton>,
public COwnerDraw<COwnerDrawButton>
{
// 同時擁有自繪能力與位圖按鈕特性
};
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。
WM_DRAWITEM 給父視窗。REFLECT_NOTIFICATIONS() (必須置於映射表末尾),將訊息反射回按鈕。OCM_DRAWITEM 對應 OnDrawItem。OnDrawItem 靜態轉型呼叫派生類 DrawItem(LPDRAWITEMSTRUCT) 完成實際繪製。COwnerDraw 不實作 DrawItem,派生類必須覆寫,否則觸發 ATL Assert。
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;
}
OwnerDrawDemo。IDC_BUTTON1 (Caption: Help),匯入位圖 IDB_BUTTON、遮罩 IDB_MASK。MainDlg.h 加入 COwnerDrawButton m_Button。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!"));
本文深入剖析 WTL 自繪按鈕實作核心:訊息分割解決多重繼承分派、訊息反射實現 OO 封裝、屏蔽基類預設繪製避免衝突、擴充圖片位置與資源載入。掌握此模式可快速打造專業級自訂外觀按鈕。