Windows 95 資源管理器的雙窗格設計確立了分隔視窗的地位。MFC CSplitterWnd 功能強大但複雜且緊耦合 Document/View 架構。WTL CSplitterWindow 更輕量、易用、易擴充,不綁定特定視圖類別。
CSplitterImpl<T, bVertical>:核心實作模板,bVertical=true 為垂直分隔。CSplitterWindowImpl:衍生自 CWindowImpl + CSplitterImpl,處理 WM_SIZE/WM_ERASEBKGND。CSplitterWindowT:視窗類別名 WTL_SplitterWindow。CSplitterWindow (垂直)、CHorSplitterWindow (水平)。// 分隔條位置控制
bool SetSplitterPos(int xyPos = -1, bool bUpdate = true); // -1 置中
int GetSplitterPos();
// 單/雙窗格模式切換
bool SetSinglePaneMode(int nPane = SPLIT_PANE_NONE); // SPLIT_PANE_LEFT/RIGHT/TOP/BOTTOM/NONE
int GetSinglePaneMode();
// 擴展樣式控制
DWORD SetSplitterExtendedStyle(DWORD dwExtendedStyle, DWORD dwMask = 0);
DWORD GetSplitterExtendedStyle();
// 樣式旗標:
// SPLIT_PROPORTIONAL 等比例縮放 (預設)
// SPLIT_RIGHTALIGNED 右/下窗格固定寬高
// SPLIT_BOTTOMALIGNED
// SPLIT_NONINTERACTIVE 分隔條不可拖曳
// 窗格子視窗指派
bool SetSplitterPane(int nPane, HWND hWnd, bool bUpdate = true);
void SetSplitterPanes(HWND hWndLeftTop, HWND hWndRightBottom, bool bUpdate = true);
HWND GetSplitterPane(int nPane);
// 作用中/預設窗格焦點控制
bool SetActivePane(int nPane); int GetActivePane();
bool ActivateNextPane(bool bNext = true);
bool SetDefaultActivePane(int nPane); // 或 HWND
int GetDefaultActivePane();
// 系統設定同步
void GetSystemSettings(bool bUpdate); // OnCreate 自動呼叫,主視窗需轉發 WM_SETTINGCHANGE
m_cxySplitBar:分隔條寬/高 (預設 SM_CXSIZEFRAME/SM_CYSIZEFRAME)m_cxyMin:窗格最小尺寸 (預設 0 或 2*Edge)m_cxyBarEdge:3D 邊框寬/高m_bFullDrag:拖曳時即時調整大小 (預設 SPI_GETDRAGFULLWINDOWS)// WTL 精靈建立 SDI,取消工具列/視圖視窗
class CMainFrame : public ...
{
protected:
CSplitterWindow m_wndVertSplit; // 垂直分隔視窗
CClipSpyListCtrl m_wndFormatList; // 左窗格:清單檢視
CRichEditCtrl m_wndDataViewer; // 右窗格:富文本編輯
};
LRESULT CMainFrame::OnCreate(LPCREATESTRUCT)
{
// 1. 建立分隔視窗
const DWORD dwStyle = WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS;
const DWORD dwExStyle = WS_EX_CLIENTEDGE;
m_wndVertSplit.Create(*this, rcDefault, NULL, dwStyle, dwExStyle);
// 2. 建立子視窗 (父視窗為分隔視窗)
m_wndFormatList.Create(m_wndVertSplit, rcDefault);
const DWORD dwRichStyle = WS_CHILD|WS_VISIBLE|WS_HSCROLL|WS_VSCROLL|
ES_READONLY|ES_AUTOHSCROLL|ES_AUTOVSCROLL|ES_MULTILINE;
m_wndDataViewer.Create(m_wndVertSplit, rcDefault, NULL, dwRichStyle);
m_wndDataViewer.SetFont(AtlGetStockFont(ANSI_FIXED_FONT));
// 3. 指派窗格 (必須在 UpdateLayout 前)
m_wndVertSplit.SetSplitterPanes(m_wndFormatList, m_wndDataViewer);
// 4. 設為客戶區並佈局
m_hWndClient = m_wndVertSplit;
UpdateLayout(); // 關鍵:先佈局再設定分隔條位置
m_wndVertSplit.SetSplitterPos(200);
return 0;
}

BEGIN_MSG_MAP(CSplitterWindowImpl)
MESSAGE_HANDLER(WM_ERASEBKGND, OnEraseBackground)
MESSAGE_HANDLER(WM_SIZE, OnSize)
CHAIN_MSG_MAP(baseClass)
FORWARD_NOTIFICATIONS() // 關鍵:將 WM_NOTIFY 轉發給父視窗
END_MSG_MAP()
WM_NOTIFY 給分隔視窗 (父視窗)。FORWARD_NOTIFICATIONS 轉發給主框架 (分隔視窗的父視窗)。類似資源管理器左側窗格:頂部標題區 + 可選關閉按鈕,管理單一子視窗並自動調整大小。
CPaneContainerImpl:完整實作CPaneContainer:一般使用HWND Create(HWND hParent, LPCTSTR lpstrTitle/UINT uTitleID, ...); // 標題或資源ID
DWORD SetPaneContainerExtendedStyle(DWORD dwExtStyle, DWORD dwMask = 0);
// PANECNT_NOCLOSEBUTTON 移除關閉鈕
// PANECNT_VERTICAL 垂直文字佈局
HWND SetClient(HWND hWndClient); HWND GetClient(); // 指派/取得子視窗
BOOL SetTitle(LPCTSTR); BOOL GetTitle(LPTSTR, int); int GetTitleLength();
BOOL EnableCloseButton(BOOL bEnable);
// OnCreate 中
m_wndPaneContainer.Create(m_wndVertSplit, IDS_LIST_HEADER); // 標題資源
m_wndFormatList.Create(m_wndPaneContainer, rcDefault); // 父視窗改為容器
// ...
m_wndPaneContainer.SetClient(m_wndFormatList);
m_wndVertSplit.SetSplitterPanes(m_wndPaneContainer, m_wndDataViewer);

點擊關閉鈕送 WM_COMMAND (ID_PANE_CLOSE) 給父視窗,主框架處理呼叫 SetSinglePaneMode 隱藏窗格。務必提供重新顯示管道 (如選單項目)。
CHorSplitterWindow m_wndHorzSplit; // 成員新增
// OnCreate
m_wndHorzSplit.Create(*this, rcDefault, NULL, dwStyle, dwExStyle);
m_wndVertSplit.Create(m_wndHorzSplit, rcDefault, NULL, dwStyle, dwExStyle);
// ...
m_hWndClient = m_wndHorzSplit; // 頂層為水平分隔
m_wndPaneContainer.SetClient(m_wndFormatList);
m_wndHorzSplit.SetSplitterPane(SPLIT_PANE_TOP, m_wndVertSplit);
m_wndVertSplit.SetSplitterPanes(m_wndPaneContainer, m_wndDataViewer);

CAxWindow wndIE;
wndIE.Create(m_wndHorzSplit, rcDefault, _T("https://www.codeproject.com"),
WS_CHILD|WS_VISIBLE|WS_CLIPCHILDREN|WS_HSCROLL|WS_VSCROLL);
m_wndHorzSplit.SetSplitterPanes(m_wndVertSplit, wndIE);
template <bool t_bVertical = true>
class CMySplitterWindowT : public CSplitterWindowImpl<CMySplitterWindowT<t_bVertical>, t_bVertical>
{
public:
DECLARE_WND_CLASS_EX(_T("My_SplitterWindow"), CS_DBLCLKS, COLOR_WINDOW)
void DrawSplitterBar(CDCHandle dc)
{
RECT rect;
if (m_br.IsNull()) m_br.CreateHatchBrush(HS_DIAGCROSS,
t_bVertical ? RGB(255,0,0) : RGB(0,0,255));
if (GetSplitterBarRect(&rect)) {
dc.FillRect(&rect, m_br);
if (GetExStyle() & WS_EX_CLIENTEDGE)
dc.DrawEdge(&rect, EDGE_RAISED, t_bVertical ? (BF_LEFT|BF_RIGHT) : (BF_TOP|BF_BOTTOM));
}
}
protected:
CBrush m_br;
};
typedef CMySplitterWindowT<true> CMySplitterWindow;
typedef CMySplitterWindowT<false> CMyHorSplitterWindow;

void CMyPaneContainer::DrawPaneTitle(CDCHandle dc)
{
RECT rect; GetClientRect(&rect);
TRIVERTEX tv[] = { {rect.left, rect.top, 0xff00, 0, 0, 0},
{rect.right, rect.top + m_cxyHeader, 0, 0xff00, 0, 0} };
GRADIENT_RECT gr = {0, 1};
dc.GradientFill(tv, 2, &gr, 1, GRADIENT_FILL_RECT_H);
}

代碼見 CMainFrame::CreateProgressCtrlInStatusBar()。
WTL Splitters and Pane Containers by Ed Gadziemski