📄 wtl for mfc programmers, part i.mht
字号:
DECLARE_WND_CLASS(_T(<SPAN class=3Dcpp-string>"My Window =
Class"</SPAN>))
};</PRE>
<P>Next comes the message map. ATL message maps are much simpler =
than MFC=20
maps. An ATL map expands into a big switch statement; the switch =
looks for=20
the right handler and calls the corresponding function. The macros =
for the=20
message map are <CODE>BEGIN_MSG_MAP</CODE> and =
<CODE>END_MSG_MAP</CODE>.=20
Let's add an empty map to our window.</P><PRE><SPAN =
class=3Dcpp-keyword>class</SPAN> CMyWindow : <SPAN =
class=3Dcpp-keyword>public</SPAN> CWindowImpl<CMyWindow>
{
<SPAN class=3Dcpp-keyword>public</SPAN>:
DECLARE_WND_CLASS(_T(<SPAN class=3Dcpp-string>"My Window =
Class"</SPAN>))
=20
BEGIN_MSG_MAP(CMyWindow)
END_MSG_MAP()
};</PRE>
<P>I'll cover how to add handlers to the map in the next section. =
Finally,=20
we need to define the <I>window traits</I> for our class. Window =
traits=20
are a combination of window styles and extended window styles that =
are=20
used when creating the window. The styles are specified as =
template=20
parameters so the caller doesn't have to be bothered with getting =
the=20
styles right when it creates our window. Here's a sample traits =
definition=20
using the ATL class <CODE>CWinTraits</CODE>:</P><PRE><SPAN =
class=3Dcpp-keyword>typedef</SPAN> CWinTraits<WS_OVERLAPPEDWINDOW | =
WS_CLIPCHILDREN,
WS_EX_APPWINDOW> CMyWindowTraits;
=20
<SPAN class=3Dcpp-keyword>class</SPAN> CMyWindow : <SPAN =
class=3Dcpp-keyword>public</SPAN> CWindowImpl<CMyWindow, CWindow, =
CMyWindowTraits>
{
<SPAN class=3Dcpp-keyword>public</SPAN>:
DECLARE_WND_CLASS(_T(<SPAN class=3Dcpp-string>"My Window =
Class"</SPAN>))
=20
BEGIN_MSG_MAP(CMyWindow)
END_MSG_MAP()
};</PRE>
<P>The caller <I>can</I> override the styles in the=20
<CODE>CMyWindowTraits</CODE> definition, but generally this is not =
necessary. ATL also has a few predefined <CODE>CWinTraits</CODE>=20
specializations, one of which is perfect for top-level windows =
like ours,=20
<CODE>CFrameWinTraits</CODE>:</P><PRE><SPAN =
class=3Dcpp-keyword>typedef</SPAN> CWinTraits<WS_OVERLAPPEDWINDOW | =
WS_CLIPCHILDREN |
WS_CLIPSIBLINGS,
WS_EX_APPWINDOW | WS_EX_WINDOWEDGE>
CFrameWinTraits;</PRE>
<H3><A name=3Dmsgmap></A>Filling in the message map</H3>
<P>The ATL message map is one area that is lacking in=20
developer-friendliness, and one area that WTL greatly improves on. =
ClassView does at least give you the ability to add message =
handers,=20
however ATL doesn't have message-specific macros and automatic =
parameter=20
unpacking like MFC does. In ATL, there are just three types of =
message=20
handlers, one for <CODE>WM_NOTIFY</CODE>, one for =
<CODE>WM_COMMAND</CODE>,=20
and one for everything else. Let's start by adding handlers for=20
<CODE>WM_CLOSE</CODE> and <CODE>WM_DESTROY</CODE> to our =
window.</P><PRE><SPAN class=3Dcpp-keyword>class</SPAN> CMyWindow : <SPAN =
class=3Dcpp-keyword>public</SPAN> CWindowImpl<CMyWindow, CWindow, =
CFrameWinTraits>
{
<SPAN class=3Dcpp-keyword>public</SPAN>:
DECLARE_WND_CLASS(_T(<SPAN class=3Dcpp-string>"My Window =
Class"</SPAN>))
=20
BEGIN_MSG_MAP(CMyWindow)
MESSAGE_HANDLER(WM_CLOSE, OnClose)
MESSAGE_HANDLER(WM_DESTROY, OnDestroy)
END_MSG_MAP()
=20
LRESULT OnClose(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& =
bHandled)
{
DestroyWindow();
<SPAN class=3Dcpp-keyword>return</SPAN> <SPAN =
class=3Dcpp-literal>0</SPAN>;
}
=20
LRESULT OnDestroy(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& =
bHandled)
{
PostQuitMessage(<SPAN class=3Dcpp-literal>0</SPAN>);
<SPAN class=3Dcpp-keyword>return</SPAN> <SPAN =
class=3Dcpp-literal>0</SPAN>;
}
};</PRE>
<P>You'll notice that the handlers get the raw <CODE>WPARAM</CODE> =
and=20
<CODE>LPARAM</CODE> values; you have to unpack them yourself when =
a=20
message uses those parameters. There is also a fourth parameter,=20
<CODE>bHandled</CODE>. This parameter is set to <CODE>TRUE</CODE> =
by ATL=20
before the handler is called. If you want ATL's default=20
<CODE>WindowProc()</CODE> to handle the message as well after your =
handler=20
returns, you set <CODE>bHandled</CODE> to <CODE>FALSE</CODE>. This =
is=20
different than MFC, where you have to explicitly call the =
base-class=20
implementation of a message handler.</P>
<P>Let's add a <CODE>WM_COMMAND</CODE> handler as well. Assuming =
our=20
window's menu has an About item with ID =
<CODE>IDC_ABOUT</CODE>:</P><PRE><SPAN class=3Dcpp-keyword>class</SPAN> =
CMyWindow : <SPAN class=3Dcpp-keyword>public</SPAN> =
CWindowImpl<CMyWindow, CWindow, CFrameWinTraits>
{
<SPAN class=3Dcpp-keyword>public</SPAN>:
DECLARE_WND_CLASS(_T(<SPAN class=3Dcpp-string>"My Window =
Class"</SPAN>))
=20
BEGIN_MSG_MAP(CMyWindow)
MESSAGE_HANDLER(WM_CLOSE, OnClose)
MESSAGE_HANDLER(WM_DESTROY, OnDestroy)
COMMAND_ID_HANDLER(IDC_ABOUT, OnAbout)
END_MSG_MAP()
=20
LRESULT OnClose(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& =
bHandled)
{
DestroyWindow();
<SPAN class=3Dcpp-keyword>return</SPAN> <SPAN =
class=3Dcpp-literal>0</SPAN>;
}
=20
LRESULT OnDestroy(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& =
bHandled)
{
PostQuitMessage(<SPAN class=3Dcpp-literal>0</SPAN>);
<SPAN class=3Dcpp-keyword>return</SPAN> <SPAN =
class=3Dcpp-literal>0</SPAN>;
}
=20
LRESULT OnAbout(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& =
bHandled)
{
MessageBox ( _T(<SPAN class=3Dcpp-string>"Sample ATL =
window"</SPAN>), _T(<SPAN class=3Dcpp-string>"About MyWindow"</SPAN>) );
<SPAN class=3Dcpp-keyword>return</SPAN> <SPAN =
class=3Dcpp-literal>0</SPAN>;
}
};</PRE>
<P>Notice that the <CODE>COMMAND_HANDLER</CODE> macro does unpack =
the=20
message parameters for you. The <CODE>NOTIFY_HANDLER</CODE> macro =
is=20
similar, and unpacks the <CODE>WM_NOTIFY</CODE> message =
parameters.</P>
<H2><A name=3Dadvmsgmap></A>Advanced Message Maps and Mix-in =
Classes</H2>
<P>One major difference in ATL is that <I>any</I> C++ class can =
handle=20
messages, unlike MFC where message-handling duties are split =
between=20
<CODE>CWnd</CODE> and <CODE>CCmdTarget</CODE>, plus several =
classes that=20
have a <CODE>PreTranslateMessage()</CODE> method. This ability =
lets us=20
write what are commonly called "mix-in" classes, so that we can =
add=20
features to our window simply by adding classes to the inheritance =
list.</P>
<P>A base class with a message map is usually a template that =
takes the=20
derived class name as a template parameter, so it can access =
members of=20
the derived class like <CODE>m_hWnd</CODE> (the <CODE>HWND</CODE> =
member=20
in <CODE>CWindow</CODE>). Let's look at a mix-in class that paints =
the=20
window background by handling =
<CODE>WM_ERASEBKGND</CODE>.</P><PRE><SPAN =
class=3Dcpp-keyword>template</SPAN> <<SPAN =
class=3Dcpp-keyword>class</SPAN> T, COLORREF t_crBrushColor>
<SPAN class=3Dcpp-keyword>class</SPAN> CPaintBkgnd : <SPAN =
class=3Dcpp-keyword>public</SPAN> CMessageMap
{
<SPAN class=3Dcpp-keyword>public</SPAN>:
CPaintBkgnd() { m_hbrBkgnd =3D CreateSolidBrush(t_crBrushColor); }
~CPaintBkgnd() { DeleteObject ( m_hbrBkgnd ); }
=20
BEGIN_MSG_MAP(CPaintBkgnd)
MESSAGE_HANDLER(WM_ERASEBKGND, OnEraseBkgnd)
END_MSG_MAP()
=20
LRESULT OnEraseBkgnd(UINT uMsg, WPARAM wParam, LPARAM lParam, =
BOOL& bHandled)
{
T* pT =3D <SPAN =
class=3Dcpp-keyword>static_cast</SPAN><T*>(<SPAN =
class=3Dcpp-keyword>this</SPAN>);
HDC dc =3D (HDC) wParam;
RECT rcClient;
=20
pT->GetClientRect ( &rcClient );
FillRect ( dc, &rcClient, m_hbrBkgnd );
<SPAN class=3Dcpp-keyword>return</SPAN> <SPAN =
class=3Dcpp-literal>1</SPAN>; <SPAN class=3Dcpp-comment>// we painted =
the background</SPAN>
}
=20
<SPAN class=3Dcpp-keyword>protected</SPAN>:
HBRUSH m_hbrBkgnd;
};</PRE>
<P>Let's go through this new class. First, =
<CODE>CPaintBkgnd</CODE> has=20
two template parameters: the name of the derived class that is =
using=20
<CODE>CPaintBkgnd</CODE>, and a color to use for the background. =
(The=20
<CODE>t_</CODE> prefix is often used for template parameters that =
are=20
plain values.) <CODE>CPaintBkgnd</CODE> also derived from the ATL =
class=20
<CODE>CMessageMap</CODE>. This is not strictly necessary, as the=20
<CODE>BEGIN_MSG_MAP</CODE> macro is all that is required for the =
class to=20
handle messages, so when you see other code samples you might not =
see that=20
used as a base class.</P>
<P>The constructor and destructor are pretty simple, they create =
and=20
destroy a brush of the color passed as =
<CODE>t_crBrushColor</CODE>. Next=20
comes the message map, which handles <CODE>WM_ERASEBKGND</CODE>. =
Finally,=20
there's the <CODE>OnEraseBkgnd()</CODE> handler which fills in the =
window=20
with the brush created in the constructor. There are two things of =
note=20
going on in <CODE>OnEraseBkgnd()</CODE>. First, it uses the =
derived=20
class's window functions (namely <CODE>GetClientRect()</CODE>). =
How do we=20
know that there even <I>is</I> a <CODE>GetClientRect()</CODE> in =
the=20
derived class? The code wouldn't compile if there isn't! The =
compiler=20
ensures that the derived class <CODE>T</CODE> is derived from=20
<CODE>CWindow</CODE>. Second, <CODE>OnEraseBkgnd()</CODE> has to =
unpack=20
the device context from <CODE>wParam</CODE>. (WTL takes care of =
this,=20
we'll get there eventually, promise!)</P>
<P>To use this mix-in class with our window, we do two things. =
First, we=20
add it to the inheritance list:</P><PRE><SPAN =
class=3Dcpp-keyword>class</SPAN> CMyWindow : <SPAN =
class=3Dcpp-keyword>public</SPAN> CWindowImpl<CMyWindow, CWindow, =
CFrameWinTraits>,
<FONT color=3Dred><SPAN =
class=3Dcpp-keyword>public</SPAN> CPaintBkgnd<CMyWindow, RGB(<SPAN =
class=3Dcpp-literal>0</SPAN>,<SPAN class=3Dcpp-literal>0</SPAN>,<SPAN =
class=3Dcpp-literal>255</SPAN>)></FONT></PRE>
<P>Second, we need to get <CODE>CMyWindow</CODE> to pass messages =
along to=20
<CODE>CPaintBkgnd</CODE>. This is called <I>chaining</I> message =
maps. In=20
the <CODE>CMyWindow</CODE> message map, we add the=20
<CODE>CHAIN_MSG_MAP</CODE> macro:</P><PRE><SPAN =
class=3Dcpp-keyword>class</SPAN> CMyWindow : <SPAN =
class=3Dcpp-keyword>public</SPAN> CWindowImpl<CMyWindow, CWindow, =
CFrameWinTraits>,
<SPAN class=3Dcpp-keyword>public</SPAN> =
CPaintBkgnd<CMyWindow, RGB(<SPAN class=3Dcpp-literal>0</SPAN>,<SPAN =
class=3Dcpp-literal>0</SPAN>,<SPAN class=3Dcpp-literal>255</SPAN>)>=20
{
...
<FONT color=3Dred><SPAN class=3Dcpp-keyword>typedef</SPAN> =
CPaintBkgnd<CMyWindow, RGB(<SPAN class=3Dcpp-literal>0</SPAN>,<SPAN =
class=3Dcpp-literal>0</SPAN>,<SPAN class=3Dcpp-literal>255</SPAN>)> =
CPaintBkgndBase;</FONT>
=20
BEGIN_MSG_MAP(CMyWindow)
MESSAGE_HANDLER(WM_CLOSE, OnClose)
MESSAGE_HANDLER(WM_DESTROY, OnDestroy)
COMMAND_HANDLER(IDC_ABOUT, OnAbout)
<FONT color=3Dred>CHAIN_MSG_MAP(CPaintBkgndBase)</FONT>
END_MSG_MAP()
...
};</PRE>
<P>Any messages that get through the <CODE>CMyWindow</CODE> map =
unhandled=20
are then passed on to the map in <CODE>CPaintBkgnd</CODE>. Note =
that=20
<CODE>WM_CLOSE</CODE>, <CODE>WM_DESTROY</CODE>, and =
<CODE>IDC_ABOUT</CODE>=20
will <B>not</B> be chained, because once those are handled, the =
message=20
map search ends. The typedef is necessary because=20
<CODE>CHAIN_MSG_MAP</CODE> is a preprocessor macro that takes one=20
parameter; if we wrote <CODE>CPaintBkgnd<CMyWindow, RGB(<SPAN=20
class=3Dcpp-literal>0</SPAN>,<SPAN =
class=3Dcpp-literal>0</SPAN>,<SPAN=20
class=3Dcpp-literal>255</SPAN>)></CODE> as the parameter, the =
commas=20
would make the preprocessor think that we're calling it with more =
than one=20
parameter.</P>
<P>You could conceivably have several mix-in classes in the =
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -