本文介紹 CLinkButton 類別,實現類似超連結的視覺效果:平時外觀如靜態文字,滑鼠懸停時顯示底線與藍色字體並切換手型游標,按下時呈現 3D 邊框下壓效果。完整涵蓋從資源設定到訊息處理的全流程實作。
| 狀態 | 外觀描述 |
|---|---|
| Normal | 純文字,無邊框,類似 CStatic 控制項 |
| Hover | 文字變藍、加底線、游標變手型、扁平按鈕邊框(凸起/下壓) |
| Down | 3D 邊框反轉產生下壓視覺回饋 |
CLinkButton 繼承 CButton// 虛函數重載
virtual void DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct);
// 訊息處理函數
afx_msg void OnMouseMove(UINT nFlags, CPoint point);
afx_msg BOOL OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message);
afx_msg void OnTimer(UINT nIDEvent);
afx_msg void OnLButtonUp(UINT nFlags, CPoint point);
afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
afx_msg BOOL OnEraseBkgnd(CDC* pDC);
CFont fUnderline; // 底線字型
HCURSOR hHand; // 手型游標
bool bLBtnDown; // 按下狀態旗標
bool bHighlight; // 滑鼠懸停旗標
void CLinkButton::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct) {
CDC* pDC = CDC::FromHandle(lpDrawItemStruct->hDC);
CRect rect(lpDrawItemStruct->rcItem);
COLORREF oc = pDC->GetTextColor();
int iObk = pDC->SetBkMode(TRANSPARENT);
UINT state = lpDrawItemStruct->itemState;
CFont *pOldFont = NULL;
CString strText; GetWindowText(strText);
if (state & ODS_DISABLED) {
// 停用狀態:灰顯陰影效果
CBrush grayBrush; grayBrush.CreateSolidBrush(GetSysColor(COLOR_GRAYTEXT));
CSize sz = pDC->GetTextExtent(strText);
int x = rect.left + (rect.Width() - sz.cx)/2;
int y = rect.top + (rect.Height() - sz.cy)/2;
rect.OffsetRect(2,2);
pDC->SetTextColor(GetSysColor(COLOR_3DHIGHLIGHT));
pDC->DrawText(strText, rect, DT_CENTER|DT_VCENTER|DT_SINGLELINE);
rect.OffsetRect(-2,-2);
pDC->SetTextColor(GetSysColor(COLOR_GRAYTEXT));
pDC->DrawText(strText, rect, DT_CENTER|DT_VCENTER|DT_SINGLELINE);
} else {
if (bHighlight) {
// 滑鼠懸停:繪製扁平邊框
if (state & ODS_SELECTED)
pDC->Draw3dRect(rect, GetSysColor(COLOR_3DSHADOW), GetSysColor(COLOR_3DHILIGHT));
else
pDC->Draw3dRect(rect, GetSysColor(COLOR_3DHILIGHT), GetSysColor(COLOR_3DSHADOW));
pDC->SetTextColor(RGB(0,0,255)); // 藍色文字
// 動態建立底線字型
if (fUnderline.GetSafeHandle() == NULL) {
CFont* pFont = GetFont(); LOGFONT lf;
pFont->GetLogFont(&lf); lf.lfUnderline = TRUE;
fUnderline.CreateFontIndirect(&lf);
}
pOldFont = pDC->SelectObject(&fUnderline);
} else {
pDC->SetTextColor(GetSysColor(COLOR_BTNTEXT));
}
pDC->DrawText(strText, rect, DT_CENTER|DT_VCENTER|DT_SINGLELINE);
if (pOldFont) pDC->SelectObject(pOldFont);
}
}
void CLinkButton::OnMouseMove(UINT nFlags, CPoint point) {
SetTimer(1, 10, NULL); // 10ms 定時器輪詢滑鼠位置
CButton::OnMouseMove(nFlags, point);
}
void CLinkButton::OnTimer(UINT nIDEvent) {
static bool pPainted = false;
POINT pt; GetCursorPos(&pt);
CRect rect; GetWindowRect(rect);
if (bLBtnDown) { KillTimer(1); if(pPainted) InvalidateRect(NULL); pPainted=false; return; }
if (!rect.PtInRect(pt)) {
bHighlight = false; KillTimer(1);
if (pPainted) InvalidateRect(NULL); pPainted = false; return;
} else {
bHighlight = true;
if (!pPainted) { pPainted = true; InvalidateRect(NULL); }
}
CButton::OnTimer(nIDEvent);
}
BOOL CLinkButton::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message) {
if (bHighlight) { ::SetCursor(hHand); return true; }
return CButton::OnSetCursor(pWnd, nHitTest, message);
}
void CLinkButton::OnLButtonDown(UINT nFlags, CPoint point) {
bLBtnDown = true; CButton::OnLButtonDown(nFlags, point);
}
void CLinkButton::OnLButtonUp(UINT nFlags, CPoint point) {
bLBtnDown = false;
if (bHighlight) { bHighlight = false; InvalidateRect(NULL); }
CButton::OnLButtonUp(nFlags, point);
}
int CLinkButton::OnCreate(LPCREATESTRUCT lpCreateStruct) {
if (CButton::OnCreate(lpCreateStruct) == -1) return -1;
CFont* pFont = GetFont(); LOGFONT lf;
pFont->GetLogFont(&lf); lf.lfUnderline = TRUE;
fUnderline.CreateFontIndirect(&lf); return 0;
}
BOOL CLinkButton::OnEraseBkgnd(CDC* pDC) {
// 繪製略深於系統色的背景,避免閃爍
COLORREF cr = GetSysColor(COLOR_3DFACE);
int r=GetRValue(cr), g=GetGValue(cr), b=GetBValue(cr);
if(r>1) r-=2; if(g>1) g-=2; if(r<3&&g<3&&b<253) b+=2;
CRect rc; GetClientRect(rc);
pDC->FillSolidRect(rc, RGB(r,g,b));
return CButton::OnEraseBkgnd(pDC);
}
CLinkButton 結合 Owner Draw、Timer 輪詢、動態字型建立、系統色彩整合 等技術,實現了輕量級的超連結風格按鈕。程式碼結構清晰,無外部相依,可直接整合專案使用。