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

VC++ 多功能標籤控制項 CLabelEx:圖片背景、滑鼠感應、超連結效果、自動下劃線

     編輯:VC 教學

前言

標準 `CStatic` 功能單薄,無法滿足現代介面對標籤的豐富視覺需求。本文實作 `CLabelEx`,繼承 `CStatic`,整合三態背景圖片(正常/滑鼠懸停/點擊按下)、三態圖示標籤、文字顏色/下劃線/自動超連結效果(懸停變藍+下劃線)、可選邊框與背景填充。核心在於 `OnPaint` 手工繪製所有視覺層、`OnMouseMove` 配合 `SetCapture/ReleaseCapture` 實現懸停偵測。

一、類別屬性與狀態


class CLabelEx : public CStatic
{
    DECLARE_DYNAMIC(CLabelEx)
public:
    CLabelEx();
    virtual ~CLabelEx();

    // 背景圖片
    void SetBGBitmap(UINT nIDResource);
    void SetMouseOverBGBitmap(UINT nIDResource);
    void SetClickedBGBitmap(UINT nIDResource);

    // 標籤圖示(文字左側)
    void SetLabelBitmap(UINT nIDResource);
    void SetMouseOverLabelBitmap(UINT nIDResource);
    void SetClickedLabelBitmap(UINT nIDResource);

    // 文字樣式
    void SetTextColor(COLORREF clr);
    void SetUnderLine(BOOL bUnderline, COLORREF clrLine = RGB(0,0,255));
    void SetAutoUnderLine(BOOL bEnable); // 懸停自動加下劃線
    void SetHighLight(BOOL bEnable);     // 懸停變藍

    // 邊框與背景
    void SetBorder(BOOL bEnable, COLORREF clrBorder = RGB(128,128,128));
    void SetBkColor(COLORREF clrBk);

protected:
    CBitmap m_bmpBG, m_bmpBGOver, m_bmpBGClicked;
    CBitmap m_bmpLabel, m_bmpLabelOver, m_bmpLabelClicked;
    COLORREF m_crText, m_crUnderLine, m_crBackText, m_crBorder, m_crBk;
    BOOL m_bBorder, m_bUnderLine, m_bAutoUnderLine, m_bHighLight;
    BOOL m_bOver, m_bClicked;
    HCURSOR m_hHandCursor;

    afx_msg void OnPaint();
    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 BOOL OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message);
    DECLARE_MESSAGE_MAP()
};

二、OnPaint 完整繪製流程


void CLabelEx::OnPaint()
{
    CPaintDC dc(this);
    CRect rc; GetClientRect(&rc);

    CDC memDC; memDC.CreateCompatibleDC(&dc);
    CFont* pOldFont = dc.SelectObject(GetFont());
    dc.SetBkMode(TRANSPARENT);
    dc.SetTextColor(m_crText);

    CPen penBorder, *pOldPen = NULL;
    CBrush brushBk, *pOldBrush = NULL;
    BITMAP bm;
    int nTextLeft = 4, nTextTop = 0;

    // 1. 邊框與背景填充
    if (m_bBorder)
    {
        penBorder.CreatePen(PS_SOLID, 1, m_crBorder);
        brushBk.CreateSolidBrush(m_crBk);
        pOldPen = dc.SelectObject(&penBorder);
        pOldBrush = dc.SelectObject(&brushBk);
        dc.Rectangle(&rc);
        dc.SelectObject(pOldPen); dc.SelectObject(pOldBrush);
        rc.DeflateRect(1,1);
    }

    // 2. 貼背景圖片(優先級:Clicked > Over > Normal)
    CBitmap* pBgBmp = NULL;
    if (m_bClicked && m_bmpBGClicked.GetSafeHandle()) pBgBmp = &m_bmpBGClicked;
    else if (m_bOver && m_bmpBGOver.GetSafeHandle()) pBgBmp = &m_bmpBGOver;
    else if (m_bmpBG.GetSafeHandle()) pBgBmp = &m_bmpBG;

    if (pBgBmp)
    {
        memDC.SelectObject(pBgBmp);
        pBgBmp->GetBitmap(&bm);
        dc.StretchBlt(rc.left, rc.top, rc.Width(), rc.Height(), &memDC, 0, 0, bm.bmWidth, bm.bmHeight, SRCCOPY);
    }

    // 3. 貼標籤圖示(左側),計算文字左邊界
    CBitmap* pLabelBmp = NULL;
    if (m_bClicked && m_bmpLabelClicked.GetSafeHandle()) pLabelBmp = &m_bmpLabelClicked;
    else if (m_bOver && m_bmpLabelOver.GetSafeHandle()) pLabelBmp = &m_bmpLabelOver;
    else if (m_bmpLabel.GetSafeHandle()) pLabelBmp = &m_bmpLabel;

    if (pLabelBmp)
    {
        pLabelBmp->GetBitmap(&bm);
        double fScale = (double)bm.bmWidth / bm.bmHeight;
        int nLabelW = (int)(rc.Height() * fScale);
        memDC.SelectObject(pLabelBmp);
        dc.StretchBlt(rc.left, rc.top, nLabelW, rc.Height(), &memDC, 0, 0, bm.bmWidth, bm.bmHeight, SRCCOPY);
        nTextLeft = rc.left + nLabelW + 4;
    }

    // 4. 輸出文字(垂直置中)
    CString str; GetWindowText(str);
    TEXTMETRIC tm; dc.GetTextMetrics(&tm);
    nTextTop = rc.top + (rc.Height() - tm.tmHeight) / 2;
    if (!str.IsEmpty()) dc.TextOut(nTextLeft, nTextTop, str);

    // 5. 畫下劃線
    if (m_bUnderLine && !str.IsEmpty())
    {
        CPen penLine(PS_SOLID, 1, m_crUnderLine);
        CPen* pOld = dc.SelectObject(&penLine);
        int nWidth = tm.tmAveCharWidth * str.GetLength();
        dc.MoveTo(nTextLeft - 2, nTextTop + tm.tmHeight + 1);
        dc.LineTo(nTextLeft - 2 + nWidth, nTextTop + tm.tmHeight + 1);
        dc.SelectObject(pOld);
    }

    dc.SelectObject(pOldFont);
}

三、滑鼠感應與狀態機


void CLabelEx::OnMouseMove(UINT nFlags, CPoint point)
{
    CRect rc; GetClientRect(&rc);
    if (m_bOver)
    {
        // 滑鼠移出判斷
        if (!rc.PtInRect(point))
        {
            m_bOver = FALSE;
            if (m_bAutoUnderLine) SetUnderLine(FALSE);
            if (m_bHighLight) { m_crText = m_crBackText; Invalidate(); }
            ReleaseCapture();
        }
    }
    else
    {
        // 滑鼠移入
        m_bOver = TRUE;
        if (m_bAutoUnderLine) SetUnderLine(TRUE, RGB(0,0,255));
        if (m_bHighLight) { m_crBackText = m_crText; m_crText = RGB(0,0,255); Invalidate(); }
        SetCapture();
        ::SetCursor(m_hHandCursor ? m_hHandCursor : AfxGetApp()->LoadStandardCursor(IDC_HAND));
    }
    CStatic::OnMouseMove(nFlags, point);
}

void CLabelEx::OnLButtonDown(UINT nFlags, CPoint point)
{
    m_bClicked = TRUE; Invalidate();
    CStatic::OnLButtonDown(nFlags, point);
}

void CLabelEx::OnLButtonUp(UINT nFlags, CPoint point)
{
    m_bClicked = FALSE; Invalidate();
    // 這裡可發送自訂通知給父視窗:GetParent()->SendMessage(WM_LABEL_CLICK, GetDlgCtrlID());
    CStatic::OnLButtonUp(nFlags, point);
}

BOOL CLabelEx::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message)
{
    if (m_bOver) { ::SetCursor(m_hHandCursor); return TRUE; }
    return CStatic::OnSetCursor(pWnd, nHitTest, message);
}

四、已知問題:模態對話框彈出後狀態無法恢復

文中提到:在 `OnLButtonUp` 中若彈出模態對話框(`DoModal()`),滑鼠捕捉被對話框奪走,導致 `m_bOver`/`m_bClicked` 無法正確重置,標籤保持按下狀態。解決方案:

  • 在 `OnLButtonUp` 中改用 `PostMessage` 發送自訂訊息給父視窗,由父視窗彈出對話框,避免在控制項內直接 `DoModal()`。
  • 或
  • 覆寫 `OnCaptureChanged`:當捕捉丟失時強制重置狀態並 `Invalidate()`。

void CLabelEx::OnCaptureChanged(CWnd* pWnd)
{
    if (pWnd != this) // 捕捉被其他視窗取走
    {
        m_bOver = m_bClicked = FALSE;
        if (m_bAutoUnderLine) SetUnderLine(FALSE);
        if (m_bHighLight) m_crText = m_crBackText;
        Invalidate();
    }
    CStatic::OnCaptureChanged(pWnd);
}

五、使用範例


void CMyDialog::OnInitDialog()
{
    CDialog::OnInitDialog();
    m_label.SubclassDlgItem(IDC_STATIC_LABEL, this);
    m_label.SetBGBitmap(IDB_BG_NORMAL);
    m_label.SetMouseOverBGBitmap(IDB_BG_HOVER);
    m_label.SetClickedBGBitmap(IDB_BG_DOWN);
    m_label.SetLabelBitmap(IDB_ICON_NORMAL);
    m_label.SetAutoUnderLine(TRUE);
    m_label.SetHighLight(TRUE);
    m_label.SetTextColor(RGB(0,0,0));
    m_label.SetWindowText(_T("點擊前往官網"));
}

六、結語

`CLabelEx` 以純 GDI 繪製實現豐富視覺狀態,無外部依賴,輕量級適合大量使用。核心設計模式:狀態機驅動重繪 + 捕捉機制偵測懸停 + 位圖拉伸適應 DPI。已知模態對話框捕捉衝突可透過 `OnCaptureChanged` 或訊息轉發解決。建議將此控制項納入共用庫,統一專案中所有標籤、超連結、圖文按鈕的視覺語言。

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