本文示範在 MDI 應用程式中,利用 Cristi Posea 的 CSizingControlBar 實作可調整大小、可停靠的控制列,並在其中嵌入 CTreeCtrl 導覽樹,點擊樹節點即可在右側視圖顯示對應內容。這是商業軟體常見的「導覽窗格 + 內容區」經典佈局。
執行結果:左側可停靠樹狀控制列,右側視圖顯示節點標題。
SizingControlBar.h/.cpp (第三方開源庫,支援動態調整大小)。CMyBar : public CSizingControlBar 作為控制列視窗。CMyTree : public CTreeCtrl 處理樹節點點擊事件。CMainFrame 建立並停靠 CMyBar。CMyView 提供 DrawRect 繪製節點標題。將 SizingControlBar.h/.cpp 複製至專案目錄並加入專案。此類別繼承自 CControlBar,支援 CBRS_SIZE_DYNAMIC 風格。
用 ClassWizard 衍生自 CToolBar,再手動將基類改為 CSizingControlBar。
// MyBar.h
class CMyBar : public CSizingControlBar
{
DECLARE_DYNCREATE(CMyBar)
public:
CMyTree m_TreeCtrl; // 內嵌樹控件
// ...
virtual void OnUpdateCmdUI(CFrameWnd* pTarget, BOOL bDisableIfNoHndler);
afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
afx_msg void OnSize(UINT nType, int cx, int cy);
DECLARE_MESSAGE_MAP()
};
// MyBar.cpp
void CMyBar::OnUpdateCmdUI(CFrameWnd* pTarget, BOOL bDisableIfNoHndler)
{
UpdateDialogControls(pTarget, bDisableIfNoHndler); // 支援按鈕啟用/禁用
}
int CMyBar::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CSizingControlBar::OnCreate(lpCreateStruct) == -1) return -1;
// 建立樹控件
m_TreeCtrl.Create(WS_VISIBLE | WS_CHILD | WS_BORDER | WS_TABSTOP |
TVS_HASBUTTONS | TVS_HASLINES | TVS_LINESATROOT |
TVS_DISABLEDRAGDROP | TVS_EDITLABELS | TVS_TRACKSELECT,
CRect(10,10,300,100), this, ID_TREECTRL);
// 設定預設 GUI 字型
CFont* pFont = CFont::FromHandle((HFONT)GetStockObject(DEFAULT_GUI_FONT));
m_TreeCtrl.SetFont(pFont);
// 建立範例樹節點
HTREEITEM hRoot = m_TreeCtrl.InsertItem(_T("VCKBASE"), 0, 0);
HTREEITEM h1 = m_TreeCtrl.InsertItem(_T("程式設計師社群"), 1, 1, hRoot);
HTREEITEM h2 = m_TreeCtrl.InsertItem(_T("文檔中心"), 1, 1, hRoot);
// ... 續加子節點 ...
m_TreeCtrl.InsertItem(_T("開發論壇"), 1, 1, h1);
m_TreeCtrl.InsertItem(_T("開發聯盟"), 1, 1, h1);
// ...
m_TreeCtrl.SelectItem(hRoot);
m_TreeCtrl.Expand(hRoot, TVE_EXPAND);
return 0;
}
void CMyBar::OnSize(UINT nType, int cx, int cy)
{
CSizingControlBar::OnSize(nType, cx, cy);
if (m_TreeCtrl.m_hWnd)
{
CRect rc; GetClientRect(&rc);
m_TreeCtrl.MoveWindow(&rc); // 佔滿控制列客戶區
}
}
// MainFrm.h
class CMainFrame : public CMDIFrameWnd
{
CMyBar m_CtrlBar; // 成員變數
// ...
};
// MainFrm.cpp
int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CMDIFrameWnd::OnCreate(lpCreateStruct) == -1) return -1;
if (!m_CtrlBar.Create(_T("資料瀏覽列"), this, CSize(200,200), TRUE, 123))
{
TRACE0("Failed to create CMyBar\n"); return -1;
}
m_CtrlBar.SetBarStyle(m_CtrlBar.GetBarStyle() |
CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC);
m_CtrlBar.EnableDocking(CBRS_ALIGN_ANY);
EnableDocking(CBRS_ALIGN_ANY);
DockControlBar(&m_CtrlBar, AFX_IDW_DOCKBAR_LEFT); // 預設停靠左側
return 0;
}
// MyTree.h
class CMyTree : public CTreeCtrl
{
UINT m_uFlags;
afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
DECLARE_MESSAGE_MAP()
};
// MyTree.cpp
void CMyTree::OnLButtonDown(UINT nFlags, CPoint point)
{
HTREEITEM hItem = HitTest(point, &m_uFlags);
if (m_uFlags & TVHT_ONITEMLABEL) // 點擊標籤文字區域
{
CMyApp* pApp = (CMyApp*)AfxGetApp();
if (pApp->pView) // 應用程式類別持有視圖指標
{
pApp->pView->m_Title = GetItemText(hItem);
pApp->pView->Invalidate(TRUE); // 觸發重繪
}
SelectItem(hItem);
}
CTreeCtrl::OnLButtonDown(nFlags, point);
}
// MyView.h
class CMyView : public CView
{
public:
CString m_Title;
void DrawRect(CDC* pDC);
};
// MyView.cpp
void CMyView::DrawRect(CDC* pDC)
{
CRect rect(100, 100, 400, 400);
pDC->Rectangle(&rect);
CRect textRect(100, 100, 250, 250);
pDC->DrawText(m_Title, &textRect, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
}
void CMyView::OnDraw(CDC* pDC)
{
DrawRect(pDC);
}
void CMyView::OnInitialUpdate()
{
CView::OnInitialUpdate();
CMyApp* pApp = (CMyApp*)AfxGetApp();
pApp->pView = this; // 註冊自身指標
}
// MyApp.h
class CMyApp : public CWinApp
{
public:
CMyView* pView = nullptr; // 指向目前作用中視圖
// ...
};
MDI 下主框架繼承 CMDIFrameWnd,視圖為 CMDIChildWnd 管理。上述架構同樣適用,只需確保 pView 指向目前啟用的子視圖 (可在 CMDIFrameWnd::MDIActivate 或視圖 OnActivateView 更新)。
CSizingControlBar 正確加入專案,且 CBRS_SIZE_DYNAMIC 風格已設定。OnCreate 中 Create 參數,確保 WS_VISIBLE | WS_CHILD 且矩形非空。AfxGetApp() 轉型是否正確,pView 是否已在 OnInitialUpdate 指派。本範例展示 MFC 文檔/視圖架構下,如何靈活組合第三方控制列庫與標準控件,建構專業級導覽介面。完整專案碼可從 VC 知識庫下載。