📄 wtl for mfc programmers, part vii.mht
字号:
as the=20
view window:</P><PRE>LRESULT CMainFrame::OnCreate ( LPCREATESTRUCT =
lpcs )
{
<SPAN class=3Dcpp-comment>//...</SPAN>
<SPAN class=3Dcpp-comment>// Create the splitter window</SPAN>
<SPAN class=3Dcpp-keyword>const</SPAN> DWORD dwSplitStyle =3D WS_CHILD | =
WS_VISIBLE |
WS_CLIPCHILDREN | WS_CLIPSIBLINGS,
dwSplitExStyle =3D WS_EX_CLIENTEDGE;
=20
m_wndVertSplit.Create ( *<SPAN class=3Dcpp-keyword>this</SPAN>, =
rcDefault, NULL,
dwSplitStyle, dwSplitExStyle );
=20
<SPAN class=3Dcpp-comment>// Set the splitter as the client area =
window, and resize</SPAN>
<SPAN class=3Dcpp-comment>// the splitter to match the frame =
size.</SPAN>
m_hWndClient =3D m_wndVertSplit;
UpdateLayout();
=20
<SPAN class=3Dcpp-comment>// Position the splitter bar.</SPAN>
m_wndVertSplit.SetSplitterPos ( <SPAN class=3Dcpp-literal>200</SPAN> =
);
=20
<SPAN class=3Dcpp-keyword>return</SPAN> <SPAN =
class=3Dcpp-literal>0</SPAN>;
}</PRE>
<P>Note that you need to set <CODE>m_hWndClient</CODE> and call=20
<CODE>CFrameWindowImpl::UpdateLayout()</CODE> before setting the =
splitter=20
position. <CODE>UpdateLayout()</CODE> resizes the splitter window =
to its=20
initial size. If you skip that step, the splitter's size isn't =
under your=20
control and it might be smaller than 200 pixels wide. The end =
result would=20
be that <CODE>SetSplitterPos()</CODE> wouldn't have the effect you =
wanted.=20
An alternative to calling <CODE>UpdateLayout()</CODE> is to get =
the client=20
<CODE>RECT</CODE> of the frame window, and use that =
<CODE>RECT</CODE> when=20
creating the splitter, instead of <CODE>rcDefault</CODE>. This =
way, you=20
create the splitter in its initial position, and all subsequent =
methods=20
dealing with position (like <CODE>SetSplitterPos()</CODE>) will =
work=20
correctly.</P>
<P>If you run the app now, you'll see the splitter in action. Even =
without=20
creating anything for the panes, the basic behavior is there. You =
can drag=20
the bar, and double-clicking it moves the bar to the center.</P>
<P><IMG height=3D255 alt=3D" [Empty splitter - 4K] "=20
src=3D"http://www.codeproject.com/wtl/WTL4MFC7/emptysplit.png" =
width=3D329=20
align=3Dbottom border=3D0></P>
<P>To demonstrate different ways of managing the pane windows, =
I'll use=20
one <CODE>CListViewCtrl</CODE>-derived class, and a plain=20
<CODE>CRichEditCtrl</CODE>. Here's a snippet from the=20
<CODE>CClipSpyListCtrl</CODE> class, which we'll use in the left =
pane:</P><PRE><SPAN class=3Dcpp-keyword>typedef</SPAN> =
CWinTraitsOR<LVS_REPORT | LVS_SINGLESEL | LVS_NOSORTHEADER>
CListTraits;
=20
<SPAN class=3Dcpp-keyword>class</SPAN> CClipSpyListCtrl :
<SPAN class=3Dcpp-keyword>public</SPAN> =
CWindowImpl<CClipSpyListCtrl, CListViewCtrl, CListTraits>,
<SPAN class=3Dcpp-keyword>public</SPAN> =
CCustomDraw<CClipSpyListCtrl>
{
<SPAN class=3Dcpp-keyword>public</SPAN>:
DECLARE_WND_SUPERCLASS(NULL, WC_LISTVIEW)
=20
BEGIN_MSG_MAP(CClipSpyListCtrl)
MSG_WM_CHANGECBCHAIN(OnChangeCBChain)
MSG_WM_DRAWCLIPBOARD(OnDrawClipboard)
MSG_WM_DESTROY(OnDestroy)
CHAIN_MSG_MAP_ALT(CCustomDraw<CClipSpyListCtrl>, <SPAN =
class=3Dcpp-literal>1</SPAN>)
DEFAULT_REFLECTION_HANDLER()
END_MSG_MAP()
<SPAN class=3Dcpp-comment>//...</SPAN>
};</PRE>
<P>If you've been following the previous articles, you should have =
no=20
trouble reading this class. It handles =
<CODE>WM_CHANGECBCHAIN</CODE> to=20
know when other clipboard viewers come and go, and=20
<CODE>WM_DRAWCLIPBOARD</CODE> to know when the contents of the =
clipboard=20
change.</P>
<P>Since the pane windows will exist for the life of the app, we =
can use=20
member variables in <CODE>CMainFrame</CODE> for them as =
well:</P><PRE><SPAN class=3Dcpp-keyword>class</SPAN> CMainFrame : <SPAN =
class=3Dcpp-keyword>public</SPAN> ...
{
<SPAN class=3Dcpp-comment>//...</SPAN>
<SPAN class=3Dcpp-keyword>protected</SPAN>:
CSplitterWindow m_wndVertSplit;
<B>CClipSpyListCtrl m_wndFormatList;
CRichEditCtrl m_wndDataViewer;</B>
};</PRE>
<H2><A name=3Dcreatingpanes></A>Creating windows in the panes</H2>
<P>Now that we have member variables for the splitter and the =
panes,=20
filling in the splitter is a simple matter. After creating the =
splitter=20
window, we create both child windows, using the splitter as their=20
parent:</P><PRE>LRESULT CMainFrame::OnCreate ( LPCREATESTRUCT lpcs =
)
{
<SPAN class=3Dcpp-comment>//...</SPAN>
<SPAN class=3Dcpp-comment>// Create the splitter window</SPAN>
<SPAN class=3Dcpp-keyword>const</SPAN> DWORD dwSplitStyle =3D WS_CHILD | =
WS_VISIBLE |
WS_CLIPCHILDREN | WS_CLIPSIBLINGS,
dwSplitExStyle =3D WS_EX_CLIENTEDGE;
=20
m_wndVertSplit.Create ( *<SPAN class=3Dcpp-keyword>this</SPAN>, =
rcDefault, NULL,
dwSplitStyle, dwSplitExStyle );
=20
<B><SPAN class=3Dcpp-comment>// Create the left pane (list of clip =
formats)</SPAN>
m_wndFormatList.Create ( m_wndVertSplit, rcDefault );
=20
<SPAN class=3Dcpp-comment>// Create the right pane (rich edit =
ctrl)</SPAN>
<SPAN class=3Dcpp-keyword>const</SPAN> DWORD dwRichEditStyle =3D=20
WS_CHILD | WS_VISIBLE | WS_HSCROLL | WS_VSCROLL |
ES_READONLY | ES_AUTOHSCROLL | ES_AUTOVSCROLL | =
ES_MULTILINE;
=20
m_wndDataViewer.Create ( m_wndVertSplit, rcDefault,=20
NULL, dwRichEditStyle );
m_wndDataViewer.SetFont ( AtlGetStockFont(ANSI_FIXED_FONT) );</B>
=20
<SPAN class=3Dcpp-comment>// Set the splitter as the client area =
window, and resize</SPAN>
<SPAN class=3Dcpp-comment>// the splitter to match the frame =
size.</SPAN>
m_hWndClient =3D m_wndVertSplit;
UpdateLayout();
=20
m_wndVertSplit.SetSplitterPos ( <SPAN class=3Dcpp-literal>200</SPAN> =
);
=20
<SPAN class=3Dcpp-keyword>return</SPAN> <SPAN =
class=3Dcpp-literal>0</SPAN>;
}</PRE>
<P>Notice that both <CODE>Create()</CODE> calls use=20
<CODE>m_wndVertSplit</CODE> as the parent window. The =
<CODE>RECT</CODE>=20
parameter is not important, since the splitter will resize both =
pane=20
windows as necessary, so we can use =
<CODE>CWindow::rcDefault</CODE>.</P>
<P>The last step is to pass the <CODE>HWND</CODE>s of the panes to =
the=20
splitter. This also has to come before <CODE>UpdateLayout()</CODE> =
so all=20
the windows end up the correct size.</P><PRE>LRESULT =
CMainFrame::OnCreate ( LPCREATESTRUCT lpcs )
{
<SPAN class=3Dcpp-comment>//...</SPAN>
m_wndDataViewer.SetFont ( AtlGetStockFont(ANSI_FIXED_FONT) );
=20
<B><SPAN class=3Dcpp-comment>// Set up the splitter panes</SPAN>
m_wndVertSplit.SetSplitterPanes ( m_wndFormatList, m_wndDataViewer =
);</B>
=20
<SPAN class=3Dcpp-comment>// Set the splitter as the client area =
window, and resize</SPAN>
<SPAN class=3Dcpp-comment>// the splitter to match the frame =
size.</SPAN>
m_hWndClient =3D m_wndVertSplit;
UpdateLayout();
=20
m_wndVertSplit.SetSplitterPos ( <SPAN class=3Dcpp-literal>200</SPAN> =
);
=20
<SPAN class=3Dcpp-keyword>return</SPAN> <SPAN =
class=3Dcpp-literal>0</SPAN>;
}</PRE>
<P>And here's what the result looks like, after the list control =
has had=20
some columns added:</P>
<P><IMG height=3D259 alt=3D" [Splitter w/panes - 4K] "=20
src=3D"http://www.codeproject.com/wtl/WTL4MFC7/panes.png" =
width=3D320=20
align=3Dbottom border=3D0></P>
<P>Note that the splitter puts no restrictions on what windows can =
go in=20
the panes, unlike MFC where you are supposed to use =
<CODE>CView</CODE>s.=20
The pane windows should have at least the <CODE>WS_CHILD</CODE> =
style, but=20
beyond that you're pretty much free to use anything.</P>
<H2><A name=3Dmessagerouting></A>Message Routing</H2>
<P>Since we now have another window sitting between the main frame =
and the=20
pane windows, you might have wondered how notification messages =
work.=20
Specifically, how can the main frame receive =
<CODE>NM_CUSTOMDRAW</CODE>=20
notifications so it can reflect them to the list? The answer can =
be found=20
in the <CODE>CSplitterWindowImpl</CODE> message map:</P><PRE> =
BEGIN_MSG_MAP(thisClass)
MESSAGE_HANDLER(WM_ERASEBKGND, OnEraseBackground)
MESSAGE_HANDLER(WM_SIZE, OnSize)
CHAIN_MSG_MAP(baseClass)
FORWARD_NOTIFICATIONS()
END_MSG_MAP()</PRE>
<P>The <CODE>FORWARD_NOTIFICATIONS()</CODE> macro at the end is =
the=20
important one. Recall from <A=20
=
href=3D"http://www.codeproject.com/wtl/WTL4MFC4.htm#reflnotify">Part =
IV</A>=20
that there are several notification messages which are always sent =
to the=20
parent of a child window. What =
<CODE>FORWARD_NOTIFICATIONS()</CODE> does=20
is resend the message to <I>the splitter's</I> parent window. So =
when the=20
list sends a <CODE>WM_NOTIFY</CODE> message to the splitter (the =
list's=20
parent), the splitter in turn sends the <CODE>WM_NOTIFY</CODE> to =
the main=20
frame (the splitter's parent). When the main frame reflects the =
message,=20
it is sent back to the window that generated the =
<CODE>WM_NOTIFY</CODE> in=20
the first place, which is the list, so the splitter doesn't get =
involved=20
in reflection.</P>
<P>The result of all this is that notification messages sent =
between the=20
main frame and the list don't get affected by the presence of the =
splitter=20
window. This makes it rather easy to add or remove splitters, =
because the=20
child window classes won't have to be changed at all for their =
message=20
processing to continue working.</P>
<H2><A name=3Dpanecont></A>Pane Containers</H2>
<P>WTL also supports a widget like the one in the left pane of =
Explorer,=20
called a <I>pane container</I>. This control provides a header =
area with=20
text, and optionally a Close button:</P>
<P><IMG height=3D150 alt=3D" [Explorer pane container - 3K] "=20
src=3D"http://www.codeproject.com/wtl/WTL4MFC7/explorerpc.png" =
width=3D199=20
align=3Dbottom border=3D0></P>
<P>The pane container manages a child window, just as the splitter =
manages=20
two pane windows. When the container is resized, the child is=20
automatically resized to match the space inside the container.</P>
<H3><A name=3Dpcclasses></A>Classes</H3>
<P>There are two classes in the implementation of pane containers, =
both in=20
atlctrlx.h: <CODE>CPaneContainerImpl</CODE> and=20
<CODE>CPaneContainer</CODE>. <CODE>CPaneContainerImpl</CODE> is a=20
<CODE>CWindowImpl</CODE>-derived class that contains the complete=20
implementation; <CODE>CPaneContainer</CODE> provides just a window =
class=20
name. Unless you want to override any methods to change how the =
container=20
is drawn, you will always use <CODE>CPaneContainer</CODE>.</P>
<H3><A name=3Dpcmethods></A>Basic methods</H3><PRE>HWND Create(
HWND hWndParent, LPCTSTR lpstrTitle =3D NULL,
DWORD dwStyle =3D WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | =
WS_CLIPCHILDREN,
DWORD dwExStyle =3D <SPAN class=3Dcpp-literal>0</SPAN>, UINT nID =3D =
<SPAN class=3Dcpp-literal>0</SPAN>, LPVOID lpCreateParam =3D NULL)
HWND Create(
HWND hWndParent, UINT uTitleID,
DWORD dwStyle =3D WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | =
WS_CLIPCHILDREN,
DWORD dwExStyle =3D <SPAN class=3Dcpp-literal>0</SPAN>, UINT nID =3D =
<SPAN class=3Dcpp-literal>0</SPAN>, LPVOID lpCreateParam =3D NULL)</PRE>
<P>Creating a <CODE>CPaneContainer</CODE> is similar to creating =
other=20
child windows. There are two <CODE>Create()</CODE> methods that =
differ in=20
just the second parameter. In the first version, you pass a string =
that=20
will be used for the title text drawn in the header. In the second =
method,=20
you pass the ID of a string table entry. The defaults for the =
remaining=20
parameters are usually sufficient.</P><PRE>DWORD =
SetPaneContainerExtendedStyle(DWORD dwExtendedStyle, DWORD dwMask =3D =
<SPAN class=3Dcpp-literal>0</SPAN>)
DWORD GetPaneContainerExtendedStyle()</PRE>
<P><CODE>CPaneContainer</CODE> has additional extended styles that =
control=20
the close button and the layout of the container:</P>
<UL>
<LI><CODE>PANECNT_NOCLOSEBUTTON</CODE>: Set this style to remove =
the=20
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -