在開發 Windows 應用程式時,擁有類似 Visual Studio IDE 那樣靈活的停靠視窗界面,能大幅提升使用者體驗。左側的工具箱、底部的輸出視窗,都能自由浮動、調整大小、自動隱藏。本文將詳細介紹如何在 MFC 專案中,利用 CSizingControlBar 衍生類別 CCoolBar,快速實現這類專業級界面,並提供完整原始碼供參考。
實現這類界面的關鍵在於 CControlBar 架構。我們不必從零開始編寫,國外開發者 Cristi Posea 已經實作了一個功能完善的 CSizingControlBar 類別,支援動態調整大小、浮動、停靠等核心功能。本文作者在此基礎上進行了精簡與封裝,將其重命名為 CCoolBar,使其更易於整合到專案中。
BarDemo 的 MFC SDI 專案,其他選項保持預設。sizecbar.h、sizecbar.cpp、scbarg.h、scbarg.cpp 複製到專案目錄,並透過 Project > Add to Project > Files 加入專案。此時 ClassView 中將出現 CCoolBar 類別。stdafx.h 中加入標頭檔引用:
#include "sizecbar.h" #include "scbarg.h"
CCoolBar m_wndMyBar1; // 左側停靠視窗 CCoolBar m_wndMyBar2; // 底部停靠視窗
在 CMainFrame::OnCreate 中撰寫建立程式碼,邏輯與建立一般工具列類似:
if (!m_wndMyBar1.Create(_T("我的控制列"), this, 123)) {
TRACE0("Failed to create mybar ");
return -1;
}
if (!m_wndMyBar2.Create(_T("我的控制列2"), this, CSize(100,100), TRUE, 124)) {
TRACE0("Failed to create mybar ");
return -1;
}
// 設定樣式:工具提示、飛越效果、動態調整大小 m_wndMyBar1.SetBarStyle(m_wndMyBar1.GetBarStyle() | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC); m_wndMyBar2.SetBarStyle(m_wndMyBar2.GetBarStyle() | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC); // 啟用停靠功能,允許停靠在任意邊緣 m_wndMyBar1.EnableDocking(CBRS_ALIGN_ANY); m_wndMyBar2.EnableDocking(CBRS_ALIGN_ANY); // 執行停靠:左側與底部 DockControlBar(&m_wndMyBar1, AFX_IDW_DOCKBAR_LEFT); DockControlBar(&m_wndMyBar2, AFX_IDW_DOCKBAR_BOTTOM);
核心技巧:建立子控制項時,將控制列指標 (&m_wndMyBar1 或 &m_wndMyBar2) 作為父視窗傳入。注意:單一控制列同一時間只能容納一個直接子視窗,若需多頁籤請使用 Tab 控制項。
範例 (1):在底部控制列放入編輯控制項
// CMainFrame 成員變數
CEdit m_wndEdit;
// OnCreate 中建立
m_wndEdit.Create(WS_VSCROLL | WS_CHILD | WS_VISIBLE | ES_AUTOVSCROLL | ES_MULTILINE | ES_WANTRETURN,
CRect(0,0,0,0), &m_wndMyBar2, 101);
m_wndEdit.ModifyStyleEx(0, WS_EX_CLIENTEDGE);
範例 (2):在左側控制列放入樹狀控制項
// CMainFrame 成員變數
CTreeCtrl m_wndTree;
// 建立樹狀控制項
if (!m_wndTree.Create(WS_CHILD | WS_VISIBLE | TVS_HASLINES | TVS_HASBUTTONS | TVS_LINESATROOT,
CRect(0,0,0,0), &m_wndMyBar1, 100)) {
TRACE0("Failed to create tree control");
return -1;
}
m_wndTree.ModifyStyleEx(0, WS_EX_CLIENTEDGE);
// 填充樹狀節點
HTREEITEM hRoot = m_wndTree.InsertItem(_T("VC 知識庫線上雜誌"));
m_wndTree.InsertItem(_T("電子文檔"));
m_wndTree.InsertItem(_T("線上雜誌第一期"), hRoot);
m_wndTree.InsertItem(_T("線上雜誌第二期"), hRoot);
範例 (3):在控制列中嵌入對話框
IDD_DIALOGBAR。CVCKBASEDlg。CMainFrame 新增成員 CVCKBASEDlg m_wndVCKBASE。m_wndVCKBASE.Create(IDD_DIALOGBAR, &m_wndMyBar1); m_wndVCKBASE.ShowWindow(SW_SHOW);
範例 (4):使用 Tab 控制項實現多頁籤切換 (左側控制列)
範例程式碼提供了封裝類 CCoolTabCtrl,用法如下:
// 建立 Tab 控制項
m_TabCtrl.Create(TCS_DOWN | WS_CHILD | WS_VISIBLE, CRect(0,0,100,100), &m_wndMyBar1, 125);
// 將樹狀控制項作為第一個頁籤頁面 (父視窗指向 m_TabCtrl)
if (!m_wndTree.Create(WS_CHILD | WS_VISIBLE | TVS_HASLINES | TVS_HASBUTTONS | TVS_LINESATROOT,
CRect(0,0,0,0), &m_TabCtrl, 100)) { ... }
// 加入頁籤頁面
m_TabCtrl.AddPage(&m_wndTree, _T("VC知識庫"), IDI_ICON1); // 現有視窗
m_TabCtrl.AddPage(RUNTIME_CLASS(CVCKBASEDlg), IDD_DIALOGBAR, _T("第二頁"), IDI_ICON2); // 動態建立對話框
m_TabCtrl.AddPage(RUNTIME_CLASS(CMyDlg), IDD_DIALOGBAR2, _T("第三頁"), IDI_ICON3);
m_TabCtrl.UpdateWindow();
重要: 需動態建立的對話框類別 (CVCKBASEDlg, CMyDlg) 必須加入動態建立巨集:
// .h 檔 DECLARE_DYNCREATE(CVCKBASEDlg) // .cpp 檔 IMPLEMENT_DYNCREATE(CVCKBASEDlg, CDialog)
在 stdafx.h 定義巨集,並在 OnCreate 中 EnableDocking 後設定浮動框架類別:
// stdafx.h #define _SCB_REPLACE_MINIFRAME // CMainFrame::OnCreate #ifdef _SCB_REPLACE_MINIFRAME m_pFloatingFrameClass = RUNTIME_CLASS(CSCBMiniDockFrameWnd); #endif
在 stdafx.h 加入:#define _SCB_STYLE_FLAT
利用 RecalcLayout 取得第一個控制列位置,稍微偏移後停靠第二個:
// 同一底部列 DockControlBar(&m_wndMyBar1, AFX_IDW_DOCKBAR_BOTTOM); RecalcLayout(); CRect rect; m_wndMyBar1.GetWindowRect(rect); rect.OffsetRect(1, 0); DockControlBar(&m_wndMyBar2, AFX_IDW_DOCKBAR_BOTTOM, rect); // 同一右側行 DockControlBar(&m_wndMyBar1, AFX_IDW_DOCKBAR_RIGHT); RecalcLayout(); CRect rect; m_wndMyBar1.GetWindowRect(rect); rect.OffsetRect(0, 1); DockControlBar(&m_wndMyBar2, AFX_IDW_DOCKBAR_RIGHT, rect);

編譯執行,即可擁有媲美 VC IDE 的專業停靠視窗界面!感謝 Cristi Posea 提供的優秀基礎類別。