?? wtl for mfc programmers, part ii - wtl gui base classes - wtl.htm
字號:
<html>
<head>
<title>WTL for MFC Programmers, Part II - WTL GUI Base Classes - WTL</title>
<meta http-equiv="Content-Type" content="text/html; charset=gb2312">
</head>
<body bgcolor="#33CCCC" text="#000000">
<p align="center"><b><font style="FONT-SIZE: 16pt" size="4" color="#0033CC">WTL
for MFC Programmers, Part II - WTL GUI Base Classes</font></b><br>
<br>
</p>
<p align="left">原作 :<b><font color="#CC3366">Michael Dunn</font></b> [<a href="http://www.codeproject.com/wtl/WTL4MFC2.asp">英文原文</a>]<br>
翻譯 :<a href="mailto:inte2000@163.com">Orbit(桔皮干了)</a> [<a href="http://www.winmsg.com/cn/orbit.htm">http://www.winmsg.com/cn/orbit.htm</a>]</p>
<p align="left"><a href="demo/WTL4MFC2_demo.zip">下載演示程序代碼</a></p>
<H2><font color="#FFFF66">本章內容</font></H2>
<UL>
<LI><A
href="#intopart2">對第二部分的介紹</A>
<LI><A
href="#wtloverview">WTL 的總體印象</A>
<LI><A
href="#beginningexe">開始寫WTL程序</A>
<LI><A href="#wtlmsgmap">WTL 對消息映射鏈的增強</A>
<LI><A href="#appwizard">從WTL的應用程序生成向導能得到什么</A>
<UL>
<LI><A
href="#thruwizard">使用向導的整個過程</A>
<LI><A
href="#examinecode">查看生成的代碼</A> </LI>
</UL>
<LI><A
href="#msgloopinternals">CMessageLoop 的內部實現</A>
<LI><A
href="#frameinternals">CFrameWindowImpl 的內部實現</A>
<LI><A
href="#backtotheclock">回到前面的時鐘程序</A>
<LI><A href="#uiupdating">UI狀態的自動更新</A>
<UL>
<LI><A
href="#newmenuitems">添加控制時鐘的菜單</A>
<LI><A
href="#callinguienable">使用UIEnable()函數</A> </LI>
</UL>
<LI><A
href="#fixclassview"> 消息映射鏈(Message Maps)中最后需要注意的地方</A>
<LI><A href="#nextup">下一站,1995</A>
<LI><A
href="#revisionhistory">修改記錄</A> </LI>
</UL>
<P>
<H2><A name=intopart2></A><font color="#FFFF66">對第二部分的介紹</font></H2>
<P>好了,現在正式開始介紹WTL!在這一部分我講的內容包括生成一個基本的主窗口和WTL提供的一些友好的改進,比如UI界面的更新(如菜單上的選擇標記)和更好的消息映射機制。為了更好地掌握本章的內容,你應該安裝WTL并將WTL庫的頭文件目錄添加到VC的搜索目錄中,還要將WTL的應用程序生成向導復制到正確的位置。WTL的發布版本中有文檔具體介紹如何做這些設置,如果遇到困難可以查看這些文檔。</P>
<H2><A name=wtloverview></A><font color="#FFFF66">WTL 總體印象</font></H2>
<P>WTL的類大致可以分為幾種類型:</P>
<OL>
<LI>主框架窗口的實現<CODE>-</CODE> CFrameWindowImpl, CMDIFrameWindowImpl
<LI>控件的封裝- CButton, CListViewCtrl
<LI>GDI 對象的封裝- CDC, CMenu
<LI>一些特殊的界面特性 - CSplitterWindow, CUpdateUI, CDialogResize, CCustomDraw
<LI>實用的工具類和宏- CString, CRect, BEGIN_MSG_MAP_EX</LI>
</OL>
<P>本篇文章將深入地介紹框架窗口類,還將簡要地講一下有關的界面特性類和工具類,這些界面特性類和工具類中絕大多數都是獨立的類,盡管有一些是嵌入類,例如:CDialogResize。</P>
<H2><A name=beginningexe></A><font color="#FFFF66">開始寫WTL程序</font></H2>
<P>如果你沒有用WTL的應用程序生成向導也沒關系(我將在后面介紹這個向導的用法), WTL的程序的代碼結構很像ATL的程序,本章使用的例子代碼有別于第一章的例子,主要是為了顯示WTL的特性,沒有什么實用價值。</P>
<P>這一節我們將在WTL生成的代碼基礎上添加代碼,生成一個新的程序,程序主窗口的客戶區顯示當前的時間。stdafx.h的代碼如下:</P>
<PRE><SPAN class=cpp-preprocessor><font color="#0000FF">#define STRICT</font></SPAN>
<font color="#0000FF"><SPAN class=cpp-preprocessor>#define WIN32_LEAN_AND_MEAN</SPAN>
<SPAN class=cpp-preprocessor>#define _WTL_USE_CSTRING</SPAN>
<SPAN class=cpp-preprocessor>#include <atlbase.h> // 基本的ATL類</SPAN>
<SPAN class=cpp-preprocessor>#include <atlapp.h> // 基本的WTL類
</SPAN><SPAN class=cpp-keyword>extern</SPAN> CAppModule _Module; <SPAN class=cpp-comment>// WTL 派生的CComModule版本</SPAN>
<SPAN class=cpp-preprocessor>#include <atlwin.h> // ATL 窗口類</SPAN>
<SPAN class=cpp-preprocessor>#include <atlframe.h> // WTL 主框架窗口類</SPAN>
<SPAN class=cpp-preprocessor>#include <atlmisc.h> // WTL 實用工具類,例如:CString</SPAN>
<SPAN class=cpp-preprocessor>#include <atlcrack.h> // WTL 增強的消息宏</SPAN></font></PRE>
<P>atlapp.h 是你的工程中第一個包含的頭文件,這個文件內定義了有關消息處理的類和CAppModule,CAppModule是從CComModule派生的類。如果你打算使用CString類,你需要手工定義_WTL_USE_CSTRING標號,因為CString類是在atlmisc.h中定義的,而許多包含在atlmisc.h之前的頭文件都會用到CString,定義_WTL_USE_CSTRING之后,atlapp.h就會向前聲明CString類,其他的頭文件就知道CString類的存在,從而避免編譯器為此大驚小怪。<br>
</P>
<P>接下來定義框架窗口。我們的SDI窗口是從CFrameWindowImpl派生的,在定義窗口類時使用DECLARE_FRAME_WND_CLASS代替前面使用的DECLARE_WND_CLASS。下面時MyWindow.h中窗口定義的開始部分:</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>:
DECLARE_FRAME_WND_CLASS(_T(<SPAN class=cpp-string>"First WTL window"</SPAN>), IDR_MAINFRAME);
BEGIN_MSG_MAP(CMyWindow)
CHAIN_MSG_MAP(CFrameWindowImpl<CMyWindow>)
END_MSG_MAP()
};</font></PRE>
<P>DECLARE_FRAME_WND_CLASS有兩個參數,窗口類名(類名可以是NULL,ATL會替你生成一個類名)和資源ID,創建窗口時WTL用這個ID裝載圖標,菜單和加速鍵表。我們還要象CFrameWindowImpl中的消息處理(例如WM_SIZE和WM_DESTROY消息)那樣將消息鏈入窗口的消息中。</P>
<P>現在來看看WinMain()函數,它和第一部分中的例子代碼中的WinMain()函數幾乎一樣,只是創建窗口部分的代碼略微不同。</P>
<PRE>// main.cpp:<br><font color="#0000FF">#include "stdafx.h"<br>#include "MyWindow.h"<br> <br>CAppModule _Module;<br> <br>int APIENTRY WinMain ( HINSTANCE hInstance, HINSTANCE hPrevInstance,<br> LPSTR lpCmdLine, int nCmdShow )<br>{<br> _Module.Init ( NULL, hInstance );<br> <br>CMyWindow wndMain;<br>MSG msg;<br> <br> // Create the main window<br> if ( NULL == wndMain.CreateEx() )<br> return 1; // Window creation failed<br> <br> // Show the window<br> wndMain.ShowWindow ( nCmdShow );<br> wndMain.UpdateWindow();<br> <br> // Standard Win32 message loop<br> while ( GetMessage ( &msg, NULL, 0, 0 ) > 0 )<br> {<br> TranslateMessage ( &msg );<br> DispatchMessage ( &msg );<br> }<br> <br> _Module.Term();<br> return msg.wParam;<br>}</font></PRE>
<P>CFrameWindowImpl中的CreateEx()函數的參數使用了常用的默認值,所以我們不需要特別指定任何參數。正如前面介紹的,CFrameWindowImpl會處理資源的裝載,你只需要使用IDR_MAINFRAME作為ID定義你的資源就行了(譯者注:主要是圖標,菜單和加速鍵表),你也可以直接使用本章的例子代碼。</P>
<P>如果你現在就運行程序,你會看到主框架窗口,事實上它沒有做任何事情。我們需要手工添加一些消息處理,所以現在是介紹WTL的消息映射宏的最佳時間。</P>
<H2><A name=wtlmsgmap></A><font color="#FFFF66">WTL 對消息映射的增強</font></H2>
<P>將Win32 API通過消息傳遞過來的WPARAM和LPARAM數據還原出來是一件麻煩的事情并且很容易出錯,不幸得是ATL并沒有為我們提供更多的幫助,我們仍然需要從消息中還原這些數據,當然WM_COMMAND和WM_NOTIFY消息除外。但是WTL的出現拯救了這一切!</P>
<P>WTL的增強消息映射宏定義在atlcrack.h中。(這個名字來源于“消息解密者”,是一個與windowsx.h的宏所使用的相同術語)首先將BEGIN_MSG_MAP改為BEGIN_MSG_MAP_EX,帶_EX的版本產生“解密”消息的代碼。</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>:</font>
<FONT color=red>BEGIN_MSG_MAP_EX(CMyWindow)</FONT>
<font color="#0000FF"> CHAIN_MSG_MAP(CFrameWindowImpl<CMyWindow>)
END_MSG_MAP()
};</font></PRE>
<P>對于我們的時鐘程序,我們需要處理WM_CREATE消息來設置定時器,WTL的消息處理使用MSG_作為前綴,后面是消息名稱,例如MSG_WM_CREATE。這些宏只是代表消息響應處理的名稱,現在我們來添加對WM_CREATE消息的響應:</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)</font><FONT color=red>
MSG_WM_CREATE(OnCreate)</FONT>
<font color="#0000FF"> CHAIN_MSG_MAP(CFrameWindowImpl<CMyWindow>)
END_MSG_MAP()
<SPAN class=cpp-comment>// OnCreate(...) ?</SPAN>
};</font></PRE>
<P>WTL的消息響應處理看起來有點象MFC,每一個處理函數根據消息傳遞的參數不同也有不同的原型。由于我們沒有向導自動添加消息響應,所以我們需要自己查找正確的消息處理函數。幸運的是VC可以幫我們的忙,將鼠標光標移到“MSG_WM_CREATE”宏的文字上按F12鍵就可以來到這個宏的定義代碼處。如果是第一次使用這個功能,VC會要求從新編譯全部文件以建立瀏覽信息數據庫(browse
info database),建立了這個數據庫之后,VC會打開atlcrack.h并將代碼定位到MSG_WM_CREATE的定義位置:</P>
<PRE><SPAN class=cpp-preprocessor><font color="#0000FF">#define MSG_WM_CREATE(func) \</font></SPAN>
<font color="#0000FF"><SPAN class=cpp-keyword>if</SPAN> (uMsg == WM_CREATE) \
{ \
SetMsgHandled(TRUE); \</font>
<FONT color=red>lResult = (LRESULT)func((LPCREATESTRUCT)lParam); \</FONT>
<font color="#0000FF"><SPAN class=cpp-keyword>if</SPAN>(IsMsgHandled()) \
<SPAN class=cpp-keyword>return</SPAN> TRUE; \
}</font></PRE>
<P>標記為紅色的那一行非常重要,就是在這里調用實際的消息響應函數,他告訴我們消息響應函數有一個<code>LPCREATESTRUCT</code>類型的參數,返回值的類型是LRESULT。請注意這里沒有ATL的宏所用的
bHandled 參數,<code>SetMsgHandled()</code>函數代替了這個參數,我會對此作些簡要的介紹。</P>
<P>現在為我們的窗口類添加OnCreate()響應函數:</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)
CHAIN_MSG_MAP(CFrameWindowImpl<CMyWindow>)
END_MSG_MAP()</font>
<FONT color=red>LRESULT OnCreate(LPCREATESTRUCT lpcs)
{
SetTimer ( <SPAN class=cpp-literal>1</SPAN>, <SPAN class=cpp-literal>1000</SPAN> );
SetMsgHandled(<SPAN class=cpp-keyword>false</SPAN>);
<SPAN class=cpp-keyword>return</SPAN> <SPAN class=cpp-literal>0</SPAN>;
}</FONT>
<font color="#0000FF">};</font></PRE>
<P>CFrameWindowImpl 是直接從CWindow類派生的, 所以它繼承了所有CWindow類的方法,如SetTimer()。這使得對窗口API的調用有點象MFC的代碼,只是MFC使用CWnd類包裝這些API。</P>
<P>我們使用SetTimer()函數創建一個定時器,它每隔一秒鐘(1000毫秒)觸發一次。由于我們需要讓CFrameWindowImpl也處理WM_CREATE消息,所以我們調用SetMsgHandled(false),讓消息通過CHAIN_MSG_MAP宏鏈入基類,這個調用代替了ATL宏使用的bHandled參數。(即使CFrameWindowImpl類不需要處理WM_CREATE消息,調用SetMsgHandled(false)讓消息流入基類是個好的習慣,因為這樣我們就不必總是記著哪個消息需要基類處理那些消息不需要基類處理,這和VC的類向導產生的代碼相似,多數的派生類的消息處理函數的開始或結尾都會調用基類的消息處理函數)</P>
<P>為了能夠停止定時器我們還需要響應WM_DESTROY消息,添加消息響應的過程和前面一樣,MSG_WM_DESTROY宏的定義是這樣的:</P>
<PRE><SPAN class=cpp-preprocessor><font color="#0000FF">#define MSG_WM_DESTROY(func) \</font></SPAN>
<font color="#0000FF"><SPAN class=cpp-keyword>if</SPAN> (uMsg == WM_DESTROY) \
{ \
SetMsgHandled(TRUE); \</font>
<FONT color=red>func(); \</FONT>
<font color="#0000CC"> lResult = <SPAN class=cpp-literal>0</SPAN>; \
<SPAN class=cpp-keyword>if</SPAN>(IsMsgHandled()) \
<SPAN class=cpp-keyword>return</SPAN> TRUE; \
}</font></PRE>
<P>OnDestroy()函數沒有參數也沒有返回值,CFrameWindowImpl也要處理WM_DESTROY消息,所以還要調用SetMsgHandled(false):<br>
</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)</font>
<FONT color=red>MSG_WM_DESTROY(OnDestroy)</FONT>
<font color="#0000CC"> CHAIN_MSG_MAP(CFrameWindowImpl<CMyWindow>)
END_MSG_MAP()<BR><BR></font><BR>
<FONT color=red><SPAN class=cpp-keyword>void</SPAN> OnDestroy()
{
KillTimer(<SPAN class=cpp-literal>1</SPAN>);
SetMsgHandled(<SPAN class=cpp-keyword>false</SPAN>);
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -