透過 BS_OWNERDRAW 樣式與 CButton::DrawItem 覆寫,可完全自繪按鈕外觀。本文實作 **3D 立體文字按鈕**:陰影/高光多層疊加模擬凸起效果、自動縮放字體適應按鈕大小、支援按下/放開狀態視覺回饋。
Test。IDC_3DTEXTBTN,Caption: 誰與爭瘋。CButton。C3dTextButton。
void C3dTextButton::Draw(CDC* pDC, const CRect& rect, UINT state)
{
CString text; GetWindowText(text);
int len = text.GetLength();
CRect rcClient = rect;
// 1. 取得字體並建立 LOGFONT
CFont* pFont = GetFont();
LOGFONT lf; pFont->GetObject(sizeof(LOGFONT), &lf);
if (lf.lfHeight == 0) lf.lfHeight = 20;
lf.lfWidth = 0; lf.lfWeight = 1000; // 粗體
lf.lfEscapement = lf.lfOrientation = 0;
CFont tryFont; tryFont.CreateFontIndirect(&lf);
CFont* pOldFont = pDC->SelectObject(&tryFont);
// 2. 自動縮放字體高度適應按鈕
CSize sz = pDC->GetTextExtent(text, len);
if (rcClient.Width() * sz.cy > rcClient.Height() * sz.cx) {
lf.lfHeight = MulDiv(lf.lfHeight, rcClient.Height(), sz.cy);
} else {
lf.lfHeight = MulDiv(lf.lfHeight, rcClient.Width(), sz.cx);
}
CFont font; font.CreateFontIndirect(&lf);
pDC->SelectObject(&font);
sz = pDC->GetTextExtent(text, len);
// 3. 計算文字置中位置
int minX = rcClient.left + (rcClient.Width() - sz.cx) / 2;
int minY = rcClient.top + (rcClient.Height() - sz.cy) / 2;
int oldBkMode = pDC->SetBkMode(TRANSPARENT);
COLORREF oldTextColor = pDC->SetTextColor(::GetSysColor(COLOR_BTNTEXT));
int cx = minX, cy = minY;
int s = (state & ODS_SELECTED) ? -1 : +1; // 按下時陰影反向
cx += 3; cy += 3; // 基準偏移
// 4. 繪製 3D 效果:多層偏移文字疊加
// 暗影層 (最外層)
pDC->SetTextColor(::GetSysColor(COLOR_3DDKSHADOW));
pDC->TextOut(cx - s*2, cy + s*2, text);
pDC->TextOut(cx + s*2, cy - s*2, text);
pDC->TextOut(cx + s*2, cy + s*2, text);
// 高光層
pDC->SetTextColor(::GetSysColor(COLOR_3DHILIGHT));
pDC->TextOut(cx + s*1, cy - s*2, text);
pDC->TextOut(cx - s*2, cy + s*1, text);
pDC->TextOut(cx - s*2, cy - s*2, text);
// 陰影層
pDC->SetTextColor(::GetSysColor(COLOR_3DSHADOW));
pDC->TextOut(cx - s*1, cy + s*1, text);
pDC->TextOut(cx + s*1, cy - s*1, text);
pDC->TextOut(cx + s*1, cy + s*1, text);
// 邊緣高光
pDC->SetTextColor(::GetSysColor(COLOR_3DLIGHT));
pDC->TextOut(cx, cy - s*1, text);
pDC->TextOut(cx - s*1, cy, text);
pDC->TextOut(cx - s*1, cy - s*1, text);
// 5. 繪製主文字
pDC->SetTextColor(::GetSysColor(COLOR_BTNTEXT));
pDC->TextOut(cx, cy, text);
// 6. 還原 DC 狀態
pDC->SetTextColor(oldTextColor);
pDC->SetBkMode(oldBkMode);
pDC->SelectObject(pOldFont);
}
void C3dTextButton::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct)
{
CDC* pDC = CDC::FromHandle(lpDrawItemStruct->hDC);
ASSERT_VALID(pDC);
CRect rcClient = lpDrawItemStruct->rcItem;
Draw(pDC, rcClient, lpDrawItemStruct->itemState);
}
IDC_3DTEXTBTN → Add Variable → Type: C3dTextButton, Name: m_3dTextButton1。#include "3dTextButton.h"。| 技術點 | 說明 |
|---|---|
| BS_OWNERDRAW | 告知系統不繪製標準按鈕,改由 DrawItem 自繪。 |
| DrawItem 參數 | rcItem 為按鈕矩形,itemState 包含 ODS_SELECTED (按下)、ODS_FOCUS 等。 |
| 系統色彩 | 使用 GetSysColor(COLOR_3D...) 自動適應系統主題 (經典/視覺風格/高對比度)。 |
| 字體自動縮放 | MulDiv 依按鈕寬高比例調整 lfHeight,確保文字填滿不溢出。 |
| 3D 疊加原理 | 以基準位置 (cx,cy) 為中心,按下狀態 s=-1 反向偏移,疊加 4 組系統色彩 (暗影/高光/陰影/邊緣) 模擬立體感。 |
| DC 狀態還原 | 儲存/還原 BkMode, TextColor, Font,避免污染後續繪圖。 |
GraphicsPath + LinearGradientBrush 繪製真正向量 3D 文字,支援抗鋸齒、透明度、縮放不失真。CPaintDC 而非 DrawItem (需 BS_OWNERDRAW 配合 WM_PRINTCLIENT),獲得更完整繪圖控制。GetSysColor 色彩正確,但像素座標需配合 DPI 感知縮放。OwnerDraw 按鈕給予完全繪圖控制權。本文實作的 3D 文字按鈕,結合**系統色彩主題適應**、**自動字體縮放**、**按下狀態視覺反饋**、**多層文字疊加模擬立體**,展示了 GDI 繪圖的經典技巧。現代專案建議遷移至 GDI+/Direct2D 獲得更佳視覺品質與高 DPI 支援。