Last Update: 06/17/2004
WTL 7.5 中包含的類別已經遠超過 Michael Dunn 撰寫文章時的數量了,在 WTL 的 Readme.txt 檔案中有詳細的介紹。大致上所有的 MFC control 都可以在 WTL 中找到對應的東西。
BEGIN_MSG_MAP_EX 這個 macro 在 ATL 7 中是沒有作用的,它會直接被替代為 BEGIN_MSG_MAP,因此底下的文章中我們都是以最新的 WTL 7.5 加 ATL 7.1 為準。
另外在 atlmisc.h 中有四個類別是 ATL 7.1 中已有的了:CRect, CSize, CPoint 以及 CString。使用時必須用 scope operator 來指明 (i.e. ATL::* 或 WTL::*),不然也可以用
#define _WTL_NO_WTYPES // disable CRect, CSize, CPoint
#define _WTL_NO_CSTRING // disable CString
我們用 WTL AppWizard 產生一個基本的 SDI 程式,叫做 WTLTest2,照預設值完全不改的話可以得到一個簡單的 SDI frame window application。先來看看這個自動產生的程式長什麼樣子:
// stdafx.h #pragma once // Change these values to use different versions #define WINVER 0x0400 //#define _WIN32_WINNT 0x0400 #define _WIN32_IE 0x0400 #define _RICHEDIT_VER 0x0100 #include <atlbase.h> #include <atlapp.h> extern CAppModule _Module; #include <atlwin.h>
乍看下並沒有很大的不同,前頭的 #define 其實是為了配合某些特定的類別或 API 而使用的,例如 _WIN32_WINNT 的值就會影響 CImage 的行為 (Windows 2000 以上才可以使用 AlphaBlend() 成員函數)。atlapp.h 則是 WTL 的基本檔,要使用 WTL 的東西就必須含入。
// stdafx.cpp #include "stdafx.h" #if (_ATL_VER < 0x0700) #include <atlimpl.cpp> #endif //(_ATL_VER < 0x0700)
stdafx.cpp 只有一些與 ATL 3.0 相容的東西,並沒有什麼用處。
AppWizard 產生的 WTL 程式將 function body 都放在 .h 檔中,有些類似 Java 的做法。我是對這種方法沒什麼意見,但 VS.Net 的 Intellisense 在 .h 檔中是不會動的,所以必須修改一下將 function body 移動到 .cpp 檔去才行。修改過後的 MainFrm.h 如下:
// MainFrm.h : interface of the CMainFrame class #pragma once class CMainFrame : public CFrameWindowImpl<CMainFrame>, public CUpdateUI<CMainFrame>, public CMessageFilter, public CIdleHandler { public: DECLARE_FRAME_WND_CLASS(NULL, IDR_MAINFRAME) CWTLTest2View m_view; CCommandBarCtrl m_CmdBar; virtual BOOL PreTranslateMessage(MSG* pMsg); virtual BOOL OnIdle(); BEGIN_UPDATE_UI_MAP(CMainFrame) UPDATE_ELEMENT(ID_VIEW_TOOLBAR, UPDUI_MENUPOPUP) UPDATE_ELEMENT(ID_VIEW_STATUS_BAR, UPDUI_MENUPOPUP) END_UPDATE_UI_MAP() BEGIN_MSG_MAP(CMainFrame) MESSAGE_HANDLER(WM_CREATE, OnCreate) COMMAND_ID_HANDLER(ID_APP_EXIT, OnFileExit) COMMAND_ID_HANDLER(ID_FILE_NEW, OnFileNew) COMMAND_ID_HANDLER(ID_VIEW_TOOLBAR, OnViewToolBar) COMMAND_ID_HANDLER(ID_VIEW_STATUS_BAR, OnViewStatusBar) COMMAND_ID_HANDLER(ID_APP_ABOUT, OnAppAbout) CHAIN_MSG_MAP(CUpdateUI<CMainFrame>) CHAIN_MSG_MAP(CFrameWindowImpl<CMainFrame>) END_MSG_MAP() // Handler prototypes (uncomment arguments if needed): // LRESULT MessageHandler(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/) // LRESULT CommandHandler(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/) // LRESULT NotifyHandler(int /*idCtrl*/, LPNMHDR /*pnmh*/, BOOL& /*bHandled*/) LRESULT OnCreate(UINT, WPARAM, LPARAM, BOOL&); LRESULT OnFileExit(WORD, WORD, HWND, BOOL&); LRESULT OnFileNew(WORD, WORD, HWND, BOOL&); LRESULT OnViewToolBar(WORD, WORD, HWND, BOOL&); LRESULT OnViewStatusBar(WORD, WORD, HWND, BOOL&); LRESULT OnAppAbout(WORD, WORD, HWND, BOOL&); };
CFrameWindowImpl<> 是用來造 frame window 用的。CMessageFilter 是用來提供 PreTranslateMessage() 用的,和 MFC 中的作用相同,都是在 message 進入 TranslateMessage() 之前加以攔截並做處理之用。CIdleHandler 則是提供 OnIdle 用的,也與 MFC 裡的 OnIdle() 一樣,都是提供在 UI thread idle 時可做一些背景處理的動作。
CUpdateUI<> 則提供了一個較有效率的 UI update 機制,它會記住 BEGIN_UPDATE_UI_MAP ... END_UPDATE_UI_MAP 中的 control 是否需要重畫 (亦即處理麻煩的 WM_PAINT),至於 client area 的 WM_PAINT,則由 view class 來負責 (本例中是 CWTLTest2View)。
在 message map 的部份,現在的 WTL programming 已不流行使用 atlhack.h 中那一堆 macro 了,而是直接歸納成三種類型:
macro | 對應的 message 類型 | 對應的 message handler prototype |
MESSAGE_HANDLER | 一般的 window message | MessageHandler |
COMMAND_ID_HANDLER | WM_COMMAND | CommandHandler |
NOTIFY_ID_HANDLER | WM_NOTIFY | NotifyHandler |
底下是修改過的 MainFrm.cpp:
#include "stdafx.h" #include <atlframe.h> #include <atlctrls.h> #include <atldlgs.h> #include <atlctrlw.h> #include "resource.h" #include "AboutDlg.h" #include "WTLTest2View.h" #include "MainFrm.h" BOOL CMainFrame::PreTranslateMessage(MSG* pMsg) { if(CFrameWindowImpl<CMainFrame>::PreTranslateMessage(pMsg)) return TRUE; return m_view.PreTranslateMessage(pMsg); } BOOL CMainFrame::OnIdle() { UIUpdateToolBar(); return FALSE; } LRESULT CMainFrame::OnCreate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { // create command bar window HWND hWndCmdBar = m_CmdBar.Create(m_hWnd, rcDefault, NULL, ATL_SIMPLE_CMDBAR_PANE_STYLE); // attach menu m_CmdBar.AttachMenu(GetMenu()); // load command bar images m_CmdBar.LoadImages(IDR_MAINFRAME); // remove old menu SetMenu(NULL); HWND hWndToolBar = CreateSimpleToolBarCtrl(m_hWnd, IDR_MAINFRAME, FALSE, ATL_SIMPLE_TOOLBAR_PANE_STYLE); CreateSimpleReBar(ATL_SIMPLE_REBAR_NOBORDER_STYLE); AddSimpleReBarBand(hWndCmdBar); AddSimpleReBarBand(hWndToolBar, NULL, TRUE); CreateSimpleStatusBar(); m_hWndClient = m_view.Create(m_hWnd, rcDefault, NULL, WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN, WS_EX_CLIENTEDGE); UIAddToolBar(hWndToolBar); UISetCheck(ID_VIEW_TOOLBAR, 1); UISetCheck(ID_VIEW_STATUS_BAR, 1); // register object for message filtering and idle updates CMessageLoop* pLoop = _Module.GetMessageLoop(); ATLASSERT(pLoop != NULL); pLoop->AddMessageFilter(this); pLoop->AddIdleHandler(this); return 0; } LRESULT CMainFrame::OnFileExit(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled) { PostMessage(WM_CLOSE); return 0; } LRESULT CMainFrame::OnFileNew(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled) { // TODO: add code to initialize document return 0; } LRESULT CMainFrame::OnViewToolBar(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled) { static BOOL bVisible = TRUE; // initially visible bVisible = !bVisible; CReBarCtrl rebar = m_hWndToolBar; int nBandIndex = rebar.IdToIndex(ATL_IDW_BAND_FIRST + 1); // toolbar is 2nd added band rebar.ShowBand(nBandIndex, bVisible); UISetCheck(ID_VIEW_TOOLBAR, bVisible); UpdateLayout(); return 0; } LRESULT CMainFrame::OnViewStatusBar(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled) { BOOL bVisible = !::IsWindowVisible(m_hWndStatusBar); ::ShowWindow(m_hWndStatusBar, bVisible ? SW_SHOWNOACTIVATE : SW_HIDE); UISetCheck(ID_VIEW_STATUS_BAR, bVisible); UpdateLayout(); return 0; } LRESULT CMainFrame::OnAppAbout(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled) { CAboutDlg dlg; dlg.DoModal(); return 0; }
標成藍色的地方是我較感興趣的部份。frame window 的 PreTranslateMessage() 會在處理後再 pass 給 view,而 frame window 的 OnCreate() 則負責 view 的 create,並將自己的 message filter 和 idle handler 加到 module 的 message loop 中。
接下來來看看 view:
// WTLTest2View.h : interface of the CWTLTest2View class #pragma once class CWTLTest2View : public CWindowImpl<CWTLTest2View> { public: DECLARE_WND_CLASS(NULL) BOOL PreTranslateMessage(MSG* pMsg) { pMsg; return FALSE; } BEGIN_MSG_MAP(CWTLTest2View) MESSAGE_HANDLER(WM_PAINT, OnPaint) END_MSG_MAP() LRESULT OnPaint(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/) { CPaintDC dc(m_hWnd); //TODO: Add your drawing code here return 0; } };
view 是用 CWindowImpl 來做的,但和之前的例子比起來,似乎少了些什麼:
class CMyWindow : public CWindowImpl<CMyWindow, CWindow, CFrameWinTraits>
這是因為 CWindowImpl<> 有 default parameter,所以我們的 view class 其實相當於
class CWTLTest2View : public CWindowImpl<CWTLTest2View, CWindow, CControlWinTraits>
最後來看看主程式:
// WTLTest2.cpp : main source file for WTLTest2.exe // #include "stdafx.h" #include <atlframe.h> #include <atlctrls.h> #include <atldlgs.h> #include <atlctrlw.h> #include "resource.h" #include "WTLTest2View.h" #include "aboutdlg.h" #include "MainFrm.h" CAppModule _Module; int Run(LPTSTR /*lpstrCmdLine*/ = NULL, int nCmdShow = SW_SHOWDEFAULT) { CMessageLoop theLoop; _Module.AddMessageLoop(&theLoop); CMainFrame wndMain; if (wndMain.CreateEx() == NULL) { ATLTRACE(_T("Main window creation failed!\n")); return 0; } wndMain.ShowWindow(nCmdShow); int nRet = theLoop.Run(); _Module.RemoveMessageLoop(); return nRet; } int WINAPI _tWinMain(HINSTANCE hInstance, HINSTANCE /*hPrevInstance*/, LPTSTR lpstrCmdLine, int nCmdShow) { HRESULT hRes = ::CoInitialize(NULL); // If you are running on NT 4.0 or higher you can use the following call instead to // make the EXE free threaded. This means that calls come in on a random RPC thread. // HRESULT hRes = ::CoInitializeEx(NULL, COINIT_MULTITHREADED); ATLASSERT(SUCCEEDED(hRes)); // this resolves ATL window thunking problem when Microsoft Layer for Unicode (MSLU) is used ::DefWindowProc(NULL, 0, 0, 0L); AtlInitCommonControls(ICC_COOL_CLASSES | ICC_BAR_CLASSES); // add flags to support other controls hRes = _Module.Init(NULL, hInstance); ATLASSERT(SUCCEEDED(hRes)); int nRet = Run(lpstrCmdLine, nCmdShow); _Module.Term(); ::CoUninitialize(); return nRet; }
在這個主程式中是使用 CMessageLoop::Run() 來跑主要的 message loop,Michael Dunn 的文章中有提到這個函數的 pseudo logic,我直接抄在下面:
int Run() { MSG msg; for(;;) { while ( !PeekMessage(&msg) ) DoIdleProcessing(); if ( 0 == GetMessage(&msg) ) break; // WM_QUIT retrieved from the queue if ( !PreTranslateMessage(&msg) ) { TranslateMessage(&msg); DispatchMessage(&msg); } } return msg.wParam; }
其他比較特異的只有 AtlInitCommonControls(),這是 WTL 提供的函數,它直接叫用了 Platform SDK 所提供的 InitCommonControlsEx() 函數,所以該給些什麼 flag,就必須查閱 InitCommonControlsEx() 的相關說明。
各個 Control 的用法用到時再去查 Code Project 即可。