由于CSplitterWindow是从CWindowImpl类派生的,所以你可以像创建其他子窗口那样创建分隔窗口。分隔窗口将存在于整个主框架窗口的生命周期,应该在CMainFrame类添加一个CSplitterWindow类型的变量。在CMainFrame::OnCreate()函数内,你可以将分隔窗口作为主窗口的子窗口创建,然后将其设置为主窗口的客户区窗口:
LRESULT CMainFrame::OnCreate ( LPCREATESTRUCT lpcs )
{
// ...
const DWORD dwSplitStyle = WS_CHILD | WS_VISIBLE |
WS_CLIPCHILDREN | WS_CLIPSIBLINGS,
dwSplitExStyle = WS_EX_CLIENTEDGE;
m_wndSplit.Create ( *this, rcDefault, NULL,
dwSplitStyle, dwSplitExStyle );
m_hWndClient = m_wndSplit;
}
bool SetSplitterPos(int xyPos = -1, bool bUpdate = true)
int GetSplitterPos()
bool SetSinglePaneMode(int nPane = SPLIT_PANE_NONE)
int GetSinglePaneMode()
DWORD SetSplitterExtendedStyle(DWORD dwExtendedStyle, DWORD dwMask = 0)
DWORD GetSplitterExtendedStyle()
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)
bool SetDefaultActivePane(HWND hWnd)
int GetDefaultActivePane()
void GetSystemSettings(bool bUpdate)
我们不使用分隔窗口是因为分隔窗口和它的窗格将作为“视图窗口”,在CMainFrame类中添加一个CSplitterWindow类型的数据成员:
class CMainFrame : public ...
{
//...
protected:
CSplitterWindow m_wndVertSplit;
};
LRESULT CMainFrame::OnCreate ( LPCREATESTRUCT lpcs )
{
//...
// Create the splitter window
const DWORD dwSplitStyle = WS_CHILD | WS_VISIBLE |
WS_CLIPCHILDREN | WS_CLIPSIBLINGS,
dwSplitExStyle = WS_EX_CLIENTEDGE;
m_wndVertSplit.Create ( *this, rcDefault, NULL,
dwSplitStyle, dwSplitExStyle );
// Set the splitter as the client area window, and resize
// the splitter to match the frame size.
m_hWndClient = m_wndVertSplit;
UpdateLayout();
// Position the splitter bar.
m_wndVertSplit.SetSplitterPos ( 200 );
return 0;
}
为了演示分隔窗口的不同使用方法,我将使用一个CListViewCtrl派生类和一个简单的CRichEditCtrl,下面是从CClipSpyListCtrl类摘录的代码,我们在左边的窗格使用这个类:
typedef CWinTraitsOR
CListTraits;
class CClipSpyListCtrl :
public CWindowImpl,
public CCustomDraw
{
public:
DECLARE_WND_SUPERCLASS(NULL, WC_LISTVIEW)
BEGIN_MSG_MAP(CClipSpyListCtrl)
MSG_WM_CHANGECBCHAIN(OnChangeCBChain)
MSG_WM_DRAWCLIPBOARD(OnDrawClipboard)
MSG_WM_DESTROY(OnDestroy)
CHAIN_MSG_MAP_ALT(CCustomDraw, 1)
DEFAULT_REFLECTION_HANDLER()
END_MSG_MAP()
//...
};
class CMainFrame : public ...
{
//...
protected:
CSplitterWindow m_wndVertSplit;
CClipSpyListCtrl m_wndFormatList;
CRichEditCtrl m_wndDataViewer;
};
LRESULT CMainFrame::OnCreate ( LPCREATESTRUCT lpcs )
{
//...
// Create the splitter window
const DWORD dwSplitStyle = WS_CHILD | WS_VISIBLE |
WS_CLIPCHILDREN | WS_CLIPSIBLINGS,
dwSplitExStyle = WS_EX_CLIENTEDGE;
m_wndVertSplit.Create ( *this, rcDefault, NULL,
dwSplitStyle, dwSplitExStyle );
// Create the left pane (list of clip formats)
m_wndFormatList.Create ( m_wndVertSplit, rcDefault );
// Create the right pane (rich edit ctrl)
const DWORD dwRichEditStyle =
WS_CHILD | WS_VISIBLE | WS_HSCROLL | WS_VSCROLL |
ES_READONLY | ES_AUTOHSCROLL | ES_AUTOVSCROLL | ES_MULTILINE;
m_wndDataViewer.Create ( m_wndVertSplit, rcDefault,
NULL, dwRichEditStyle );
m_wndDataViewer.SetFont ( AtlGetStockFont(ANSI_FIXED_FONT) );
// Set the splitter as the client area window, and resize
// the splitter to match the frame size.
m_hWndClient = m_wndVertSplit;
UpdateLayout();
m_wndVertSplit.SetSplitterPos ( 200 );
return 0;
}
LRESULT CMainFrame::OnCreate ( LPCREATESTRUCT lpcs )
{
//...
m_wndDataViewer.SetFont ( AtlGetStockFont(ANSI_FIXED_FONT) );
// Set up the splitter panes
m_wndVertSplit.SetSplitterPanes ( m_wndFormatList, m_wndDataViewer );
// Set the splitter as the client area window, and resize
// the splitter to match the frame size.
m_hWndClient = m_wndVertSplit;
UpdateLayout();
m_wndVertSplit.SetSplitterPos ( 200 );
return 0;
}
需要注意的是分隔窗口对放进窗格的窗口类型没有限制,不像MFC那样必须是CView的派生类。窗格窗口只要有WS_CHILD样式就行了,没有任何其他限制。
消息处理
由于在主框架窗口和我们的窗格窗口之间加了一个分隔窗口,你可能想知道现在通知消息是如何工作的,比如,主框架窗口是如何收到NM_CUSTOMDRAW通知消息并将它反射给list控件的?答案就在CSplitterWindowImpl的消息链中:
BEGIN_MSG_MAP(thisClass)
MESSAGE_HANDLER(WM_ERASEBKGND, OnEraseBackground)
MESSAGE_HANDLER(WM_SIZE, OnSize)
CHAIN_MSG_MAP(baseClass)
FORWARD_NOTIFICATIONS()
END_MSG_MAP()
就像分隔窗口管理两个窗格窗口一样,这个窗格容器也管理一个子窗口,当容器窗口的大小改变时,子窗口也相应的改变大小以便能够填充容器窗口的内部空间。
相关的类
这个窗格容器的实现需要两个类:CPaneContainerImpl和CPaneContainer,它们都在atlctrlx.h中声明。CPaneContainerImpl是一个CWindowImpl派生类,它含有窗格容器的完整实现,CPaneContainer只是提供了一个类名,除非重载CPaneContainerImpl的方法或改变容器的外观,一般使用CPaneContainer就够了。
基本方法
HWND Create(
HWND hWndParent, LPCTSTR lpstrTitle = NULL,
DWORD dwStyle = WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN,
DWORD dwExStyle = 0, UINT nID = 0, LPVOID lpCreateParam = NULL)
HWND Create(
HWND hWndParent, UINT uTitleID,
DWORD dwStyle = WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN,
DWORD dwExStyle = 0, UINT nID = 0, LPVOID lpCreateParam = NULL)
DWORD SetPaneContainerExtendedStyle(DWORD dwExtendedStyle, DWORD dwMask = 0)
DWORD GetPaneContainerExtendedStyle()
HWND SetClient(HWND hWndClient)
HWND GetClient()
BOOL SetTitle(LPCTSTR lpstrTitle)
BOOL GetTitle(LPTSTR lpstrTitle, int cchLength)
int GetTitleLength()
BOOL EnableCloseButton(BOOL bEnable)
LRESULT CMainFrame::OnCreate ( LPCREATESTRUCT lpcs )
{
//...
m_wndVertSplit.Create ( *this, rcDefault, NULL, dwSplitStyle, dwSplitExStyle );
// Create the pane container.
m_wndPaneContainer.Create ( m_wndVertSplit, IDS_LIST_HEADER );
// Create the left pane (list of clip formats)
m_wndFormatList.Create ( m_wndPaneContainer, rcDefault );
//...
// Set up the splitter panes
m_wndPaneContainer.SetClient ( m_wndFormatList );
m_wndVertSplit.SetSplitterPanes ( m_wndPaneContainer, m_wndDataViewer );
关闭按钮和消息处理
当用户用鼠标单击Close按钮时,窗格容器向父窗口发送一个WM_COMMAND消息,命令的ID是ID_PANE_CLOSE。如果你在分隔窗口中使用了窗格容器,你需要响应整个消息,调用SetSinglePaneMode()隐藏这个窗格。(但是,不要忘了提供用户一个重新显示窗格的方法!)
CPaneContainer的消息链也用到了FORWARD_NOTIFICATIONS()宏,和CSplitterWindow一样,窗格容器在客户窗口和它的父窗口之间传递通知消息。在ClipSpy这个例子中,在list控件和主框架窗口之间隔了两个窗口(窗格容器和分隔窗口),但是FORWARD_NOTIFICATIONS()宏可以确保所有的通知消息被送到主框架窗口。
高级功能
在这一节,我将介绍一些如何使用WTL的高级界面特性。
嵌套的分隔窗口
如果你要编写一个email的客户端程序,你可能需要使用嵌套的分隔条,一个水平的和一个垂直的分隔条。使用WTL很容易做到这一点:创建一个分隔窗口作为另一个分隔窗口的子窗口。
为了演示这种效果,我将为ClipSpy添加一个水平分隔窗口。首先,添加一个名为m_wndHorzSplitter的CHorSplitterWindow类型的成员,像创建垂直分隔窗口m_wndVertSplitter那样创建这个水平分隔窗口,使水平分隔窗口m_wndHorzSplitter成为顶层窗口,将m_wndVertSplitter创建成m_wndHorzSplitter的子窗口。最后将m_hWndClient设置为m_wndHorzSplitter,因为现在水平分隔窗口占据整个主框架窗口的客户区。
LRESULT CMainFrame::OnCreate()
{
//...
// Create the splitter windows.
m_wndHorzSplit.Create ( *this, rcDefault, NULL,
dwSplitStyle, dwSplitExStyle );
m_wndVertSplit.Create ( m_wndHorzSplit, rcDefault, NULL,
dwSplitStyle, dwSplitExStyle );
//...
// Set the horizontal splitter as the client area window.
m_hWndClient = m_wndHorzSplit;
// Set up the splitter panes
m_wndPaneContainer.SetClient ( m_wndFormatList );
m_wndHorzSplit.SetSplitterPane ( SPLIT_PANE_TOP, m_wndVertSplit );
m_wndVertSplit.SetSplitterPanes ( m_wndPaneContainer, m_wndDataViewer );
//...
}
在窗格中使用ActiveX控件
在分隔窗口的窗格中使用ActiveX控件与在对话框中使用ActiveX控件类似,使用CAxWindow类的方法在运行是创建控件,然后将这个CAxWindow指定给分隔窗口的窗格。下面演示了如何在水平分隔窗口下面的窗格中使用浏览器控件:
// Create the bottom pane (browser)
CAxWindow wndIE;
const DWORD dwIEStyle = WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN |
WS_HSCROLL | WS_VSCROLL;
wndIE.Create ( m_wndHorzSplit, rcDefault,
_T("http://www.codeproject.com"), dwIEStyle );
// Set the horizontal splitter as the client area window.
m_hWndClient = m_wndHorzSplit;
// Set up the splitter panes
m_wndPaneContainer.SetClient ( m_wndFormatList );
m_wndHorzSplit.SetSplitterPanes ( m_wndVertSplit, wndIE );
m_wndVertSplit.SetSplitterPanes ( m_wndPaneContainer, m_wndDataViewer );
template
class CMySplitterWindowT :
public CSplitterWindowImpl, t_bVertical>
{
public:
DECLARE_WND_CLASS_EX(_T("My_SplitterWindow"),
CS_DBLCLKS, COLOR_WINDOW)
// Overrideables
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 );
// draw 3D edge if needed
if ( (GetExStyle() & WS_EX_CLIENTEDGE) != 0)
dc.DrawEdge(&rect, EDGE_RAISED,
t_bVertical ? (BF_LEFT | BF_RIGHT)
: (BF_TOP | BF_BOTTOM));
}
}
protected:
CBrush m_br;
};
typedef CMySplitterWindowT CMySplitterWindow;
typedef CMySplitterWindowT CMyHorSplitterWindow;
窗格容器内的特殊绘制
CPaneContainer也有几个函数可以重载,用来改变窗格容器的外观。你可以从CPaneContainerImpl派生新类并重载你需要的方法,例如:
class CMyPaneContainer :
public CPaneContainerImpl
{
public:
DECLARE_WND_CLASS_EX(_T("My_PaneContainer"), 0, -1)
//... overrides here ...
};
void CalcSize()
HFONT GetTitleFont()
BOOL GetToolTipText(LPNMHDR lpnmh)
void DrawPaneTitle(CDCHandle dc)
void CMyPaneContainer::DrawPaneTitle ( CDCHandle dc )
{
RECT rect;
GetClientRect(&rect);
TRIVERTEX tv[] = {
{ rect.left, rect.top, 0xff00 },
{ rect.right, rect.top + m_cxyHeader, 0, 0xff00 }
};
GRADIENT_RECT gr = { 0, 1 };
dc.GradientFill ( tv, 2, &gr, 1, GRADIENT_FILL_RECT_H );
}
从上面的图中可以看到,这个演示程序有一个Splitters菜单,通过它可以在各种风格的分隔条(包括自画风格)和窗格容器之间切换,比较它们之间的异同。你还可以锁定分隔条的位置,这是通过设置和取消SPLIT_NONINTERACTIVE扩展风格来实现的。
在状态栏显示进度条
正如我在前几篇文章中做得保证那样,新的ClipSpy也演示了如何在状态条上创建进展条,它和MFC版本得功能一样,几个相关得步骤是:
得到状态条第一个窗格得坐标范围RECT
创建一个进展条作为状态条得子窗口,窗口大小就是哪个状态条窗格得大小
随着edit控件被填充的同时更新进展条的位置
这些代码在CMainFrame::CreateProgressCtrlInStatusBar()函数中。
继续
在第八章我将介绍属性页和向导对话框的用法
参考
WTL Splitters and Pane Containers by Ed Gadziemski
修改记录
July 9, 2003: 文章第一次发布。
原作 :Michael Dunn [英文原文]