📄 wtl for mfc programmers, part ii.mht
字号:
T* pT =3D <SPAN =
class=3Dcpp-keyword>static_cast</SPAN><T*>(<SPAN =
class=3Dcpp-keyword>this</SPAN>);
pT->UpdateLayout();
}
=20
bHandled =3D FALSE;
<SPAN class=3Dcpp-keyword>return</SPAN> <SPAN =
class=3Dcpp-literal>1</SPAN>;
}</PRE>
<P>This checks that the window is not being minimized. If not, it=20
delegates to <CODE>UpdateLayout()</CODE>. Here is=20
<CODE>UpdateLayout()</CODE>:</P><PRE><SPAN =
class=3Dcpp-keyword>void</SPAN> UpdateLayout(BOOL bResizeBars =3D TRUE)
{
RECT rect;
=20
GetClientRect(&rect);
=20
<SPAN class=3Dcpp-comment>// position bars and offset their =
dimensions</SPAN>
UpdateBarsPosition(rect, bResizeBars);
=20
<SPAN class=3Dcpp-comment>// resize client window</SPAN>
<SPAN class=3Dcpp-keyword>if</SPAN>(m_hWndClient !=3D NULL)
::SetWindowPos(m_hWndClient, NULL, rect.left, rect.top,
rect.right - rect.left, rect.bottom - rect.top,
SWP_NOZORDER | SWP_NOACTIVATE);
}</PRE>
<P>Notice how the code is referencing <CODE>m_hWndClient</CODE>. =
Since=20
<CODE>m_hWndClient</CODE> is a plain <CODE>HWND</CODE>, it can be =
any=20
window at all. There is no restriction on what kind of window it =
can be,=20
unlike MFC where some features (like splitter windows) require a=20
<CODE>CView</CODE>-derived class. If you go back to=20
<CODE>CMainFrame::OnCreate()</CODE>, you'll see that it creates a =
view=20
window and stores its handle in <CODE>m_hWndClient</CODE>, which =
ensures=20
that the view will be resized properly.</P>
<H2><A name=3Dbacktotheclock></A>Back to the Clock Program</H2>
<P>Now that we've seen some of the frame window class details, =
let's get=20
back to our clock app. The view window can handle the timer and =
drawing,=20
just as <CODE>CMyWindow</CODE> did in the previous sample. Here's =
a=20
partial class definition:</P><PRE><SPAN =
class=3Dcpp-keyword>class</SPAN> CWTLClockView : <SPAN =
class=3Dcpp-keyword>public</SPAN> CWindowImpl<CWTLClockView>
{
<SPAN class=3Dcpp-keyword>public</SPAN>:
DECLARE_WND_CLASS(NULL)
=20
BOOL PreTranslateMessage(MSG* pMsg);
=20
BEGIN_MSG_MAP_EX(CWTLClockView)
MESSAGE_HANDLER(WM_PAINT, OnPaint)
MSG_WM_CREATE(OnCreate)
MSG_WM_DESTROY(OnDestroy)
MSG_WM_TIMER(OnTimer)
MSG_WM_ERASEBKGND(OnEraseBkgnd)
END_MSG_MAP()
};</PRE>
<P>Note that it's fine to mix the ATL message map macros with the =
WTL=20
versions, as long as you change <CODE>BEGIN_MSG_MAP</CODE> to=20
<CODE>BEGIN_MSG_MAP_EX</CODE>. <CODE>OnPaint()</CODE> has all the =
drawing=20
code that was in <CODE>OnEraseBkgnd()</CODE> in the previous =
sample.=20
Here's what the new window looks like:</P>
<P><IMG height=3D215 alt=3D" [Clock app w/view window - 3K] "=20
src=3D"http://www.codeproject.com/wtl/WTL4MFC2/clockapp.png" =
width=3D277=20
align=3Dbottom border=3D0></P>
<P>The last thing we'll add to this app is UI updating. To =
demonstrate=20
this, we'll add a new top-level menu item with <I>Start</I> and=20
<I>Stop</I> commands to stop and start the clock. The <I>Start</I> =
and=20
<I>Stop</I> menu items will be enabled and disabled as =
appropriate.</P>
<H2><A name=3Duiupdating></A>UI Updating</H2>
<P>Several things work together to provide idle-time UI updating: =
a=20
<CODE>CMessageLoop</CODE> object, the mix-in classes=20
<CODE>CIdleHandler</CODE> and <CODE>CUpdateUI</CODE> that=20
<CODE>CMainFrame</CODE> inherits from, and the =
<CODE>UPDATE_UI_MAP</CODE>=20
in <CODE>CMainFrame</CODE>. <CODE>CUpdateUI</CODE> can operate on =
five=20
different types of elements: top-level menu items (in the menu bar =
itself), menu items in popup menus, toolbar buttons, status bar =
panes, and=20
child windows (such as dialog controls). Each type of element has =
a=20
corresponding constant in <CODE>CUpdateUIBase</CODE>:</P>
<UL>
<LI>menu bar items: <CODE>UPDUI_MENUBAR</CODE>=20
<LI>popup menu items: <CODE>UPDUI_MENUPOPUP</CODE>=20
<LI>toolbar buttons: <CODE>UPDUI_TOOLBAR</CODE>=20
<LI>status bar panes: <CODE>UPDUI_STATUSBAR</CODE>=20
<LI>child windows: <CODE>UPDUI_CHILDWINDOW</CODE> </LI></UL>
<P><CODE>CUpdateUI</CODE> can set the enabled state, checked =
state, and=20
text of items (but of course not all items support all states; you =
can't=20
check a child window that's an edit box). It can also set a popup =
menu=20
item to the default state so the text appears in bold.</P>
<P>To hook up UI updating, we need to do four things:</P>
<OL>
<LI>Derive the frame window class from <CODE>CUpdateUI</CODE> =
and=20
<CODE>CIdleHandler</CODE>=20
<LI>Chain messages from <CODE>CMainFrame</CODE> to=20
<CODE>CUpdateUI</CODE>=20
<LI>Add the frame window to the module's list of idle handlers=20
<LI>Fill in the frame window's <CODE>UPDATE_UI_MAP</CODE> =
</LI></OL>
<P>The AppWizard-generated code takes care of the first three =
parts for=20
us, so all that's left is to decide which menu items we'll update, =
and=20
when they will be enabled or disabled.</P>
<H3><A name=3Dnewmenuitems></A>New menu items to control the =
clock</H3>
<P>Let's add a new <I>Clock</I> menu to the menu bar, with two =
items:=20
<CODE>IDC_START</CODE> and <CODE>IDC_STOP</CODE>:</P>
<P><IMG height=3D103 alt=3D" [Clock menu - 2K] "=20
src=3D"http://www.codeproject.com/wtl/WTL4MFC2/clockmenu.png" =
width=3D169=20
align=3Dbottom border=3D0></P>
<P>Then we add an entry in the <CODE>UPDATE_UI_MAP</CODE> for each =
menu=20
item:</P><PRE><SPAN class=3Dcpp-keyword>class</SPAN> CMainFrame : =
<SPAN class=3Dcpp-keyword>public</SPAN> ...
{
<SPAN class=3Dcpp-keyword>public</SPAN>:
<SPAN class=3Dcpp-comment>// ...</SPAN>
BEGIN_UPDATE_UI_MAP(CMainFrame)
<FONT color=3Dred>UPDATE_ELEMENT(IDC_START, UPDUI_MENUPOPUP)
UPDATE_ELEMENT(IDC_STOP, UPDUI_MENUPOPUP)</FONT>
END_UPDATE_UI_MAP()
<SPAN class=3Dcpp-comment>// ...</SPAN>
};</PRE>
<P>Then whenever we want to change the enabled state of either =
item, we=20
call <CODE>CUpdateUI::UIEnable()</CODE>. <CODE>UIEnable()</CODE> =
takes the=20
ID of the item, and a <CODE><SPAN =
class=3Dcpp-keyword>bool</SPAN></CODE>=20
that indicates the enabled state (<CODE><SPAN=20
class=3Dcpp-keyword>true</SPAN></CODE> for enabled, <CODE><SPAN=20
class=3Dcpp-keyword>false</SPAN></CODE> for disabled).</P>
<P>This system works backwards from MFC's=20
<CODE>ON_UPDATE_COMMAND_UI</CODE> system. In MFC, we write =
handlers that=20
MFC calls when it is about to show a menu and needs to know the =
state of=20
the items in the menu. In WTL, we tell WTL when the logical state =
of an=20
item changes. However, in both libraries, the library waits until =
just=20
before the menu is shown to apply the state changes to the =
menu.</P>
<H3><A name=3Dcallinguienable></A>Calling UIEnable()</H3>
<P>Let's go back to the <CODE>OnCreate()</CODE> function and see =
how we=20
set up the initial state of the <I>Clock</I> menu =
items.</P><PRE>LRESULT CMainFrame::OnCreate(UINT <SPAN =
class=3Dcpp-comment>/*uMsg*/</SPAN>, WPARAM <SPAN =
class=3Dcpp-comment>/*wParam*/</SPAN>,=20
LPARAM <SPAN =
class=3Dcpp-comment>/*lParam*/</SPAN>, BOOL& <SPAN =
class=3Dcpp-comment>/*bHandled*/</SPAN>)
{
m_hWndClient =3D m_view.Create(...);
=20
<SPAN class=3Dcpp-comment>// register object for message filtering =
and idle updates</SPAN>
<SPAN class=3Dcpp-comment>// [omitted for clarity]</SPAN>
=20
<FONT color=3Dred><SPAN class=3Dcpp-comment>// Set the initial state =
of the Clock menu items:</SPAN>
UIEnable ( IDC_START, <SPAN class=3Dcpp-keyword>false</SPAN> );
UIEnable ( IDC_STOP, <SPAN class=3Dcpp-keyword>true</SPAN> );</FONT>
=20
<SPAN class=3Dcpp-keyword>return</SPAN> <SPAN =
class=3Dcpp-literal>0</SPAN>;
}</PRE>
<P>And here's what the Clock menu looks like when the app is =
started:</P>
<P><IMG height=3D216 alt=3D" [Start item disabled - 4K] "=20
src=3D"http://www.codeproject.com/wtl/WTL4MFC2/startdisabled.png" =
width=3D294=20
align=3Dbottom border=3D0></P>
<P><CODE>CMainFrame</CODE> now needs handlers for our two new =
items. The=20
handlers will flip the states of the menu items, then call methods =
in the=20
view class to start and stop the clock. This is one area where =
MFC's=20
built-in message routing is sorely missed; if this were an MFC =
app, all=20
the UI updating and command handling could be put entirely in the =
view=20
class. However, in WTL, the frame and view classes have to =
communicate in=20
some way; the menu is owned by the frame window, so the frame gets =
menu-related messages and is responsible for either acting on them =
or=20
sending them to the view class.</P>
<P>The communication <I>could</I> be done through=20
<CODE>PreTranslateMessage()</CODE>, however the =
<CODE>UIEnable()</CODE>=20
calls still have to be done from <CODE>CMainFrame</CODE>.=20
<CODE>CMainFrame</CODE> could get around this by passing its =
<CODE><SPAN=20
class=3Dcpp-keyword>this</SPAN></CODE> pointer to the view class, =
so the=20
view could call <CODE>UIEnable()</CODE> through that pointer. For =
this=20
sample, I have chosen the solution that results in a =
tightly-coupled frame=20
and view, since I find it easier to understand (and =
explain!).</P><PRE><SPAN class=3Dcpp-keyword>class</SPAN> CMainFrame : =
<SPAN class=3Dcpp-keyword>public</SPAN> ...
{
<SPAN class=3Dcpp-keyword>public</SPAN>:
BEGIN_MSG_MAP_EX(CMainFrame)
<SPAN class=3Dcpp-comment>// ...</SPAN>
COMMAND_ID_HANDLER_EX(IDC_START, OnStart)
COMMAND_ID_HANDLER_EX(IDC_STOP, OnStop)
END_MSG_MAP()
=20
<SPAN class=3Dcpp-comment>// ...</SPAN>
<SPAN class=3Dcpp-keyword>void</SPAN> OnStart(UINT uCode, <SPAN =
class=3Dcpp-keyword>int</SPAN> nID, HWND hwndCtrl);
<SPAN class=3Dcpp-keyword>void</SPAN> OnStop(UINT uCode, <SPAN =
class=3Dcpp-keyword>int</SPAN> nID, HWND hwndCtrl);
};
=20
<SPAN class=3Dcpp-keyword>void</SPAN> CMainFrame::OnStart(UINT uCode, =
<SPAN class=3Dcpp-keyword>int</SPAN> nID, HWND hwndCtrl)
{
<SPAN class=3Dcpp-comment>// Enable Stop and disable Start</SPAN>
UIEnable ( IDC_START, <SPAN class=3Dcpp-keyword>false</SPAN> );
UIEnable ( IDC_STOP, <SPAN class=3Dcpp-keyword>true</SPAN> );
=20
<SPAN class=3Dcpp-comment>// Tell the view to start its =
clock.</SPAN>
m_view.StartClock();
}
=20
<SPAN class=3Dcpp-keyword>void</SPAN> CMainFrame::OnStop(UINT uCode, =
<SPAN class=3Dcpp-keyword>int</SPAN> nID, HWND hwndCtrl)
{
<SPAN class=3Dcpp-comment>// Enable Start and disable Stop</SPAN>
UIEnable ( IDC_START, <SPAN class=3Dcpp-keyword>true</SPAN> );
UIEnable ( IDC_STOP, <SPAN class=3Dcpp-keyword>false</SPAN> );
=20
<SPAN class=3Dcpp-comment>// Tell the view to stop its clock.</SPAN>
m_view.StopClock();
}</PRE>
<P>Each handler updates the <I>Clock</I> menu, then calls a method =
in the=20
view, since the view is the class that controls the clock. The=20
<CODE>StartClock()</CODE> and <CODE>StopClock()</CODE> methods are =
not=20
shown here, but you can find them in the sample project.</P>
<H2><A name=3Dfixclassview></A>One Last Note on Message Maps</H2>
<P>If you are using VC 6, you might have noticed that when you =
change=20
<CODE>BEGIN_MSG_MAP</CODE> to <CODE>BEGIN_MSG_MAP_EX</CODE>, =
ClassView=20
goes haywire:</P>
<P><IMG height=3D248 alt=3D" [Messed-up ClassView - 6K] "=20
src=3D"http://www.codeproject.com/wtl/WTL4MFC2/classview.png" =
width=3D350=20
align=3Dbottom border=3D0></P>
<P>This happens because ClassView doesn't understand=20
<CODE>BEGIN_MSG_MAP_EX</CODE>, and it thinks that all the WTL =
message map=20
macros are actually functions. You can fix this by changing the =
macros=20
back to <CODE>BEGIN_MSG_MAP</CODE> and adding these lines at the=20
<B>end</B> of stdafx.h:</P><PRE><SPAN =
class=3Dcpp-preprocessor>#undef BEGIN_MSG_MAP</SPAN>
<SPAN class=3Dcpp-preprocessor>#define BEGIN_MSG_MAP(x) =
BEGIN_MSG_MAP_EX(x)</SPAN></PRE>
<H2><A name=3Dnextup></A>Next Stop, 1995</H2>
<P>We've only just begun to scratch the surface of WTL. In the =
next=20
article, I'll bring our sample clock app up to 1995 UI standards =
and=20
introduce toolbars and status bars. In the meantime, experiment a =
bit with=20
the <CODE>CUpdateUI</CODE> methods; for example, try calling=20
<CODE>UISetCheck()</CODE> instead of <CODE>UIEnable()</CODE> to =
see the=20
different ways the menu items can be changed.</P>
<H2><A name=3Drevisionhistory></A>Revision
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -