📄 wtl for mfc programmers, part ii.mht
字号:
<SPAN class=3Dcpp-preprocessor>#include <atlcrack.h> // WTL =
enhanced msg map macros</SPAN></PRE>
<P><EM>atlapp.h</EM> is the first WTL header you include. It =
contains=20
classes for message handling and <CODE>CAppModule</CODE>, a class =
derived=20
from <CODE>CComModule</CODE>. You should also define=20
<CODE>_WTL_USE_CSTRING</CODE> if you plan on using =
<CODE>CString</CODE>,=20
because <CODE>CString</CODE> is defined in atlmisc.h yet there are =
other=20
headers that come before atlmisc.h which have features that use=20
<CODE>CString</CODE>. Defining <CODE>_WTL_USE_CSTRING</CODE> makes =
atlapp.h forward-declare the <CODE>CString</CODE> class so those =
other=20
headers know what a <CODE>CString</CODE> is.</P>
<P>Next let's define our frame window. SDI windows like ours are =
derived=20
from <CODE>CFrameWindowImpl</CODE>. The window class is defined =
with=20
<CODE>DECLARE_FRAME_WND_CLASS</CODE> instead of=20
<CODE>DECLARE_WND_CLASS</CODE>. Here's the beginning of our window =
definition in MyWindow.h:</P><PRE><SPAN =
class=3Dcpp-keyword>class</SPAN> CMyWindow : <SPAN =
class=3Dcpp-keyword>public</SPAN> CFrameWindowImpl<CMyWindow>
{
<SPAN class=3Dcpp-keyword>public</SPAN>:
DECLARE_FRAME_WND_CLASS(_T(<SPAN class=3Dcpp-string>"First WTL =
window"</SPAN>), IDR_MAINFRAME);
BEGIN_MSG_MAP(CMyWindow)
CHAIN_MSG_MAP(CFrameWindowImpl<CMyWindow>)
END_MSG_MAP()
};</PRE>
<P><CODE>DECLARE_FRAME_WND_CLASS</CODE> takes two parameters, the =
window=20
class name (which can be <CODE>NULL</CODE> to have ATL generate a =
name for=20
you), and a resource ID. WTL will look for an icon, menu, and =
accelerator=20
table with that ID, and load them when the window is created. It =
will also=20
look for a string with that ID and use it as the window title. We =
also=20
chain messages to <CODE>CFrameWindowImpl</CODE> as it has some =
message=20
handlers of its own (most notably <CODE>WM_SIZE</CODE> and=20
<CODE>WM_DESTROY</CODE>).</P>
<P>Now let's look at <CODE>WinMain()</CODE>. It is pretty similar =
to the=20
<CODE>WinMain()</CODE> we had in Part I, the difference is in the =
call to=20
create the main window.</P><PRE><SPAN class=3Dcpp-comment>// =
main.cpp:</SPAN>
<SPAN class=3Dcpp-preprocessor>#include "stdafx.h"</SPAN>
<SPAN class=3Dcpp-preprocessor>#include "MyWindow.h"</SPAN>
=20
CAppModule _Module;
=20
<SPAN class=3Dcpp-keyword>int</SPAN> APIENTRY WinMain ( HINSTANCE =
hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, <SPAN =
class=3Dcpp-keyword>int</SPAN> nCmdShow )
{
_Module.Init ( NULL, hInstance );
=20
CMyWindow wndMain;
MSG msg;
=20
<SPAN class=3Dcpp-comment>// Create the main window</SPAN>
<SPAN class=3Dcpp-keyword>if</SPAN> ( NULL =3D=3D wndMain.CreateEx() =
)
<SPAN class=3Dcpp-keyword>return</SPAN> <SPAN =
class=3Dcpp-literal>1</SPAN>; <SPAN class=3Dcpp-comment>// Window =
creation failed</SPAN>
=20
<SPAN class=3Dcpp-comment>// Show the window</SPAN>
wndMain.ShowWindow ( nCmdShow );
wndMain.UpdateWindow();
=20
<SPAN class=3Dcpp-comment>// Standard Win32 message loop</SPAN>
<SPAN class=3Dcpp-keyword>while</SPAN> ( GetMessage ( &msg, =
NULL, <SPAN class=3Dcpp-literal>0</SPAN>, <SPAN =
class=3Dcpp-literal>0</SPAN> ) > <SPAN class=3Dcpp-literal>0</SPAN> )
{
TranslateMessage ( &msg );
DispatchMessage ( &msg );
}
=20
_Module.Term();
<SPAN class=3Dcpp-keyword>return</SPAN> msg.wParam;
}</PRE>
<P><CODE>CFrameWindowImpl</CODE> has a <CODE>CreateEx()</CODE> =
method that=20
has the most common default values, so we don't need to specify =
any=20
parameters. <CODE>CFrameWindowImpl</CODE> will also handle loading =
resources as explained earlier, so you should make some dummy =
resources=20
now with IDs of <CODE>IDR_MAINFRAME</CODE>, or check out the =
sample code=20
accompanying the article.</P>
<P>If you run this now, you'll see the main frame window, but of =
course it=20
doesn't actually <I>do</I> anything yet. We'll need to add some =
message=20
handlers to do stuff, so now is a good time to cover the WTL =
message map=20
macros.</P>
<H2><A name=3Dwtlmsgmap></A>WTL Message Map Enhancements</H2>
<P>One of the cumbersome and error-prone things when using the =
Win32 API=20
is unpacking parameters from the <CODE>WPARAM</CODE> and=20
<CODE>LPARAM</CODE> data sent with a message. Unfortunately, ATL =
doesn't=20
help much, and we still have to unpack data from all messages =
aside from=20
<CODE>WM_COMMAND</CODE> and <CODE>WM_NOTIFY</CODE>. But WTL comes =
to the=20
rescue here!</P>
<P>WTL's enhanced message map macros are in <EM>atlcrack.h</EM>. =
(The name=20
comes from "message cracker", a term used for similar macros in=20
<EM>windowsx.h</EM>.) To start, change <CODE>BEGIN_MSG_MAP</CODE> =
to=20
<CODE>BEGIN_MSG_MAP_EX</CODE>. The <CODE>_EX</CODE> version =
generates some=20
code that the message crackers use.</P><PRE><SPAN =
class=3Dcpp-keyword>class</SPAN> CMyWindow : <SPAN =
class=3Dcpp-keyword>public</SPAN> CFrameWindowImpl<CMyWindow>
{
<SPAN class=3Dcpp-keyword>public</SPAN>:
<FONT color=3Dred>BEGIN_MSG_MAP_EX(CMyWindow)</FONT>
CHAIN_MSG_MAP(CFrameWindowImpl<CMyWindow>)
END_MSG_MAP()
};</PRE>
<P>For our clock program, we'll need to handle =
<CODE>WM_CREATE</CODE> and=20
set a timer. The WTL message handler for a message is called=20
<CODE>MSG_</CODE> followed by the message name, for example=20
<CODE>MSG_WM_CREATE</CODE>. These macros take just the name of the =
handler, so let's add one for =
<CODE>WM_CREATE</CODE>:</P><PRE><SPAN class=3Dcpp-keyword>class</SPAN> =
CMyWindow : <SPAN class=3Dcpp-keyword>public</SPAN> =
CFrameWindowImpl<CMyWindow>
{
<SPAN class=3Dcpp-keyword>public</SPAN>:
BEGIN_MSG_MAP_EX(CMyWindow)<FONT color=3Dred>
MSG_WM_CREATE(OnCreate)</FONT>
CHAIN_MSG_MAP(CFrameWindowImpl<CMyWindow>)
END_MSG_MAP()
=20
<SPAN class=3Dcpp-comment>// OnCreate(...) ?</SPAN>
};</PRE>
<P>WTL message handlers look a lot like MFC's, where each handler =
has a=20
different prototype depending on what parameters are passed with =
the=20
message. But since we don't have a wizard to write the handler, =
we'll have=20
to find the prototype ourselves. Fortunately, VC can help out. Put =
the=20
cursor on the "MSG_WM_CREATE" text and press F12 to go to the =
definition=20
of that macro. Since this is the first time we've used this =
feature with=20
this project, VC will have to rebuild all first to build its =
browse info=20
database. Once that's done, VC will open atlcrack.h at the =
definition of=20
<CODE>MSG_WM_CREATE</CODE>:</P><PRE><SPAN =
class=3Dcpp-preprocessor>#define MSG_WM_CREATE(func) \</SPAN>
<SPAN class=3Dcpp-keyword>if</SPAN> (uMsg =3D=3D WM_CREATE) \
{ \
SetMsgHandled(TRUE); \
<FONT color=3Dred>lResult =3D =
(LRESULT)func((LPCREATESTRUCT)lParam); \</FONT>
<SPAN class=3Dcpp-keyword>if</SPAN>(IsMsgHandled()) \
<SPAN class=3Dcpp-keyword>return</SPAN> TRUE; \
}</PRE>
<P>The line in red is the important one, it's the actual call to =
the=20
handler, and it tells us the handler returns an =
<CODE>LRESULT</CODE> and=20
takes one parameter, a <CODE>LPCREATESTRUCT</CODE>. Notice that =
there is=20
no <CODE>bHandled</CODE> parameter like the ATL macros use. The=20
<CODE>SetMsgHandled()</CODE> function replaces that parameter; =
I'll=20
explain this more shortly.</P>
<P>Now we can add an <CODE>OnCreate()</CODE> handler to our window =
class:</P><PRE><SPAN class=3Dcpp-keyword>class</SPAN> CMyWindow : =
<SPAN class=3Dcpp-keyword>public</SPAN> =
CFrameWindowImpl<CMyWindow>
{
<SPAN class=3Dcpp-keyword>public</SPAN>:
BEGIN_MSG_MAP_EX(CMyWindow)<FONT color=3Dred>
</FONT> MSG_WM_CREATE(OnCreate)
CHAIN_MSG_MAP(CFrameWindowImpl<CMyWindow>)
END_MSG_MAP()
=20
<FONT color=3Dred>LRESULT OnCreate(LPCREATESTRUCT lpcs)
{
SetTimer ( <SPAN class=3Dcpp-literal>1</SPAN>, <SPAN =
class=3Dcpp-literal>1000</SPAN> );
SetMsgHandled(<SPAN class=3Dcpp-keyword>false</SPAN>);
<SPAN class=3Dcpp-keyword>return</SPAN> <SPAN =
class=3Dcpp-literal>0</SPAN>;
}</FONT>
};</PRE>
<P><CODE>CFrameWindowImpl</CODE> inherits indirectly from=20
<CODE>CWindow</CODE>, so it has all the <CODE>CWindow</CODE> =
functions=20
like <CODE>SetTimer()</CODE>. This makes windowing API calls look =
a lot=20
like MFC code, where you use the various <CODE>CWnd</CODE> methods =
that=20
wrap APIs.</P>
<P>We call <CODE>SetTimer()</CODE> to create a timer that fires =
every=20
second (1000 ms). Since we want to let =
<CODE>CFrameWindowImpl</CODE>=20
handle <CODE>WM_CREATE</CODE> as well, we call =
<CODE>SetMsgHandled(<SPAN=20
class=3Dcpp-keyword>false</SPAN>)</CODE> so that the message gets =
chained to=20
base classes via the <CODE>CHAIN_MSG_MAP</CODE> macro. This call =
replaces=20
the <CODE>bHandled</CODE> parameter that the ATL macros use. (Even =
though=20
<CODE>CFrameWindowImpl</CODE> doesn't handle =
<CODE>WM_CREATE</CODE>,=20
calling <CODE>SetMsgHandled(<SPAN =
class=3Dcpp-keyword>false</SPAN>)</CODE>=20
is a good habit to get into when using base classes, so you don't =
have to=20
remember which messages the base classes handle. This is similar =
to the=20
code that ClassWizard generates; most handlers start or end with a =
call to=20
the base class handler.)</P>
<P>We'll also need a <CODE>WM_DESTROY</CODE> handler so we can =
stop the=20
timer. Going through the same process as before, we find the=20
<CODE>MSG_WM_DESTROY</CODE> macro looks like this:</P><PRE><SPAN =
class=3Dcpp-preprocessor>#define MSG_WM_DESTROY(func) \</SPAN>
<SPAN class=3Dcpp-keyword>if</SPAN> (uMsg =3D=3D WM_DESTROY) \
{ \
SetMsgHandled(TRUE); \
<FONT color=3Dred>func(); \</FONT>
lResult =3D <SPAN class=3Dcpp-literal>0</SPAN>; \
<SPAN class=3Dcpp-keyword>if</SPAN>(IsMsgHandled()) \
<SPAN class=3Dcpp-keyword>return</SPAN> TRUE; \
}</PRE>
<P>So our <CODE>OnDestroy()</CODE> handler takes no parameters and =
returns=20
nothing. <CODE>CFrameWindowImpl</CODE> <I>does</I> handle=20
<CODE>WM_DESTROY</CODE> as well, so in this case we do need to =
call=20
<CODE>SetMsgHandled(<SPAN =
class=3Dcpp-keyword>false</SPAN>)</CODE>:</P><PRE><SPAN =
class=3Dcpp-keyword>class</SPAN> CMyWindow : <SPAN =
class=3Dcpp-keyword>public</SPAN> CFrameWindowImpl<CMyWindow>
{
<SPAN class=3Dcpp-keyword>public</SPAN>:
BEGIN_MSG_MAP_EX(CMyWindow)<FONT color=3Dred>
</FONT> MSG_WM_CREATE(OnCreate)
<FONT color=3Dred>MSG_WM_DESTROY(OnDestroy)</FONT>
CHAIN_MSG_MAP(CFrameWindowImpl<CMyWindow>)
END_MSG_MAP()<BR><BR><BR>
<FONT color=3Dred><SPAN class=3Dcpp-keyword>void</SPAN> OnDestroy()
{
KillTimer(<SPAN class=3Dcpp-literal>1</SPAN>);
SetMsgHandled(<SPAN class=3Dcpp-keyword>false</SPAN>);
}
</FONT>};</PRE>
<P>Next up is our <CODE>WM_TIMER</CODE> handler, which is called =
every=20
second. You should have the hang of the F12 trick by now, so I'll =
just=20
show the handler:</P><PRE><SPAN class=3Dcpp-keyword>class</SPAN> =
CMyWindow : <SPAN class=3Dcpp-keyword>public</SPAN> =
CFrameWindowImpl<CMyWindow>
{
<SPAN class=3Dcpp-keyword>public</SPAN>:
BEGIN_MSG_MAP_EX(CMyWindow)<FONT color=3Dred>
</FONT> MSG_WM_CREATE(OnCreate)
MSG_WM_DESTROY(OnDestroy)<FONT color=3Dred>
MSG_WM_TIMER(OnTimer)
</FONT> CHAIN_MSG_MAP(CFrameWindowImpl<CMyWindow>)
END_MSG_MAP()
=20
<FONT color=3Dred><SPAN class=3Dcpp-keyword>void</SPAN> OnTimer ( =
UINT uTimerID, TIMERPROC pTimerProc )
{
<SPAN class=3Dcpp-keyword>if</SPAN> ( <SPAN =
class=3Dcpp-literal>1</SPAN> !=3D uTimerID )
SetMsgHandled(<SPAN class=3Dcpp-keyword>false</SPAN>);
<SPAN class=3Dcpp-keyword>else</SPAN>
RedrawWindow();
}
</FONT>};</PRE>
<P>This handler just redraws the window so the new time appears in =
the=20
client area. Finally, we handle <CODE>WM_ERASEBKGND</CODE> and in =
that=20
handler, we draw the current time in the upper-left corner of the =
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -