📄 wtl for mfc programmers, part ii - wtl gui base classes - wtl.htm
字号:
}
</FONT><font color="#0033FF">};</font></PRE>
<P>接下来是响应WM_TIMER消息的处理函数,它每秒钟被调用一次。你应该知道怎样使用F12键的窍门了,所以我直接给出响应函数的代码:</P>
<PRE><SPAN class=cpp-keyword><font color="#0000FF">class</font></SPAN><font color="#0000FF"> CMyWindow : <SPAN class=cpp-keyword>public</SPAN> CFrameWindowImpl<CMyWindow>
{
<SPAN class=cpp-keyword>public</SPAN>:
BEGIN_MSG_MAP_EX(CMyWindow)
MSG_WM_CREATE(OnCreate)
MSG_WM_DESTROY(OnDestroy)</font><FONT color=red>
MSG_WM_TIMER(OnTimer)
</FONT><font color="#0000CC"> CHAIN_MSG_MAP(CFrameWindowImpl<CMyWindow>)
END_MSG_MAP()</font>
<FONT color=red><SPAN class=cpp-keyword>void</SPAN> OnTimer ( UINT uTimerID, TIMERPROC pTimerProc )
{
<SPAN class=cpp-keyword>if</SPAN> ( <SPAN class=cpp-literal>1</SPAN> != uTimerID )
SetMsgHandled(<SPAN class=cpp-keyword>false</SPAN>);
<SPAN class=cpp-keyword>else</SPAN>
RedrawWindow();
}
</FONT><font color="#0000CC">};</font></PRE>
<P>这个响应函数只是在每次定时器触发时重画窗口的客户区。最后我们要响应WM_ERASEBKGND消息,在窗口客户区的左上角显示当前的时间。</P>
<PRE><SPAN class=cpp-keyword><font color="#0000FF">class</font></SPAN><font color="#0000FF"> CMyWindow : <SPAN class=cpp-keyword>public</SPAN> CFrameWindowImpl<CMyWindow>
{
<SPAN class=cpp-keyword>public</SPAN>:
BEGIN_MSG_MAP_EX(CMyWindow)
MSG_WM_CREATE(OnCreate)
MSG_WM_DESTROY(OnDestroy)
MSG_WM_TIMER(OnTimer)</font><FONT color=red>
MSG_WM_ERASEBKGND(OnEraseBkgnd)
</FONT><font color="#0000FF"> CHAIN_MSG_MAP(CFrameWindowImpl<CMyWindow>)
END_MSG_MAP()</font>
<FONT color=red> LRESULT OnEraseBkgnd ( HDC hdc )
{
CDCHandle dc(hdc);
CRect rc;
SYSTEMTIME st;
CString sTime;
<SPAN class=cpp-comment>// Get our window's client area.</SPAN>
GetClientRect ( rc );
<SPAN class=cpp-comment>// Build the string to show in the window.</SPAN>
GetLocalTime ( &st );
sTime.Format ( _T(<SPAN class=cpp-string>"The time is %d:%02d:%02d"</SPAN>),
st.wHour, st.wMinute, st.wSecond );
<SPAN class=cpp-comment>// Set up the DC and draw the text.</SPAN>
dc.SaveDC();
dc.SetBkColor ( RGB(<SPAN class=cpp-literal>255</SPAN>,<SPAN class=cpp-literal>153</SPAN>,<SPAN class=cpp-literal>0</SPAN>);
dc.SetTextColor ( RGB(<SPAN class=cpp-literal>0</SPAN>,<SPAN class=cpp-literal>0</SPAN>,<SPAN class=cpp-literal>0</SPAN>) );
dc.ExtTextOut ( <SPAN class=cpp-literal>0</SPAN>, <SPAN class=cpp-literal>0</SPAN>, ETO_OPAQUE, rc, sTime,
sTime.GetLength(), NULL );
<SPAN class=cpp-comment>// Restore the DC.</SPAN>
dc.RestoreDC(-<SPAN class=cpp-literal>1</SPAN>);
<SPAN class=cpp-keyword>return</SPAN> <SPAN class=cpp-literal>1</SPAN>; <SPAN class=cpp-comment>// We erased the background (ExtTextOut did it)</SPAN>
}
</FONT><font color="#0000FF">};</font></PRE>
<P>这个消息处理函数不仅使用了CRect和CString类,还使用了一个GDI包装类CDCHandle。对于CString类我想说的是它等同与MFC的CString类,我在后面的文章中还会介绍这些包装类,现在你只需要知道CDCHandle是对HDC的简单封装就行了,使用方法与MFC的CDC类相似,只是CDCHandle的实例在超出作用域后不会销毁它所操作的设备上下文。</P>
<P>所有的工作完成了,现在看看我们的窗口是什么样子:</P>
<P><IMG height=208 alt=" [clock window - 4K] "
src="images/firstwin2.png"
width=283 align=bottom border=0></P>
<P>例子代码中还使用了WM_COMMAND响应菜单消息,在这里我不作介绍,但是你可以查看例子代码,看看WTL的COMMAND_ID_HANDLER_EX宏是如何工作的。</P>
<H2><A name=appwizard></A><font color="#FFFF66">从</font><font color="#FFFF66">WTL的应用程序生成向导能得到什么</font></H2>
<P>WTL的发布版本附带一个很棒的应用程序生成向导,让我们以一个SDI 应用为例看看它有什么特性。</P>
<H3><A name=thruwizard></A><font color="#FFFF66">使用向导的整个过程</font></H3>
<P>在VC的IDE环境下单击File|New菜单,从列表中选择ATL/WTL AppWizard,我们要重写时钟程序,所以用WTLClock作为项目的名字:</P>
<P><IMG height=403 alt=" [AppWiz screen 1 - 14K] "
src="images/appwiz1.png"
width=561 align=bottom border=0></P>
<P>在下一页你可以选择项目的类型,SDI,MDI或者是基于对话框的应用,当然还有其它选项,如下图所示设置这些选项,然后点击“下一步”:</P>
<P><IMG height=387 alt=" [AppWiz screen 2 - 22K] "
src="images/appwiz2.png"
width=477 align=bottom border=0></P>
<P>在最后一页你可以选择是否使用toolbar,rebar和status bar,为了简单起见,取消这些选项并单击“结束”。</P>
<P><IMG height=387 alt=" [AppWiz screen 3 - 21K] "
src="images/appwiz3.png"
width=477 align=bottom border=0></P>
<H3><A name=examinecode></A><font color="#FFFF66">查看生成的代码</font></H3>
<P>向导完成后,在生成的代码中有三个类:CMainFrame, CAboutDlg, 和CWTLClockView,从名字上就可以猜出这些类的作用。虽然也有一个是视图类,但它仅仅是从CWindowImpl派生出来的一个简单的窗口类,没有象MFC那样的文档/视图结构。</P>
<P>还有一个_tWinMain()函数,它先初始化COM环境,公用控件和_Module,然后调用全局函数Run()。Run()函数创建主窗口并开始消息循环,Run()调用CMessageLoop::Run(),消息泵实际上是位于CMessageLoop::Run()内,我将在下一个章节介绍CMessageLoop的更多细节。<br>
</P>
<P>CAboutDlg是CDialogImpl的派生类,它对应于ID IDD_ABOUTBOX资源,我在第一部分已经介绍过对话框,所以你应该能看懂CAboutDlg的代码。<br>
</P>
<P>CWTLClockView是我们的程序的视图类,它的作用和MFC的视图类一样,没有标题栏,覆盖整个主窗口的客户区。CWTLClockView类有一个PreTranslateMessage()函数,也和MFC中的同名函数作用相同,还有一个WM_PAINT的消息响应函数。这两个函数都没有什么特别之处,只是我们会填写OnPaint()函数来显示时间。</P>
<P>最后是我们的CMainFrame类,它有许多有趣的新东西,这是这个类的定义缩略版本:</P>
<PRE><SPAN class=cpp-keyword><font color="#0000FF">class</font></SPAN><font color="#0000FF"> CMainFrame : <SPAN class=cpp-keyword>public</SPAN> CFrameWindowImpl<CMainFrame>,
<SPAN class=cpp-keyword>public</SPAN> CUpdateUI<CMainFrame>,
<SPAN class=cpp-keyword>public</SPAN> CMessageFilter,
<SPAN class=cpp-keyword>public</SPAN> CIdleHandler
{
<SPAN class=cpp-keyword>public</SPAN>:
DECLARE_FRAME_WND_CLASS(NULL, IDR_MAINFRAME)
CWTLClockView m_view;
<SPAN class=cpp-keyword>virtual</SPAN> BOOL PreTranslateMessage(MSG* pMsg);
<SPAN class=cpp-keyword>virtual</SPAN> BOOL OnIdle();
BEGIN_UPDATE_UI_MAP(CMainFrame)
END_UPDATE_UI_MAP()
BEGIN_MSG_MAP(CMainFrame)
<SPAN class=cpp-comment>// ...</SPAN>
CHAIN_MSG_MAP(CUpdateUI<CMainFrame>)
CHAIN_MSG_MAP(CFrameWindowImpl<CMainFrame>)
END_MSG_MAP()
};</font></PRE>
<P>CMessageFilter是一个嵌入类,它提供PreTranslateMessage()函数,CIdleHandler也是一个嵌入类,它提供了OnIdle()函数。CMessageLoop,
CIdleHandler 和 CUpdateUI三个类互相协同完成界面元素的状态更新(UI update),就像MFC中的ON_UPDATE_COMMAND_UI宏一样。</P>
<p>CMainFrame::OnCreate()中创建了视图窗口并保存这个窗口的句柄,当主窗口改变大小时视图窗口的大小也会随之改变。OnCreate()函数还将CMainFrame对象添加到由CAppModule维持的消息过滤器队列和空闲处理队列,我将在稍后介绍这些。</p>
<PRE><font color="#0000FF">LRESULT CMainFrame::OnCreate(UINT <SPAN class=cpp-comment>/*uMsg*/</SPAN>, WPARAM <SPAN class=cpp-comment>/*wParam*/</SPAN>,
LPARAM <SPAN class=cpp-comment>/*lParam*/</SPAN>, BOOL& <SPAN class=cpp-comment>/*bHandled*/</SPAN>)
{
m_hWndClient = m_view.Create(m_hWnd, rcDefault, NULL, |
WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS |
WS_CLIPCHILDREN, WS_EX_CLIENTEDGE);
<SPAN class=cpp-comment>// register object for message filtering and idle updates</SPAN>
CMessageLoop* pLoop = _Module.GetMessageLoop();
pLoop->AddMessageFilter(<SPAN class=cpp-keyword>this</SPAN>);
pLoop->AddIdleHandler(<SPAN class=cpp-keyword>this</SPAN>);
<SPAN class=cpp-keyword>return</SPAN> <SPAN class=cpp-literal>0</SPAN>;
}</font></PRE>
<P>m_hWndClient是CFrameWindowImpl对象的一个成员变量,当主窗口大小改变时此窗口的大小也将改变。</P>
<p>在生成的CMainFrame中还添加了对File|New, File|Exit, 和 Help|About菜单消息的处理。我们的时钟程序不需要这些默认的菜单项,但是现在将它们留在代码中也没有害处。现在可以编译并运行向导生成的代码,不过这个程序确实没有什么用处。如果你感兴趣的话可以深入CMainFrame::CreateEx()函数的内部看看主窗口和它的资源是如何被加载和创建得。</p>
<p>我们的下一步WTL之旅是CMessageLoop,它掌管消息泵和空闲处理。</p>
<H2><A name=msgloopinternals></A><font color="#FFFF66">CMessageLoop 的内部实现</font></H2>
<P>CMessageLoop为我们的应用程序提供一个消息泵,除了一个标准的DispatchMessage/TranslateMessage循环外,它还通过调用PreTranslateMessage()函数实现了消息过滤机制,通过调用OnIdle()实现了空闲处理功能。下面是Run()函数的伪代码:</P>
<PRE><SPAN class=cpp-keyword><font color="#0000FF">int</font></SPAN><font color="#0000FF"> Run()
{
MSG msg;
<SPAN class=cpp-keyword>for</SPAN>(;;)
{
<SPAN class=cpp-keyword>while</SPAN> ( !PeekMessage(&msg) )
DoIdleProcessing();
<SPAN class=cpp-keyword>if</SPAN> ( <SPAN class=cpp-literal>0</SPAN> == GetMessage(&msg) )
<SPAN class=cpp-keyword>break</SPAN>; <SPAN class=cpp-comment>// WM_QUIT retrieved from the queue</SPAN>
<SPAN class=cpp-keyword>if</SPAN> ( !PreTranslateMessage(&msg) )
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
<SPAN class=cpp-keyword>return</SPAN> msg.wParam;
}</font></PRE>
<P>那些需要过滤消息的类只需要象CMainFrame::OnCreate()函数那样调用CMessageLoop::AddMessageFilter()函数就行了,CMessageLoop就会知道该调用那个PreTranslateMessage()函数,同样,如果需要空闲处理就调用CMessageLoop::AddIdleHandler()函数。</P>
<p>需要注意得是在这个消息循环中没有调用TranslateAccelerator() 或 IsDialogMessage() 函数,因为CFrameWindowImpl在这之前已经做了处理,但是如果你在程序中使用了非模式对话框,那你就需要在CMainFrame::PreTranslateMessage()函数中添加对IsDialogMessage()函数的调用。</p>
<H2><A name=frameinternals></A><font color="#FFFF66">CFrameWindowImpl 的内部实现</font></H2>
<P>CFrameWindowImpl 和它的基类 CFrameWindowImplBase提供了对toolbars,rebars, status bars,工具条按钮的工具提示和菜单项的掠过式帮助,这些也是MFC的CFrameWnd类的基本特征。我会逐步介绍这些特征,完整的讨论CFrameWindowImpl类需要再写两篇文章,但是现在看看CFrameWindowImpl是如何处理WM_SIZE和它的客户区就足够了。需要记住一点前面提到的东西,m_hWndClient是CFrameWindowImplBase类的成员变量,它存储主窗口内的“视图”窗口的句柄。</P>
<p>CFrameWindowImpl类处理了WM_SIZE消息:</p>
<PRE><font color="#0000FF">LRESULT OnSize(UINT <SPAN class=cpp-comment>/*uMsg*/</SPAN>, WPARAM wParam, LPARAM <SPAN class=cpp-comment>/*lParam*/</SPAN>, BOOL& bHandled)
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -