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 即可。