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

基於 CDialogBar 實現 IE 風格多標籤欄:自繪按鈕、動態寬度調整、命令路由完整解析

     編輯:關於VC++

前言

IE7+ 採用單進程多標籤架構 (Single Process, Multiple Tabs),以 CDialogBar 作為標籤容器、自繪 CButton 實現標籤視覺效果,是 MFC 實現瀏覽器標籤欄的經典方案。本文完整解析佈局調整、自繪邏輯、關閉按鈕熱區、命令訊息路由、標籤與 CChildFrame 映射管理。

架構設計

  • CTabBar : public CDialogBar:標籤欄容器,管理標籤陣列、響應 WM_SIZE 調整佈局
  • CTabButton : public CButton:自繪標籤按鈕,處理 WM_PAINT、滑鼠進入/離開關閉區域、點擊通知
  • TABINFO 結構:雙向映射 CTabButton* ↔ CChildFrame*

關鍵實作細節

1. 動態寬度調整:OnSize

void CTabBar::OnSize(UINT nType, int cx, int cy) {
    CDialogBar::OnSize(nType, cx, cy);
    CRect rcClient; GetClientRect(rcClient);
    int nBarWidth = rcClient.Width();
    int nTabWidth = nBarWidth - 120; // 預留工具列空間
    int nCount = m_ptrArray.GetCount();

    if (nCount * m_nWidth > nTabWidth) {
        // 空間不足:平均分配
        int nAveWidth = nTabWidth * 1.0 / nCount;
        for (int i = 0; i < nCount; i++) {
            TABINFO* pTabInfo = (TABINFO*)m_ptrArray.GetAt(i);
            CRect rcBtn; pTabInfo->pTabButton->GetClientRect(&rcBtn);
            pTabInfo->pTabButton->MoveWindow(
                nAveWidth * i,
                (rcClient.Height() - rcBtn.Height()) / 2,
                nAveWidth, m_nHeight, FALSE);
        }
    } else {
        // 空間充足:固定寬度
        for (int i = 0; i < nCount; i++) {
            TABINFO* pTabInfo = (TABINFO*)m_ptrArray.GetAt(i);
            CRect rcBtn; pTabInfo->pTabButton->GetClientRect(&rcBtn);
            pTabInfo->pTabButton->MoveWindow(
                m_nWidth * i,
                (rcClient.Height() - rcBtn.Height()) / 2,
                m_nWidth, m_nHeight, FALSE);
        }
    }
    Invalidate(); // 觸發重繪
}

2. 標籤按鈕自繪:DrawItem

void CTabButton::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct) {
    ASSERT(lpDrawItemStruct->CtlType == ODT_BUTTON);
    CRect rcItem = lpDrawItemStruct->rcItem;
    HDC hDC = lpDrawItemStruct->hDC;
    CDC dc; dc.Attach(hDC);
    dc.SetBkMode(TRANSPARENT);
    CString strTitle; GetWindowText(strTitle);

    if (m_bSel) { // 選中狀態:三段式背景 (左中右)
        m_bmpBmpBkgnd.Draw(dc.m_hDC, CRect(0,0,10, m_bmpBmpBkgnd.GetHeight()),
            CRect(m_bmpBmpBkgnd.GetWidth()/4*2, 0, m_bmpBmpBkgnd.GetWidth()/4*2+10, m_bmpBmpBkgnd.GetHeight()));
        m_bmpBmpBkgnd.Draw(dc.m_hDC, CRect(4,0,rcItem.right-10, m_bmpBmpBkgnd.GetHeight()),
            CRect(m_bmpBmpBkgnd.GetWidth()/4*2+10, 0, m_bmpBmpBkgnd.GetWidth()/4*3-10, m_bmpBmpBkgnd.GetHeight()));
        m_bmpBmpBkgnd.Draw(dc.m_hDC, CRect(rcItem.right-10,0,rcItem.right, m_bmpBmpBkgnd.GetHeight()),
            CRect(m_bmpBmpBkgnd.GetWidth()/4*3-10, 0, m_bmpBmpBkgnd.GetWidth()/4*3, m_bmpBmpBkgnd.GetHeight()));
        // 圖示 + 白色文字
        // 關閉按鈕熱區圖示 (滑鼠懸停顯示不同狀態)
    } else { // 未選中狀態:單一背景
        // 類似繪製,文字白色
    }
    dc.Detach();
}

關鍵點:三段式位圖 (左端帽、中間重複、右端帽) 適應任意寬度;關閉按鈕僅在 m_bOverCloseButton 時顯示高亮狀態。

3. 滑鼠追蹤關閉按鈕熱區

// CTabButton::OnMouseMove
// 計算關閉按鈕矩形區域 (右側 16x16 等)
// 設定 m_bOverCloseButton,呼叫 Invalidate() 觸發重繪

4. 命令訊息路由與標籤切換

問題:CDialogBar 上按鈕的 BN_CLICKED 命令優先發送給主框架 CMainFrame,若主框架無處理函數,按鈕顯示 Disabled 狀態。

解決方案:按鈕自發自訂通知訊息 NM_TBSEL (WM_USER+1) 給 CTabBar,附帶 LPARAM = this (CTabButton*)。

#define NM_TBSEL WM_USER+1

void CTabButton::OnBnClicked() {
    CTabBar* pTabBar = (CTabBar*)GetParent();
    pTabBar->SendMessage(NM_TBSEL, 0, (LPARAM)this);
}

HRESULT CTabBar::OnNmTbSel(WPARAM wParam, LPARAM lParam) {
    CTabButton* pTabButton = (CTabButton*)lParam;
    SetCurSel(pTabButton); // 內部更新選中狀態
    CChildFrame* pChildFrame = GetChildFrameByTabButton(pTabButton);
    pChildFrame->MDIActivate();
    pChildFrame->MDIMaximize();
    return TRUE;
}

5. 標籤與 ChildFrame 雙向映射管理

struct TABINFO {
    CChildFrame* pChildFrame;
    CTabButton* pTabButton;
};

CTabButton* CTabBar::GetTabButtonByChildFrame(CChildFrame* pChildFrame) {
    for (int i = 0; i < m_ptrArray.GetCount(); i++) {
        TABINFO* pTabInfo = (TABINFO*)m_ptrArray.GetAt(i);
        if (pTabInfo->pChildFrame == pChildFrame) return pTabInfo->pTabButton;
    }
    return NULL;
}

CChildFrame* CTabBar::GetChildFrameByTabButton(CTabButton* pTabButton) {
    for (int i = 0; i < m_ptrArray.GetCount(); i++) {
        TABINFO* pTabInfo = (TABINFO*)m_ptrArray.GetAt(i);
        if (pTabInfo->pTabButton == pTabButton) return pTabInfo->pChildFrame;
    }
    return NULL;
}

優化建議:大量標籤時改用 CMap<CChildFrame*, CTabButton*> O(1) 查找。

執行效果

IE 風格多標籤欄效果

  • 標籤動態寬度適應
  • 選中/未選中視覺區分
  • 滑鼠懸停顯示關閉按鈕
  • 點擊標籤切換對應 MDI 子視窗

總結

基於 CDialogBar + 自繪 CButton 實現 IE 風格標籤欄,核心在於:

  1. 佈局自適應:OnSize 動態計算標籤寬度與位置
  2. 視覺自繪:DrawItem 三段式背景 + 關閉按鈕熱區
  3. 命令路由繞過:自訂通知訊息 NM_TBSEL 直接送達標籤欄,避免主框架命令路由導致按鈕 Disabled
  4. 雙向映射:TABINFO 結構維護標籤與子視窗對應

此架構可直接擴展至現代瀏覽器標籤功能:拖拽排序、固定標籤、靜音圖示、分組等。

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