📄 wtl for mfc programmers, part vii - splitter windows - wtl.htm
字号:
<html>
<head>
<title>WTL for MFC Programmers, Part VII - Splitter Windows</title>
<meta http-equiv="Content-Type" content="text/html; charset=gb2312">
</head>
<body bgcolor="#33CCCC" text="#000000">
<p align="center"><b><font style="FONT-SIZE: 16pt" size="4" color="#0033CC">WTL for MFC Programmers, Part VII - Splitter Windows</font></b><br>
<br>
</p>
<p align="left">原作 :<b><font color="#CC3366">Michael Dunn</font></b> [<a href="http://www.codeproject.com/wtl/WTL4MFC7.asp">英文原文</a>]<br>
翻译 :<a href="mailto:inte2000@163.com">Orbit(桔皮干了)</a> [<a href="http://www.winmsg.com/cn/orbit.htm">http://www.winmsg.com/cn/orbit.htm</a>]</p>
<p align="left"><a href="demo/WTL4MFC7_demo.zip">下载演示程序代码</a></p>
<H2><font color="#FFFF66">本章内容</font></H2>
<UL>
<LI><A href="#intro">介绍</A>
<LI><A href="#wtlsplitters">WTL 的分隔窗口</A>
<UL>
<LI><A href="#splitclasses">相关的类</A>
<LI><A href="#creatingsplitter">创建分隔窗口</A>
<LI><A href="#splittermethods">基本的方法</A>
<LI><A href="#splitdata">数据成员</A> </LI></UL>
<LI><A href="#startsample">开始一个例子工程</A>
<LI><A href="#creatingpanes">创建一个窗格内的窗口</A>
<LI><A href="#messagerouting">消息处理</A>
<LI><A href="#panecont">窗格容器</A>
<UL>
<LI><A href="#pcclasses">相关的类</A>
<LI><A href="#pcmethods">基本方法</A>
<LI><A href="#pcinsplitter">在分隔窗口中使用窗格容器</A>
<LI><A href="#pcclosebtn">关闭按钮和消息处理</A> </LI></UL>
<LI><A href="#advancedsplitter">高级功能</A>
<UL>
<LI><A href="#nested">嵌套的分隔窗口</A>
<LI><A href="#axinpane">在窗格中使用ActiveX控件</A>
<LI><A href="#specialdraw">特殊绘制</A> </LI></UL>
<LI><A href="#pcoverrides">窗格容器内的特殊绘制</A>
<LI><A href="#bonus">在状态栏显示进度条</A>
<LI><A href="#upnext">继续</A>
<LI><A href="#references">参考</A>
<LI><A href="#revisionhistory">修改记录</A> </LI></UL>
<H2><A name=intro></A><font color="#FFFF66">介绍</font></H2>
<P>随着使用两个分隔的视图管理文件系统的资源管理器在Windows 95中第一次出现,分隔窗口逐渐成为一种流行的界面元素。MFC也有一个复杂的功能强大的分隔窗口类,但是要掌握它的用法确实有点难,并且它和文档/视图框架联系紧密。在第七章我将介绍WTL的分隔窗口,它比MFC的分隔窗口要简单一些。WTL的分隔窗口没有MFC那么多特性,但是易于使用和扩展。</P>
<p>本章的例子工程是用WTL重写的<a href="http://www.codeproject.com/clipboard/clipspy.asp">ClipSpy</a>,如果你对这个程序不太熟悉,现在可以快速浏览一下本章内容,因为我只是复制了ClipSpy的功能而没用深入的解释它是如何工作的,毕竟这篇文章的重点是分隔窗口,不是剪贴板。</p>
<H2><A name=wtlsplitters></A><font color="#FFFF66">WTL 的分隔窗口</font></H2>
<P>头文件atlsplit.h含有所有WTL的分隔窗口类,一共有三个类:CSplitterImpl,CSplitterWindowImpl和CSplitterWindowT,不过你通常只会用到其中的一个。下面将介绍这些类和它们的基本方法。</P>
<H3><A name=splitclasses></A><font color="#FFFF66">相关的类</font></H3>
<P>CSplitterImpl是一个有两个参数的模板类,一个是窗口界面类的类名,另一个是布尔型变量表示分隔窗口的方向:true表示垂直方向,false表示水平方向。CSplitterImpl类包含了几乎所有分隔窗口的实现代码,它的许多方法是可重载的,重载这些方法可以自己绘制分隔条的外观或者实现其它的效果。CSplitterWindowImpl类是从CWindowImpl和CSplitterImpl两个类派生出来的,但是它的代码不多,有一个空的WM_ERASEBKGND消息处理函数和一个WM_SIZE处理函数用于重新定位分隔窗口。</P>
<p>最后一个是CSplitterWindowT类,它从CSplitterImpl类派生,它的窗口类名是“WTL_SplitterWindow”。还有两个自定义数据类型通常用来取代上面的三个类:CSplitterWindow用于垂直分隔窗口,CHorSplitterWindow用于水平分隔窗口。</p>
<H3><A name=creatingsplitter></A><font color="#FFFF66">创建分割窗口</font></H3>
<P>由于CSplitterWindow是从CWindowImpl类派生的,所以你可以像创建其他子窗口那样创建分隔窗口。分隔窗口将存在于整个主框架窗口的生命周期,应该在CMainFrame类添加一个CSplitterWindow类型的变量。在CMainFrame::OnCreate()函数内,你可以将分隔窗口作为主窗口的子窗口创建,然后将其设置为主窗口的客户区窗口:</P>
<PRE><font color="#0033FF">LRESULT CMainFrame::OnCreate ( LPCREATESTRUCT lpcs )
{
<SPAN class=cpp-comment>// ...</SPAN>
<SPAN class=cpp-keyword>const</SPAN> DWORD dwSplitStyle = WS_CHILD | WS_VISIBLE |
WS_CLIPCHILDREN | WS_CLIPSIBLINGS,
dwSplitExStyle = WS_EX_CLIENTEDGE;
m_wndSplit.Create ( *<SPAN class=cpp-keyword>this</SPAN>, rcDefault, NULL,
dwSplitStyle, dwSplitExStyle );
m_hWndClient = m_wndSplit;
}</font></PRE>
<P>创建分隔窗口之后,你就可以为每个窗格指定窗口或者做其他必要的初始化工作。</P>
<H3><A name=splittermethods></A><font color="#FFFF66">基本方法</font></H3>
<PRE><SPAN class=cpp-keyword><font color="#0033FF">bool</font></SPAN><font color="#0033FF"> SetSplitterPos(<SPAN class=cpp-keyword>int</SPAN> xyPos = -<SPAN class=cpp-literal>1</SPAN>, <SPAN class=cpp-keyword>bool</SPAN> bUpdate = <SPAN class=cpp-keyword>true</SPAN>)
<SPAN class=cpp-keyword>int</SPAN> GetSplitterPos()</font></PRE>
<P>可以调用SetSplitterPos()函数设置分隔条的位置,这个位置表示分割条距离分隔窗口的上边界(水平分隔窗口)或左边界(垂直分隔窗口)有多少个象素点。你可以使用默认值-1将分隔条设置到分隔窗口的中间,使两个窗格大小相同,通常传递true给bUpdate参数表示在移动分隔条之后相应的改变两个窗格的大小。GetSplitterPos()返回当前分隔条的位置,这个位置也是相对于分隔窗口的上边界或左边界。</P>
<PRE><SPAN class=cpp-keyword><font color="#0033FF">bool</font></SPAN><font color="#0033FF"> SetSinglePaneMode(<SPAN class=cpp-keyword>int</SPAN> nPane = SPLIT_PANE_NONE)
<SPAN class=cpp-keyword>int</SPAN> GetSinglePaneMode()</font></PRE>
<P>调用SetSinglePaneMode()函数可以改变分隔窗口的模式使单窗格模式还是双窗格模式,在单窗格模式下,只有一个窗格使可见的并且隐藏了分隔条,这和MFC的动态分隔窗口相似(只是没有那个小钳子形状的手柄,它用于重新分隔分隔窗口)。对于nPane参数可用的值是SPLIT_PANE_LEFT,SPLIT_PANE_RIGHT,SPLIT_PANE_TOP,SPLIT_PANE_BOTTOM,和SPLIT_PANE_NONE,前四个指示显示那个窗格(例如,使用SPLIT_PANE_LEFT参数将显示左边的窗格,隐藏右边的窗格),使用SPLIT_PANE_NONE表示两个窗格都显示。GetSinglePaneMode()返回五个SPLIT_PANE_*值中的一个表示当前的模式。</P>
<PRE><font color="#0033FF">DWORD SetSplitterExtendedStyle(DWORD dwExtendedStyle, DWORD dwMask = <SPAN class=cpp-literal>0</SPAN>)
DWORD GetSplitterExtendedStyle()</font></PRE>
<P>分隔窗口有自己的样式用于控制当整个分隔窗口改变大小时如何移动分隔条。有以下几种样式:</P>
<UL>
<LI>SPLIT_PROPORTIONAL: 两个窗格一起改变大小
<LI>SPLIT_RIGHTALIGNED: 右边的窗格保持大小不变,只改变左边的窗格大小
<LI>SPLIT_BOTTOMALIGNED: 下部的窗格保持大小不变,只改变上边的窗格大小</LI>
</UL>
<P>如果既没有指定SPLIT_PROPORTIONAL,也没有指定SPLIT_RIGHTALIGNED/SPLIT_BOTTOMALIGNED,则分隔窗口会变成左对齐或上对齐。如果将SPLIT_PROPORTIONAL和SPLIT_RIGHTALIGNED/SPLIT_BOTTOMALIGNED一起使用,则优先选用SPLIT_PROPORTIONAL样式。</P>
<p>还有一个附加的样式用来控制分隔条是否可以被用户移动:</p>
<UL>
<LI>SPLIT_NONINTERACTIVE:分隔条不能被移动并且不相应鼠标</LI>
</UL>
<P>扩展样式的默认值是 SPLIT_PROPORTIONAL。</P>
<PRE><SPAN class=cpp-keyword><font color="#0033FF">bool</font></SPAN><font color="#0033FF"> SetSplitterPane(<SPAN class=cpp-keyword>int</SPAN> nPane, HWND hWnd, <SPAN class=cpp-keyword>bool</SPAN> bUpdate = <SPAN class=cpp-keyword>true</SPAN>)
<SPAN class=cpp-keyword>void</SPAN> SetSplitterPanes(HWND hWndLeftTop, HWND hWndRightBottom, <SPAN class=cpp-keyword>bool</SPAN> bUpdate = <SPAN class=cpp-keyword>true</SPAN>)
HWND GetSplitterPane(<SPAN class=cpp-keyword>int</SPAN> nPane)</font></PRE>
<P>可以调用SetSplitterPane()为分隔窗口的窗格指派子窗口,nPane是一个SPLIT_PANE_*类型的值,表示设置拿一个窗格。hWnd是子窗口的窗口句柄。你可以使用SetSplitterPane()将一个子窗口同时指定给两个窗格,对于bUpdate参数通常使用默认值,也就是告诉分隔窗口立即调整子窗口的大小以适应窗格的大小。可以调用GetSplitterPane()得到某个窗格的子窗口句柄,如果窗格没有指派子窗口则GetSplitterPane()返回NULL。</P>
<PRE><SPAN class=cpp-keyword><font color="#0033FF">bool</font></SPAN><font color="#0033FF"> SetActivePane(<SPAN class=cpp-keyword>int</SPAN> nPane)
<SPAN class=cpp-keyword>int</SPAN> GetActivePane()</font></PRE>
<P>SetActivePane()函数将分隔窗口中的某个子窗口设置为当前焦点窗口,nPane是SPLIT_PANE_*类型的值,表示需要激活哪个窗格,这个函数还可以设置默认的活动窗格(后面介绍)。GetActivePane()函数查看所有拥有焦点的窗口,如果拥有焦点的窗口是窗格或窗格的子窗口就返回一个SPLIT_PANE_*类型的值,表示是哪个窗格。如果当前拥有焦点的窗口不是窗格的子窗口,那么GetActivePane()返回SPLIT_PANE_NONE。</P>
<PRE><SPAN class=cpp-keyword><font color="#0033FF">bool</font></SPAN><font color="#0033FF"> ActivateNextPane(<SPAN class=cpp-keyword>bool</SPAN> bNext = <SPAN class=cpp-keyword>true</SPAN>)</font></PRE>
<P>如果分隔窗口是单窗格模式,焦点被设到可见的窗格上,否则的话,ActivateNextPane()函数将调用GetActivePane()查看拥有焦点的窗口。如果一个窗格(或窗格内的子窗口)拥有检点,分隔窗口就将焦点设给另一个窗格,否则ActivateNextPane()将判断bNext的值,如果是true就激活left/top窗格,如果是false则激活right/bottom窗格。</P>
<PRE><SPAN class=cpp-keyword><font color="#0033FF">bool</font></SPAN><font color="#0033FF"> SetDefaultActivePane(<SPAN class=cpp-keyword>int</SPAN> nPane)
<SPAN class=cpp-keyword>bool</SPAN> SetDefaultActivePane(HWND hWnd)
<SPAN class=cpp-keyword>int</SPAN> GetDefaultActivePane()</font></PRE>
<P>调用SetDefaultActivePane()函数可以设置默认的活动窗格,它的参数可以是SPLIT_PANE_*类型的值,也可以是窗口的句柄。如果分隔窗口自身得到的焦点,可以通过调用SetFocus()将焦点转移给默认窗格。GetDefaultActivePane()函数返回SPLIT_PANE_*类型的值表示哪个窗格是当前默认的活动窗格。</P>
<PRE><SPAN class=cpp-keyword><font color="#0033FF">void</font></SPAN><font color="#0033FF"> GetSystemSettings(<SPAN class=cpp-keyword>bool</SPAN> bUpdate)</font></PRE>
<P>GetSystemSettings()读取系统设置并相应的设置数据成员。分隔窗口在OnCreate()函数中自动调用这个函数,你不需要自己调用这个函数。当然,你的主框架窗口应该响应WM_SETTINGCHANGE并将它传递给分隔窗口,
CSplitterWindow在WM_SETTINGCHANGE消息的处理函数中调用GetSystemSettings()。传递true给bUpdate参数,分隔窗口会根据新的设置重画自己。</P>
<H3><A name=splitdata></A><font color="#FFFF66">数据成员</font></H3>
<P>其他的一些特性可以通过直接访问CSplitterWindow的公有成员来设定,只要GetSystemSettings()被调用了,这些公有成员也会相应的被重置。</P>
<P>m_cxySplitBar:控制分隔条的宽度(垂直分隔条)和高度(水平分隔条)。默认值是通过调用GetSystemMetrics(SM_CXSIZEFRAME)(垂直分隔条)或GetSystemMetrics(SM_CYSIZEFRAME)(水平分隔条)得到的。</P>
<P>m_cxyMin:控制每个窗格的最小宽度(垂直分隔)和最小高度(水平分隔),分隔窗口不允许拖动比这更小的宽度或高度。如果分隔窗口有WS_EX_CLIENTEDGE扩展属性,则这个变量的默认值是0,否则其默认值是2*GetSystemMetrics(SM_CXEDGE)(垂直分隔)或2*GetSystemMetrics(SM_CYEDGE)(水平分隔)。</P>
<p>m_cxyBarEdge:控制画在分隔条两侧的3D边界的宽度(垂直分隔)或高度(水平分隔),其默认值刚好和m_cxyMin相反。</p>
<p>m_bFullDrag:如果是true,当分隔条被拖动时窗格大小跟着调整,如果是false,拖动时只显示一个分隔条的影子,直到拖动停止才调整窗格的大小。默认值是调用SystemParametersInfo(SPI_GETDRAGFULLWINDOWS)函数的返回值。</p>
<H2><A name=startsample></A><font color="#FFFF66">开始一个例子工程</font></H2>
<P>既然我们已经对分隔窗口有了基本的了解,我们就来看看如何创建一个包含分隔窗口的框架窗口。使用WTL向导开始一个新工程,在第一页选择SDI Application并单击Next,在第二页,如下图所示取消工具条并选择不使用视图窗口:</P>
<P><IMG height=387 alt=" [AppWizard pg 2 - 22K] "
src="images/appwiz7.png"
width=477 align=bottom border=0></P>
<P>我们不使用分隔窗口是因为分隔窗口和它的窗格将作为“视图窗口”,在CMainFrame类中添加一个CSplitterWindow类型的数据成员:</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;
};</font></PRE>
<P>接着在OnCreate()中创建分隔窗口并将其设为视图窗口:</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 );
<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();
<SPAN class=cpp-comment>// Position the splitter bar.</SPAN>
m_wndVertSplit.SetSplitterPos ( <SPAN class=cpp-literal>200</SPAN> );
<SPAN class=cpp-keyword>return</SPAN> <SPAN class=cpp-literal>0</SPAN>;
}</font></PRE>
<P>需要注意的是在设置分隔窗口的位置之前要先设置m_hWndClient并调用CFrameWindowImpl::UpdateLayout()函数,UpdateLayout()将分隔窗口设置为初始时的大小。如果跳过这一步,分隔窗口的大小将不确定,可能小于200个象素点的宽度,最终导致SetSplitterPos()出现意想不到的结果。还有一种不调用UpdateLayout()函数的方,就是先得到框架窗口的客户区坐标,然后使用这个客户区坐标替换rcDefault坐标创建分隔窗口。使用这种方式创建的分隔窗口一开始就在正确的初始位置上,随后对位置调整的函数(例如
SetSplitterPos())都可以正常工作。</P>
<P>现在运行我们的程序就可以看到分隔条,即使没有创建任何窗格窗口它仍具有基本的行为。你可以拖动分隔条,用鼠标双击分隔条使其移到窗口的中间位置。</P>
<P><img height=255 alt=" [Empty splitter - 4K] "
src="images/emptysplit7.png"
width=329 align=bottom border=0></P>
<P>为了演示分隔窗口的不同使用方法,我将使用一个CListViewCtrl派生类和一个简单的CRichEditCtrl,下面是从CClipSpyListCtrl类摘录的代码,我们在左边的窗格使用这个类:</P>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -