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

MFC 程式設計師的 WTL 入門指南 (八):分隔視窗與窗格容器深度應用

     編輯:關於VC++

分隔視窗:從資源管理器到通用 UI 元素

Windows 95 資源管理器的雙窗格設計確立了分隔視窗的地位。MFC CSplitterWnd 功能強大但複雜且緊耦合 Document/View 架構。WTL CSplitterWindow 更輕量、易用、易擴充,不綁定特定視圖類別。

核心類別體系 (atlsplit.h)

  • 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)

實戰範例:重寫 ClipSpy 示範分隔視窗

建立專案與主框架成員

// WTL 精靈建立 SDI,取消工具列/視圖視窗
class CMainFrame : public ...
{
protected:
    CSplitterWindow m_wndVertSplit;       // 垂直分隔視窗
    CClipSpyListCtrl m_wndFormatList;     // 左窗格:清單檢視
    CRichEditCtrl m_wndDataViewer;        // 右窗格:富文本編輯
};

OnCreate 完整建立流程

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;
}

分隔視窗執行畫面

訊息轉發機制:FORWARD_NOTIFICATIONS

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()
  • 子視窗 (如 ListView) 送 WM_NOTIFY 給分隔視窗 (父視窗)。
  • FORWARD_NOTIFICATIONS 轉發給主框架 (分隔視窗的父視窗)。
  • 主框架反射訊息回原發送者 (ListView),分隔視窗完全透明。
  • 加入/移除分隔視窗零修改子視窗代碼。

窗格容器:CPaneContainer

類似資源管理器左側窗格:頂部標題區 + 可選關閉按鈕,管理單一子視窗並自動調整大小。

核心類別 (atlctrlx.h)

  • 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);

巢狀分隔視窗

窗格內嵌 ActiveX 控件 (如瀏覽器)

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);
}

漸層標題

狀態列進度條整合

  1. 取得狀態列第一格矩形區域
  2. 建立進度條為狀態列子視窗,大小同格子
  3. 隨編輯內容更新進度條位置

代碼見 CMainFrame::CreateProgressCtrlInStatusBar()。

參考資源

WTL Splitters and Pane Containers by Ed Gadziemski

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