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

MFC 核心類別庫深度解析(13):工具列與狀態列架構、建立流程與佈局機制全解析

     編輯:關於VC++

前言

工具列(Toolbar)與狀態列 是 MFC 框架視窗架構中最基礎也最重要的控制條。本文基於 MFC 原始碼,深度剖析 CControlBar、CToolBar、CStatusBar 的類別體系、視窗建立流程、按鈕/分格管理、泊位/浮動佈局算法,以及框架視窗的重新佈局機制。

一、Windows 通用控制項基礎

  • 工具列類別名:ToolbarWindow32,狀態列:msctls_statusbar32,ToolTip:tooltips_class32。
  • 需先呼叫 InitCommonControls() 或 AfxDeferRegisterClass(AFX_WNDCOMMCTLS_REG) 確保 DLL 載入並註冊類別。
  • 通用控制項樣式:CCS_TOP(頂部)、CCS_BOTTOM(底部)、TBSTYLE_TOOLTIPS(啟用提示)。

二、MFC 控制條類別體系

CObject
└── CCmdTarget
    └── CWnd
        └── CControlBar (基類)
            ├── CToolBar
            ├── CStatusBar
            ├── CDockBar
            ├── CDialogBar
            └── COleResizeBar

CControlBar 實作共性:對齊父視窗邊緣、子項目管理(HWND 或非 HWND)、CBRS_* 樣式(CBRS_TOP/BOTTOM/LEFT/RIGHT, CBRS_ALIGN_ANY, CBRS_FLOAT_MULTI, CBRS_TOOLTIPS, CBRS_FLYBY, CBRS_SIZE_DYNAMIC)。

三、工具列建立流程深度追蹤

1. CToolBar::Create — 核心建立函數

BOOL CToolBar::Create(CWnd* pParentWnd, DWORD dwStyle, UINT nID)
{
    ASSERT_VALID(pParentWnd);
    ASSERT(!((dwStyle & CBRS_SIZE_FIXED) && (dwStyle & CBRS_SIZE_DYNAMIC)));

    m_dwStyle = dwStyle;                 // 保存 CBRS_* 樣式
    if (nID == AFX_IDW_TOOLBAR) m_dwStyle |= CBRS_HIDE_INPLACE;

    dwStyle &= ~CBRS_ALL;                // 移除 CBRS_* 避免干擾視窗樣式
    dwStyle |= CCS_NOPARENTALIGN | CCS_NOMOVEY | CCS_NODIVIDER | CCS_NORESIZE;

    VERIFY(AfxDeferRegisterClass(AFX_WNDCOMMCTLS_REG)); // 確保通用控制項類別已註冊

    CRect rect; rect.SetRectEmpty();
    return CWnd::Create(TOOLBARCLASSNAME, NULL, dwStyle, rect, pParentWnd, nID);
}

關鍵點:分離視窗樣式與 CBRS_* 控制條樣式,使用 ToolbarWindow32 系統類別建立視窗。

2. PreCreateWindow — 樣式修正

CControlBar::PreCreateWindow 強制加入 WS_CLIPSIBLINGS 防止重繪閃爍;在 Win4+ 系統上根據對齊方向自動轉換邊框樣式(CBRS_BORDER_3D)。

3. OnCreate — 共性初始化

int CControlBar::OnCreate(LPCREATESTRUCT lpcs)
{
    if (CWnd::OnCreate(lpcs) == -1) return -1;
    if (m_dwStyle & CBRS_TOOLTIPS) EnableToolTips(); // 啟用 ToolTip
    CFrameWnd* pFrame = (CFrameWnd*)GetParent();
    if (pFrame->IsFrameWnd()) {
        m_pDockSite = pFrame;
        pFrame->AddControlBar(this); // 加入框架視窗控制條列表
    }
    return 0;
}

4. OnNcCreate — 工具列專屬初始化

BOOL CToolBar::OnNcCreate(LPCREATESTRUCT lpCreateStruct)
{
    if (!CControlBar::OnNcCreate(lpCreateStruct)) return FALSE;
    if (m_hWndOwner) DefWindowProc(TB_SETPARENT, (WPARAM)m_hWndOwner, 0);
    DefWindowProc(TB_BUTTONSTRUCTSIZE, sizeof(TBBUTTON), 0); // 設定按鈕結構大小
    return TRUE;
}

5. LoadToolBar — 載入資源並建立按鈕

流程:尋找 RT_TOOLBAR 資源 → 鎖定記憶體 → 解析 CToolBarData(版本、寬高、按鈕數、命令 ID 陣列) → SetButtons 建立按鈕 → SetSizes 設定按鈕/圖片尺寸 → LoadBitmap 載入工具列位圖。

SetButtons 核心邏輯

BOOL CToolBar::SetButtons(const UINT* lpIDArray, int nIDCount)
{
    // 1. 刪除現有按鈕
    int nCount = (int)DefWindowProc(TB_BUTTONCOUNT, 0, 0);
    while (nCount--) VERIFY(DefWindowProc(TB_DELETEBUTTON, 0, 0));

    // 2. 新增按鈕
    TBBUTTON btn; memset(&btn, 0, sizeof(TBBUTTON));
    int iImage = 0;
    for (int i = 0; i < nIDCount; i++) {
        btn.fsState = TBSTATE_ENABLED;
        if ((btn.idCommand = *lpIDArray++) == 0) { // ID=0 為分隔條
            btn.fsStyle = TBSTYLE_SEP;
            btn.iBitmap = 8; // 8 像素寬度
        } else {
            btn.fsStyle = TBSTYLE_BUTTON;
            btn.iBitmap = iImage++;
        }
        if (!DefWindowProc(TB_ADDBUTTONS, 1, (LPARAM)&btn)) return FALSE;
    }
    m_nCount = (int)DefWindowProc(TB_BUTTONCOUNT, 0, 0);
    m_bDelayedButtonLayout = TRUE;
    return TRUE;
}

四、狀態列建立與分格管理

  • CStatusBar::Create 使用 msctls_statusbar32 類別,ID 為 AFX_IDW_STATUS_BAR (0xE801)。
  • SetIndicators 設定分格 ID 陣列(類似工具列按鈕 ID),內部發送 SB_SETPARTS 訊息。
  • 每個分格非視窗,由狀態列視窗程序直接繪製文字/圖示。

五、控制條銷毀機制

CControlBar 覆寫 DestroyWindow、PostNcDestroy、OnDestroy 統一處理銷毀邏輯;m_bAutoDelete = TRUE 時動態建立的控制條自動 delete this。

六、佈局核心算法:RecalcLayout → RepositionBars

1. CFrameWnd::RecalcLayout

虛擬函數,在視窗建立、WM_SIZE、初始化時被呼叫。分兩種情況:

  • 浮動控制條容器 (CMiniFrameWnd, FWS_SNAPTOBARS):計算邊框矩形後 SetWindowPos。
  • 標準框架視窗:直接呼叫 RepositionBars(0, 0xFFFF, AFX_IDW_PANE_FIRST, reposExtra, &m_rectBorder)。

2. CWnd::RepositionBars — 佈局引擎

void CWnd::RepositionBars(UINT nIDFirst, UINT nIDLast, UINT nIDLeftOver,
    UINT nFlags, LPRECT lpRectParam, LPCRECT lpRectClient, BOOL bStretch)
{
    AFX_SIZEPARENTPARAMS layout;
    layout.hDWP = (nFlags != reposQuery) ? ::BeginDeferWindowPos(8) : NULL;
    layout.rect = lpRectClient ? *lpRectClient : ClientRect;
    layout.bStretch = bStretch;

    // 1. 發送 WM_SIZEPARENT 給每個控制條,讓其從客戶區扣除自身高度/寬度
    for (HWND hWndChild = ::GetTopWindow(m_hWnd); hWndChild; hWndChild = ::GetNextWindow(hWndChild, GW_HWNDNEXT)) {
        UINT nIDC = _AfxGetDlgCtrlID(hWndChild);
        if (nIDC == nIDLeftOver) { hWndLeftOver = hWndChild; continue; }
        if (nIDC >= nIDFirst && nIDC <= nIDLast) {
            ::SendMessage(hWndChild, WM_SIZEPARENT, 0, (LPARAM)&layout);
        }
    }

    // 2. 剩餘區域給 View (nIDLeftOver)
    if (hWndLeftOver) {
        CWnd* pLeft = CWnd::FromHandle(hWndLeftOver);
        if (nFlags == reposExtra && lpRectParam) { /* 套用邊框保留區 */ }
        pLeft->CalcWindowRect(&layout.rect);
        AfxRepositionWindow(&layout, hWndLeftOver, &layout.rect);
    }

    // 3. 統一提交視窗位置
    if (layout.hDWP) ::EndDeferWindowPos(layout.hDWP);
}

3. CControlBar::OnSizeParent — 控制條回應佈局查詢

控制條收到 WM_SIZEPARENT 時,呼叫 CalcDynamicLayout 或 CalcFixedLayout 計算自身所需矩形,從 layout.rect 扣除,並視 layout.hDWP 決定是否立即定位。

七、框架視窗與工具列/狀態列的介面互動

  • WM_SETMESSAGESTRING / WM_POPMESSAGESTRING:MFC 內部訊息,透過框架視窗在狀態列第 0 格顯示命令提示文字(資源 ID 對應字串表)。
  • CFrameWnd::SaveBarState / LoadBarState:將工具列泊位狀態、可見性存入註冊表/INI,實現使用者自訂佈局持久化。

八、標準 AppWizard 生成代碼解讀

// CMainFrame::OnCreate 典型代碼
if (!m_wndToolBar.Create(this) || !m_wndToolBar.LoadToolBar(IDR_MAINFRAME)) return -1;
if (!m_wndStatusBar.Create(this) || !m_wndStatusBar.SetIndicators(indicators, ...)) return -1;

m_wndToolBar.SetBarStyle(m_wndToolBar.GetBarStyle() | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC);
m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY);
EnableDocking(CBRS_ALIGN_ANY);
DockControlBar(&m_wndToolBar);

結語

MFC 控制條架構以 CControlBar 為核心,封裝了 Windows 通用控制項的建立、樣式轉換、佈局協商、持久化等複雜細節。深入理解 Create/PreCreateWindow/OnCreate/OnNcCreate 建立鏈、LoadToolBar/SetButtons 按鈕管理、RecalcLayout/RepositionBars/OnSizeParent 佈局三部曲,對於客製化工具列、修復泊位異常、實現動態增減控制條具有決定性指導意義。

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