在现代的多窗格应用程序中,分割窗口(Splitter)和窗格容器(Pane Container)是两个非常关键的组件。它们允许开发者将应用程序的主窗口划分为多个功能区域,从而提供更加灵活的用户界面。本文将介绍如何在WTL(Windows Template Library)框架中使用CSplitterWindow和CPaneContainer来构建一个三窗格SDI(Single Document Interface)应用程序。
分割器可以是垂直的也可以是水平的,它们在像Microsoft Outlook这样的多窗格应用程序中非常流行,用于将应用程序的主窗口划分为不同的功能区域。在WTL中,分割器的模板可以在atlsplit.h头文件中找到,提供了两种现成的分割器实现。
CSplitterWindow是一个标准的垂直分割器,而CHorSplitterWindow提供了一个水平分割器。要创建一个类似于Microsoft Outlook风格的三窗格布局,需要一个垂直分割器将屏幕分为左右两个部分,以及一个水平分割器将垂直分割器的右侧部分进一步划分为上下两个部分。
应用程序的主框架是垂直分割器的父窗口,而垂直分割器又是水平分割器的父窗口。以下是基本的设置代码,使用m_vSplit和m_hzSplit成员变量:
// 获取垂直分割器的客户区矩形
CRect rcVert;
GetClientRect(&rcVert);
// 创建垂直分割器
m_vSplit.Create(m_hWnd, rcVert, NULL, WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN);
// 获取水平分割器的客户区矩形
CRect rcHorz;
GetClientRect(&rcHorz);
// 创建水平分割器,注意vSplit是hzSplit的父窗口
m_hzSplit.Create(m_vSplit.m_hWnd, rcHorz, NULL, WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN);
// 将水平分割器添加到垂直分割器的右侧(1)
m_vSplit.SetSplitterPane(1, m_hzSplit);
此外,还可以为分割器设置一些选项。下面的代码片段展示了如何设置最小分割大小为35像素,这限制了分割器可以移动到主窗口的左侧或右侧边缘的接近程度。同时,还设置了初始分割位置为从左侧85像素,并且启用了“幽灵条”效果。在幽灵条模式下,拖动分割器时会显示一个深灰色的条。在普通模式下,整个分割器和窗格内容都会被拖动。
// 设置垂直分割器参数
m_vSplit.m_cxyMin = 35;
// 最小尺寸
m_vSplit.SetSplitterPos(85);
// 从左侧
m_vSplit.m_bFullDrag = false;
// 启用幽灵条
窗格容器提供了一个区域,可以容纳子窗口和控件,并且提供了一个带有标题和关闭按钮的标题栏。窗格容器的常见用途是提供有用的标题和逻辑上相关的程序元素的分组。CPaneContainer类可以在atlctrlx.h头文件中找到。
使用窗格容器需要三个基本步骤。首先,使用适当的分割器句柄作为父窗口创建容器。其次,将容器添加到适当的分割器部分。第三,设置容器标题。以下是代码示例,展示了如何为垂直分割的左侧部分设置窗格容器:
// 创建左侧容器
m_lPane.Create(m_vSplit.m_hWnd);
// 将容器添加到垂直分割器的左侧(0)
m_vSplit.SetSplitterPane(0, m_lPane);
// 设置左侧窗格标题
m_lPane.SetTitle("左侧窗格");
如果需要,可以在创建窗格容器时设置标题,通过提供标题文本或资源ID作为第二个参数传递给窗格容器创建语句。
在容器创建并分配给分割器之后,可以设置扩展选项。扩展选项有PANECNT_NOCLOSEBUTTON和PANECNT_VERTICAL。第一个选项控制是否在容器标题栏中显示关闭按钮,第二个选项控制标题是水平布局(在容器顶部)还是垂直布局(在容器左侧)。请注意,当标题设置为垂直方向时,标题不会显示。扩展选项的设置如下:
// 从顶部容器移除关闭按钮
m_tPane.SetPaneContainerExtendedStyle(PANECNT_NOCLOSEBUTTON);
如前所述,窗格容器可以容纳子窗口或子控件,例如示例程序中的编辑控件。将子控件添加到窗格容器的步骤如下: 1. 像通常一样创建控件。 2. 根据需要配置控件。 3. 将控件添加到窗格容器。
使用类似的步骤可以向容器添加任何类型的窗口。以下代码展示了如何在示例项目的底部窗格容器中添加编辑控件:
// 创建并配置编辑控件。注意m_bPane是父窗口
m_edit.Create(m_bPane.m_hWnd, rcDefault, NULL, WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN, WS_EX_CLIENTEDGE);
m_edit.SetFont((HFONT)GetStockObject(DEFAULT_GUI_FONT), TRUE);
m_edit.SetWindowText("底部窗格 -- 带有垂直标题和编辑控件作为子项");
// 将编辑控件分配给底部容器
m_bPane.SetClient(m_edit.m_hWnd);
当用户点击窗格容器的关闭按钮(标记为X)时,按钮会发送一个ID_PANE_CLOSE通知。示例项目在主框架的消息映射中捕获该通知,并使用以下例程处理它:
LRESULT OnPaneClose(WORD, WORD, HWND hWndCtl, BOOL&)
{
// 隐藏被点击关闭按钮的容器
::ShowWindow(hWndCtl, SW_HIDE);
// 查找容器的父分割器
HWND hWnd = ::GetParent(hWndCtl);
CSplitterWindow* pWnd;
pWnd = (CSplitterWindow*)::GetWindowLong(hWnd, GWL_ID);
// 将被关闭的容器从分割器中移除
int nCount = pWnd->m_nPanesCount;
for (int nPane = 0; nPane < nCount; nPane++)
{
if (hWndCtl == pWnd->m_hWndPane[nPane])
{
pWnd->SetSinglePaneMode(nCount - nPane - 1);
break;
}
}
return 0;
}
如果想完全移除容器而不是仅仅隐藏它,请使用DestroyWindow(hWndCtl)代替ShowWindow。如果想保持多窗格模式而不是切换到单窗格模式,也可以使用SetSplitterPane(nPane, NULL)代替SetSinglePaneMode。