一、WTL 總體印象
WTL的類大致可以分為幾種類型:
1、主架構視窗的實現- CFrameWindowImpl, CMDIFrameWindowImpl
2、控制項的封裝- CButton, CListViewCtrl GDI
3、對象的封裝- CDC, CMenu
4、一些特殊的介面特性 - CSplitterWindow, CUpdateUI, CDialogResize, CCustomDraw
5、實用的工具類和宏- CString, CRect, BEGIN_MSG_MAP_EX
二、開始寫WTL程式
如果你沒有用WTL的應用程式產生嚮導也沒關係(我將在後面介紹這個嚮導的用法), WTL的程式的代碼結構很像ATL的程式,本章使用的例子代碼有別於第一章的例子,主要是為了顯示WTL的特性,沒有什麼實用價值。
這一節我們將在WTL產生的程式碼基礎上添加代碼,產生一個新的程式,程式主視窗的客戶區顯示當前的時間。stdafx.h的代碼如下:
#define STRICT<br />#define WIN32_LEAN_AND_MEAN<br />#define _WTL_USE_CSTRING</p><p>#include <atlbase.h> // 基本的ATL類<br />#include <atlapp.h> // 基本的WTL類<br />extern CAppModule _Module; // WTL 派生的CComModule版本<br />#include <atlwin.h> // ATL 視窗類別<br />#include <atlframe.h> // WTL 主架構視窗類別<br />#include <atlmisc.h> // WTL 工具 + 生產力類,例如:CString<br />#include <atlcrack.h> // WTL 增強訊息宏
atlapp.h 是你的工程中第一個包含的標頭檔,這個檔案內定義了有關訊息處理的類和CAppModule,CAppModule是從CComModule派生的類。如 果你打算使用CString類,你需要手工定義_WTL_USE_CSTRING標號,因為CString類是在atlmisc.h中定義的,而許多包含 在atlmisc.h之前的標頭檔都會用到CString,定義_WTL_USE_CSTRING之後,atlapp.h就會向前聲明CString類, 其他的標頭檔就知道CString類的存在,從而避免編譯器為此大驚小怪。
接下來定義架構視窗。我們的SDI視窗是從CFrameWindowImpl派生的,在定義視窗類別時使用DECLARE_FRAME_WND_CLASS代替前面使用的DECLARE_WND_CLASS。下面時MyWindow.h中視窗定義的開始部分:
class CMyWindow : public CFrameWindowImpl<CMyWindow><br />{<br />public:<br /> DECLARE_FRAME_WND_CLASS(_T("First WTL window"), IDR_MAINFRAME); BEGIN_MSG_MAP(CMyWindow) CHAIN_MSG_MAP(CFrameWindowImpl<CMyWindow>) END_MSG_MAP()};
DECLARE_FRAME_WND_CLASS有兩個參數,視窗類別名(類名可以是NULL,ATL會替你產生一個類名)和資源ID,建立視窗時 WTL用這個ID裝載表徵圖,菜單和加速鍵表。我們還要象CFrameWindowImpl中的訊息處理(例如WM_SIZE和WM_DESTROY訊息) 那樣將訊息鏈入視窗的訊息中。
現在來看看WinMain()函數,它和第一部分中的例子代碼中的WinMain()函數幾乎一樣,只是建立視窗部分的代碼略微不同。
// main.cpp:<br />#include "stdafx.h"<br />#include "MyWindow.h"</p><p>CAppModule _Module;</p><p>int APIENTRY WinMain ( HINSTANCE hInstance, HINSTANCE hPrevInstance,<br /> LPSTR lpCmdLine, int nCmdShow )<br />{<br /> _Module.Init ( NULL, hInstance );</p><p>CMyWindow wndMain;<br />MSG msg;</p><p> // Create the main window<br /> if ( NULL == wndMain.CreateEx() )<br /> return 1; // Window creation failed</p><p> // Show the window<br /> wndMain.ShowWindow ( nCmdShow );<br /> wndMain.UpdateWindow();</p><p> // Standard Win32 message loop<br /> while ( GetMessage ( &msg, NULL, 0, 0 ) > 0 )<br /> {<br /> TranslateMessage ( &msg );<br /> DispatchMessage ( &msg );<br /> }</p><p> _Module.Term();<br /> return msg.wParam;<br />}
CFrameWindowImpl中的CreateEx()函數的參數使用了常用的預設值,所以我們不需要特別指定任何參數。正如前面介紹 的,CFrameWindowImpl會處理資源的裝載,你只需要使用IDR_MAINFRAME作為ID定義你的資源就行了(譯者註:主要是表徵圖,菜單 和加速鍵表),你也可以直接使用本章的例子代碼。
如果你現在就運行程式,你會看到主架構視窗,事實上它沒有做任何事情。我們需要手工添加一些訊息處理,所以現在是介紹WTL的訊息映射宏的最佳時間。
三、WTL 對訊息映射的增強
將Win32 API通過訊息傳遞過來的WPARAM和LPARAM資料還原出來是一件麻煩的事情並且很容易出錯,不幸得是ATL並沒有為我們提供更多的協助,我們仍然需要從訊息中還原這些資料,當然WM_COMMAND和WM_NOTIFY訊息除外。但是WTL的出現拯救了這一切!
WTL的增強訊息映射宏定義在atlcrack.h中。(這個名字來源於“訊息解密者”,是一個與windowsx.h的宏所使用的相同術語)首先將BEGIN_MSG_MAP改為BEGIN_MSG_MAP_EX,帶_EX的版本產生“解密”訊息的代碼。
class CMyWindow : public CFrameWindowImpl<CMyWindow><br />{<br />public:<br /> BEGIN_MSG_MAP_EX(CMyWindow)<br /> CHAIN_MSG_MAP(CFrameWindowImpl<CMyWindow>)<br /> END_MSG_MAP()<br />};
對於我們的時鐘程式,我們需要處理WM_CREATE訊息來設定定時器,WTL的訊息處理使用MSG_作為首碼,後面是訊息名稱,例如MSG_WM_CREATE。這些宏只是代表訊息響應處理的名稱,現在我們來添加對WM_CREATE訊息的響應:
class CMyWindow : public CFrameWindowImpl<CMyWindow><br />{<br />public:<br /> BEGIN_MSG_MAP_EX(CMyWindow)<br /> MSG_WM_CREATE(OnCreate)<br /> CHAIN_MSG_MAP(CFrameWindowImpl<CMyWindow>)<br /> END_MSG_MAP()</p><p> // OnCreate(...) ?<br />};
WTL的訊息響應處理看起來有點象MFC,每一個處理函數根據訊息傳遞的參數不同也有不同的原型。由於我們沒有嚮導自動添加訊息響應,所以我們需要 自己尋找正確的訊息處理函數。幸運的是VC可以幫我們的忙,將滑鼠游標移到“MSG_WM_CREATE”宏的文字上按F12鍵就可以來到這個宏的定義代 碼處。如果是第一次使用這個功能,VC會要求從新編譯全部檔案以建立瀏覽資訊資料庫(browse info database),建立了這個資料庫之後,VC會開啟atlcrack.h並將代碼定位到MSG_WM_CREATE的定義位置:
#define MSG_WM_CREATE(func) /<br /> if (uMsg == WM_CREATE) /<br /> { /<br /> SetMsgHandled(TRUE); /<br /> lResult = (LRESULT)func((LPCREATESTRUCT)lParam); /<br /> if(IsMsgHandled()) /<br /> return TRUE; /<br /> }
標記為紅色的那一行非常重要,就是在這裡調用實際的訊息響應函數,他告訴我們訊息響應函數有一個LPCREATESTRUCT類型的參數,傳回值的類型是LRESULT。請注意這裡沒有ATL的宏所用的 bHandled 參數,SetMsgHandled()函數代替了這個參數,我會對此作些簡要的介紹。
現在為我們的視窗類別添加OnCreate()響應函數:
class CMyWindow : public CFrameWindowImpl<CMyWindow><br />{<br />public:<br /> BEGIN_MSG_MAP_EX(CMyWindow)<br /> MSG_WM_CREATE(OnCreate)<br /> CHAIN_MSG_MAP(CFrameWindowImpl<CMyWindow>)<br /> END_MSG_MAP()</p><p> LRESULT OnCreate(LPCREATESTRUCT lpcs)<br /> {<br /> SetTimer ( 1, 1000 );<br /> SetMsgHandled(false);<br /> return 0;<br /> }<br />};
CFrameWindowImpl 是直接從CWindow類派生的, 所以它繼承了所有CWindow類的方法,如SetTimer()。這使得對視窗API的調用有點象MFC的代碼,只是MFC使用CWnd類封裝這些API。
我們使用SetTimer()函數建立一個定時器,它每隔一秒鐘(1000毫秒)觸發一次。由於我們需要讓CFrameWindowImpl也處理 WM_CREATE訊息,所以我們調用SetMsgHandled(false),讓訊息通過CHAIN_MSG_MAP宏鏈入基類,這個調用代替了 ATL宏使用的bHandled參數。(即使CFrameWindowImpl類不需要處理WM_CREATE訊息,調用 SetMsgHandled(false)讓訊息流程入基類是個好的習慣,因為這樣我們就不必總是記著哪個訊息需要基類處理那些訊息不需要基類處理,這和 VC的類嚮導產生的代碼相似,多數的衍生類別的訊息處理函數的開始或結尾都會調用基類的訊息處理函數)
為了能夠停止定時器我們還需要響應WM_DESTROY訊息,添加訊息響應的過程和前面一樣,MSG_WM_DESTROY宏的定義是這樣的:
#define MSG_WM_DESTROY(func) /<br /> if (uMsg == WM_DESTROY) /<br /> { /<br /> SetMsgHandled(TRUE); /<br /> func(); /<br /> lResult = 0; /<br /> if(IsMsgHandled()) /<br /> return TRUE; /<br /> }
OnDestroy()函數沒有參數也沒有傳回值,CFrameWindowImpl也要處理WM_DESTROY訊息,所以還要調用SetMsgHandled(false):
class CMyWindow : public CFrameWindowImpl<CMyWindow><br />{<br />public:<br /> BEGIN_MSG_MAP_EX(CMyWindow)<br /> MSG_WM_CREATE(OnCreate)<br /> MSG_WM_DESTROY(OnDestroy)<br /> CHAIN_MSG_MAP(CFrameWindowImpl<CMyWindow>)<br /> END_MSG_MAP()</p><p> void OnDestroy()<br /> {<br /> KillTimer(1);<br /> SetMsgHandled(false);<br /> }<br />};
接下來是響應WM_TIMER訊息的處理函數,它每秒鐘被調用一次。你應該知道怎樣使用F12鍵的竅門了,所以我直接給出響應函數的代碼:
class CMyWindow : public CFrameWindowImpl<CMyWindow><br />{<br />public:<br /> BEGIN_MSG_MAP_EX(CMyWindow)<br /> MSG_WM_CREATE(OnCreate)<br /> MSG_WM_DESTROY(OnDestroy)<br /> MSG_WM_TIMER(OnTimer)<br /> CHAIN_MSG_MAP(CFrameWindowImpl<CMyWindow>)<br /> END_MSG_MAP()</p><p> void OnTimer ( UINT uTimerID, TIMERPROC pTimerProc )<br /> {<br /> if ( 1 != uTimerID )<br /> SetMsgHandled(false);<br /> else<br /> RedrawWindow();<br /> }<br />};
這個響應函數只是在每次定時器觸發時重畫視窗的客戶區。最後我們要響應WM_ERASEBKGND訊息,在視窗客戶區的左上方顯示當前的時間。
class CMyWindow : public CFrameWindowImpl<CMyWindow><br />{<br />public:<br /> BEGIN_MSG_MAP_EX(CMyWindow)<br /> MSG_WM_CREATE(OnCreate)<br /> MSG_WM_DESTROY(OnDestroy)<br /> MSG_WM_TIMER(OnTimer)<br /> MSG_WM_ERASEBKGND(OnEraseBkgnd)<br /> CHAIN_MSG_MAP(CFrameWindowImpl<CMyWindow>)<br />END_MSG_MAP()</p><p>LRESULT OnEraseBkgnd ( HDC hdc )<br /> {<br />CDCHandle dc(hdc);<br />CRect rc;<br />SYSTEMTIME st;<br />CString sTime;</p><p> // Get our window''s client area.<br /> GetClientRect ( rc );</p><p> // Build the string to show in the window.<br /> GetLocalTime ( &st );<br /> sTime.Format ( _T("The time is %d:%02d:%02d"), st.wHour, st.wMinute, st.wSecond );</p><p>// Set up the DC and draw the text.<br />dc.SaveDC();<br />dc.SetBkColor ( RGB(255,153,0);<br />dc.SetTextColor ( RGB(0,0,0) );<br />dc.ExtTextOut ( 0, 0, ETO_OPAQUE, rc, sTime, sTime.GetLength(), NULL );</p><p>// Restore the DC.<br />dc.RestoreDC(-1);<br />return 1;// We erased the background (ExtTextOut did it)<br />}<br />};
這個訊息處理函數不僅使用了CRect和CString類,還使用了一個GDI封裝類CDCHandle。對於CString類我想說的是它等同與 MFC的CString類,我在後面的文章中還會介紹這些封裝類,現在你只需要知道CDCHandle是對HDC的簡單封裝就行了,使用方法與MFC的 CDC類相似,只是CDCHandle的執行個體在超出範圍後不會銷毀它所操作的裝置上下文。
四、程式運行效果