📄 wtl for mfc programmers, part vii - splitter windows - wtl.htm
字号:
<PRE><SPAN class=cpp-keyword><font color="#0033FF">typedef</font></SPAN><font color="#0033FF"> CWinTraitsOR<LVS_REPORT | LVS_SINGLESEL | LVS_NOSORTHEADER>
CListTraits;
<SPAN class=cpp-keyword>class</SPAN> CClipSpyListCtrl :
<SPAN class=cpp-keyword>public</SPAN> CWindowImpl<CClipSpyListCtrl, CListViewCtrl, CListTraits>,
<SPAN class=cpp-keyword>public</SPAN> CCustomDraw<CClipSpyListCtrl>
{
<SPAN class=cpp-keyword>public</SPAN>:
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<CClipSpyListCtrl>, <SPAN class=cpp-literal>1</SPAN>)
DEFAULT_REFLECTION_HANDLER()
END_MSG_MAP()
<SPAN class=cpp-comment>//...</SPAN>
};</font></PRE>
<P>如果你看过前面的几篇文章就会很容易读懂这个类的代码。它响应WM_CHANGECBCHAIN消息,这样就可以知道是否启动和关闭了其它剪贴板查看程序,它还响应WM_DRAWCLIPBOARD消息,这样就可以知道剪贴板的内容是否改变。</P>
<p>由于分隔窗口窗格内的子窗口在程序运行其间一直存在,我们也可以将它们设为CMainFrame类的成员:</p>
<PRE><SPAN class=cpp-keyword><font color="#0033FF">class</font></SPAN><font color="#0033FF"> CMainFrame : <SPAN class=cpp-keyword>public</SPAN> ...
{
<SPAN class=cpp-comment>//...</SPAN>
<SPAN class=cpp-keyword>protected</SPAN>:
CSplitterWindow m_wndVertSplit;
<B>CClipSpyListCtrl m_wndFormatList;
CRichEditCtrl m_wndDataViewer;</B>
};</font></PRE>
<H2><A name=creatingpanes></A><font color="#FFFF66">创建一个窗格内的窗口</font></H2>
<P>既然已经有了分隔窗口和子窗口的成员变量,填充分隔窗口就是一件简单的事情了。先创建分隔窗口,然后创建两个子窗口,使用分隔窗口作为它们的父窗口:</P>
<PRE><font color="#0033FF">LRESULT CMainFrame::OnCreate ( LPCREATESTRUCT lpcs )
{
<SPAN class=cpp-comment>//...</SPAN>
<SPAN class=cpp-comment>// Create the splitter window</SPAN>
<SPAN class=cpp-keyword>const</SPAN> DWORD dwSplitStyle = WS_CHILD | WS_VISIBLE |
WS_CLIPCHILDREN | WS_CLIPSIBLINGS,
dwSplitExStyle = WS_EX_CLIENTEDGE;
m_wndVertSplit.Create ( *<SPAN class=cpp-keyword>this</SPAN>, rcDefault, NULL,
dwSplitStyle, dwSplitExStyle );
<B><SPAN class=cpp-comment>// Create the left pane (list of clip formats)</SPAN>
m_wndFormatList.Create ( m_wndVertSplit, rcDefault );
<SPAN class=cpp-comment>// Create the right pane (rich edit ctrl)</SPAN>
<SPAN class=cpp-keyword>const</SPAN> 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) );</B>
<SPAN class=cpp-comment>// Set the splitter as the client area window, and resize</SPAN>
<SPAN class=cpp-comment>// the splitter to match the frame size.</SPAN>
m_hWndClient = m_wndVertSplit;
UpdateLayout();
m_wndVertSplit.SetSplitterPos ( <SPAN class=cpp-literal>200</SPAN> );
<SPAN class=cpp-keyword>return</SPAN> <SPAN class=cpp-literal>0</SPAN>;
}</font></PRE>
<P>注意两个类的Create()函数都用m_wndVertSplit作为父窗口,RECT参数无关紧要,因为分隔窗口会重新调整它们的大小,所以可以使用CWindow::rcDefault。</P>
<p>最后就是将窗口的句柄传递给分隔窗口的窗格,这一步也需要在UpdateLayout()调用之前完成,这样最终所有的窗口都有正确的大小。</p>
<PRE><font color="#0033FF">LRESULT CMainFrame::OnCreate ( LPCREATESTRUCT lpcs )
{
<SPAN class=cpp-comment>//...</SPAN>
m_wndDataViewer.SetFont ( AtlGetStockFont(ANSI_FIXED_FONT) );
<B><SPAN class=cpp-comment>// Set up the splitter panes</SPAN>
m_wndVertSplit.SetSplitterPanes ( m_wndFormatList, m_wndDataViewer );</B>
<SPAN class=cpp-comment>// Set the splitter as the client area window, and resize</SPAN>
<SPAN class=cpp-comment>// the splitter to match the frame size.</SPAN>
m_hWndClient = m_wndVertSplit;
UpdateLayout();
m_wndVertSplit.SetSplitterPos ( <SPAN class=cpp-literal>200</SPAN> );
<SPAN class=cpp-keyword>return</SPAN> <SPAN class=cpp-literal>0</SPAN>;
}</font></PRE>
<P>现在,list控件上增加了几栏,结果看起来是这个样子:</P>
<P><IMG height=259 alt=" [Splitter w/panes - 4K] "
src="images/panes7.png"
width=320 align=bottom border=0></P>
<P>需要注意的是分隔窗口对放进窗格的窗口类型没有限制,不像MFC那样必须是CView的派生类。窗格窗口只要有WS_CHILD样式就行了,没有任何其他限制。</P>
<H2><A name=messagerouting></A><font color="#FFFF66">消息处理</font></H2>
<P>由于在主框架窗口和我们的窗格窗口之间加了一个分隔窗口,你可能想知道现在通知消息是如何工作的,比如,主框架窗口是如何收到NM_CUSTOMDRAW通知消息并将它反射给list控件的?答案就在CSplitterWindowImpl的消息链中:<br>
</P>
<PRE><font color="#0033FF"> BEGIN_MSG_MAP(thisClass)
MESSAGE_HANDLER(WM_ERASEBKGND, OnEraseBackground)
MESSAGE_HANDLER(WM_SIZE, OnSize)
CHAIN_MSG_MAP(baseClass)
FORWARD_NOTIFICATIONS()
END_MSG_MAP()</font></PRE>
<P>最后的哪个FORWARD_NOTIFICATIONS()宏最重要,回忆一下第四章,有一些通知消息总是被发送的子窗口的父窗口,FORWARD_NOTIFICATIONS()就是做了这些工作,它将这些消息转发给分隔窗口的父窗口。也就是说,当list窗口发送一个WM_NOTIFY消息给分隔窗口时(它是list的父窗口),分隔窗口就将这个WM_NOTIFY消息转发给主框架窗口(它是分隔窗口的父窗口)。当主框架窗口反射回消息时会将消息反射给WM_NOTIFY消息的最初发送者,也就是list窗口,所以分隔窗口并没有参与消息反射。</P>
<P>在list窗口和主框架窗口之间的这些消息传递并不影响分隔窗口的工作,这使得在程序中添加和移除分隔窗口非常容易,因为子窗口不需要做任何改变就可以继续工作。</P>
<H2><A name=panecont></A><font color="#FFFF66">窗格容器</font></H2>
<P>WTL还有一个被称为窗格容器的构件,它就像Explorer中左边的窗格那样,顶部有一个可以显示文字的区域,还有一个可选择是否显示的Close按钮:</P>
<P><IMG height=150 alt=" [Explorer pane container - 3K] "
src="images/explorerpc7.png"
width=199 align=bottom border=0></P>
<P>就像分隔窗口管理两个窗格窗口一样,这个窗格容器也管理一个子窗口,当容器窗口的大小改变时,子窗口也相应的改变大小以便能够填充容器窗口的内部空间。</P>
<H3><A name=pcclasses></A><font color="#FFFF66">相关的类</font></H3>
<P>这个窗格容器的实现需要两个类:CPaneContainerImpl和CPaneContainer,它们都在atlctrlx.h中声明。CPaneContainerImpl是一个CWindowImpl派生类,它含有窗格容器的完整实现,CPaneContainer只是提供了一个类名,除非重载CPaneContainerImpl的方法或改变容器的外观,一般使用CPaneContainer就够了。</P>
<H3><A name=pcmethods></A><font color="#FFFF66">基本方法</font></H3>
<PRE><font color="#0033FF">HWND Create(
HWND hWndParent, LPCTSTR lpstrTitle = NULL,
DWORD dwStyle = WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN,
DWORD dwExStyle = <SPAN class=cpp-literal>0</SPAN>, UINT nID = <SPAN class=cpp-literal>0</SPAN>, LPVOID lpCreateParam = NULL)
HWND Create(
HWND hWndParent, UINT uTitleID,
DWORD dwStyle = WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN,
DWORD dwExStyle = <SPAN class=cpp-literal>0</SPAN>, UINT nID = <SPAN class=cpp-literal>0</SPAN>, LPVOID lpCreateParam = NULL)</font></PRE>
<P>创建一个CPaneContainer窗口和创建其它子窗口一样。有两个Create()函数,它们的区别仅仅是第二个参数不同。第一个函数需要传递一个字符串作为容器顶部区域显示的文字,第二个参数需要需要传一个字符串的资源ID,其他参数只要使用默认值就行了。</P>
<PRE><font color="#0033FF">DWORD SetPaneContainerExtendedStyle(DWORD dwExtendedStyle, DWORD dwMask = <SPAN class=cpp-literal>0</SPAN>)
DWORD GetPaneContainerExtendedStyle()</font></PRE>
<P>CPaneContainer还有一些扩展样式用来控制容器窗口上Close按钮的布局方式:</P>
<UL>
<LI>PANECNT_NOCLOSEBUTTON:使用样式去掉顶部的Close按钮。
<LI>PANECNT_VERTICAL:设置这个样式后,顶部的文字区域将沿着容器窗口的左边界垂直放置。</LI>
</UL>
<P>扩展样式的默认值是0,表示容器窗口是水平放置的,还有一个Close按钮。</P>
<PRE><font color="#0033FF">HWND SetClient(HWND hWndClient)
HWND GetClient()</font></PRE>
<P>调用SetClient()可以将一个子窗口指派给窗格容器,这和调用CSplitterWindow类的SetSplitterPane()方法作用类似。SetClient()同时返回原来的客户区窗口句柄而调用GetClient()则可以得到当前的客户区窗口句柄。</P>
<PRE><font color="#0033FF">BOOL SetTitle(LPCTSTR lpstrTitle)
BOOL GetTitle(LPTSTR lpstrTitle, <SPAN class=cpp-keyword>int</SPAN> cchLength)
<SPAN class=cpp-keyword>int</SPAN> GetTitleLength()</font></PRE>
<P>调用SetTitle()可以改变容器窗口顶部显示的文字,调用GetTitle()可以得到当前窗口顶部区域显示的文字,调用GetTitleLength()可以得到当前显示的文字的字符个数(不包括结尾的空字符)。</P>
<PRE><font color="#0033FF">BOOL EnableCloseButton(BOOL bEnable)</font></PRE>
<P>如果窗格容器使用的Close按钮,你可以调用EnableCloseButton()来控制这个按钮的状态。</P>
<H3><A name=pcinsplitter></A><font color="#FFFF66">在分隔窗口中使用窗格容器</font></H3>
<P>为了说明窗格容器的使用方法,我们将向ClipSpy的分隔窗口的左窗格添加一个窗格容器,我们将一个窗格容器指派给左窗格取代原来使用的list控件,而将list控件指派给窗格容器。下面是在CMainFrame::OnCreate()中为支持窗格容器而添加的代码。</P>
<PRE><font color="#0033FF">LRESULT CMainFrame::OnCreate ( LPCREATESTRUCT lpcs )
{
<SPAN class=cpp-comment>//...</SPAN>
m_wndVertSplit.Create ( *<SPAN class=cpp-keyword>this</SPAN>, rcDefault, NULL, dwSplitStyle, dwSplitExStyle );
<B><SPAN class=cpp-comment>// Create the pane container.</SPAN>
m_wndPaneContainer.Create ( m_wndVertSplit, IDS_LIST_HEADER );
<SPAN class=cpp-comment>// Create the left pane (list of clip formats)</SPAN>
m_wndFormatList.Create ( m_wndPaneContainer, rcDefault );
</B><SPAN class=cpp-comment>//...</SPAN>
<B><SPAN class=cpp-comment>// Set up the splitter panes</SPAN>
m_wndPaneContainer.SetClient ( m_wndFormatList );
m_wndVertSplit.SetSplitterPanes ( m_wndPaneContainer, m_wndDataViewer );</B></font></PRE>
<P>注意,现在list控件的父窗口是m_wndPaneContainer,同时m_wndPaneContainer被设定成分隔窗口的左窗格。</P>
<p>下面是修改后的左窗格的外观,由于窗格容器在顶部的文本区域自己画了一个三维边框,所以我还要稍微修改一下边框的样式。这样看起来不是很好看,你可以自己调整样式知道你满意为止。(当然,你需要在Windows
XP 上测试一下哪个界面主题可以使得分隔窗口看起来“更有意思”。)</p>
<P><IMG height=280 alt=" [Pane container - 5K] "
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -