
WTL Makes UI Programming a Joy,
Part 1: The Basics
第一章 ATL, the Foundation of WTL
The Windows Template Library (WTL) 随着 2000 年 1 月的 Platform SDK 一起釋出 , 作為一個 SDK 的示例代碼 , 由 ATL ( 活動模闆庫 ) 小組成員編寫而成 , 它僅僅是基于 ATL 并封裝了視窗部分的 Win32 API. 此後 , ATL 2.0 包含了一些簡單的視窗封裝類 , 比如 CWindow, CWindowImpl 和 CDialogImpl. 然而 , 與 MFC 比起來 , ATL 已有的視窗類根本不能當回事 . 即使是到了 ATL3.0 也沒有提供對一些常用 UI 屬性的支援 , 如 MDI, 指令條 , DDX, 列印 , GDI, 甚至是連 MFC 中最受歡迎的 CString 類都沒有 . 如果沒有上述這些易用特性 , 那麼 WTL 終究不能令大多數 MFC 程式員滿意 . 是以 , WTL 就是 ATL 小組成員開發出來的一個視窗架構, 他們認為一個完美的視窗架構就應該是這個樣子的 . Table 1 展示了 WTL 與 MFC 相關特性的比較 .
Feature | MFC | WTL |
Stand-alone library | Yes | No (built on ATL) |
AppWizard support | Yes | Yes |
ClassWizard support | Yes | No |
Officially supported by Microsoft | Yes | No (Supported by volunteers inside MS) |
Support for OLE Documents | Yes | No |
Support for Views | Yes | Yes |
Support for Documents | Yes | No |
Basic Win32 & Common Control Wrappers | Yes | Yes |
Advanced Common Control Wrappers (Flat scrollbar, IP Address, Pager Control, etc.) | No | Yes |
Command Bar support (including bitmapped context menus) | No (MFC does provide dialog bars) | Yes |
CString | Yes | Yes |
GDI wrappers | Yes | Yes |
Helper classes (CRect, Cpoint, etc.) | Yes | Yes |
Property Sheets/Wizards | Yes | Yes |
SDI, MDI support | Yes | Yes |
Multi-SDI support | No | Yes |
MRU Support | Yes | Yes |
Docking Windows/Bars | Yes | No |
Splitters | Yes | Yes |
DDX | Yes | Yes (not as extensive as MFC) |
Printing/Print Preview | Yes | Yes |
Scrollable Views | Yes | Yes |
Custom Draw/Owner Draw Wrapper | No | Yes |
Feature | MFC | WTL |
Message/Command Routing | Yes | Yes |
Common Dialogs | Yes | Yes |
HTML Views | Yes | Yes |
Single Instance Applications | No | No |
UI Updating | Yes | Yes |
Template-based | No | Yes |
Size of a statically linked do-nothing SDI application with toolbar, status bar, and menu | 228KB + MSVCRT.DLL (288KB) | 24k (with /OPT:NOWIN98) (+ MSVCRT.DLL if you use CString) |
Size of a dynamically linked do-nothing SDI application with toolbar, status bar, and menu | 24KB + MFC42.DLL (972KB) + MSVCRT.DLL (288KB) | N/A |
Runtime Dependencies | CRT (+ MFC42.DLL, if dynamically linked) | None (CRT if you use CString) |
起初是為了支援的 COM 控件及 OLE 屬性頁的一套 ATL 視窗類 , 形成了 WTL 的架構視窗基礎 . ATL 提供了所有的最基本視窗應用 , 包括視窗 / 對話框建立和管理 , 視窗過程處理 , 消息路由 , 視窗子類化 / 超類化 , 消息鍊 . Figure1 給出了 ATL 視窗類層次架構 .
Figure 1 展示了 ATL 視窗類結構.
Figure 1: ATL Windowing-class Hierarchy
為了建立直接基于 ATL 的主視窗或對話框視窗 , 需要分别從 CWindowImpl 或 CDialogImpl 派生自己的視窗類 . Figure2 是一個 SDI 應用程式 , 向你展示了如何利用 ATL 的視窗應用支援來建立主視窗和對話框 .
Figure 2: Simple SDI Application
如圖所示 , 此 SDI 視窗程式包括視窗架構 , 菜單條 , 狀态條和客戶區 . 它提供一個關于對話框 Aboutbox 示範如何利用 ATL 來建立對話框 . 在 VC6 中建立一個 Win32 Application 工程 , 并添加對 ATL 視窗應用的支援 .
( 譯者注:原作以 WTL7.1 講解 , 現在 WTL8.0 是最新版本,下載下傳連結:http://sourceforge.net/projects/wtl/ 支援 Vista 特性和 API, 原生支援 VS2005, 也可以在 VS2008 中使用 , 本系列中所提及的 VC6, 均可以 VS2005 中的 VC8 或者 VS2008 的 VC9 來代替 , 相應的設定及界面或許有些許不同 , 當然 , 建議放棄使用 VC6, 因為其對标準 C++ 的支援不是很好 , 以後對此不再提及 )
(譯者再注:http://sourceforge.net/projects/wtl/ 點選“View all files” 你将看到的最新版本是WTL81, 版本号WTL8.1.9127 , 05/07/2009釋出,支援VS2008)
Figure3 給出了源代碼相關檔案 .
Figure 3: Main Source Files
//---------------------------------------------------------------------
// stdafx.h :
#if !defined(AFX_STDAFX_H__DE06DA2F_25B6_41BA_9B20_A17362C38C8C__INCLUDED_)
#define AFX_STDAFX_H__DE06DA2F_25B6_41BA_9B20_A17362C38C8C__INCLUDED_
#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000
#define STRICT
#ifndef _WIN32_WINNT
#define _WIN32_WINNT 0x0400
#endif
#define _ATL_APARTMENT_THREADED
#include <atlbase.h>
//You may derive a class from CComModule and use it if you want to override
//something, but do not change the name of _Module
extern CComModule _Module;
#include <atlwin.h>
//{{AFX_INSERT_LOCATION}}
// Microsoft Visual C++ will insert additional declarations immediately before the previous line.
#endif // !defined(AFX_STDAFX_H__DE06DA2F_25B6_41BA_9B20_A17362C38C8C__INCLUDED)
//---------------------------------------------------------------------
//MainFrame.H
#pragma once
#ifndef _MAINFRAME_H_
#define _MAINFRAME_H_
#include "commctrl.H"
class CMainFrame : public CWindowImpl<CMainFrame,CWindow, CWinTraits<WS_OVERLAPPEDWINDOW|WS_CLIPCHILDREN , WS_EX_APPWINDOW | WS_EX_WINDOWEDGE> >
{
public:
CMainFrame():m_hWndStatusBar(NULL),m_hBmp(NULL) {
m_hBmp =
LoadBitmap(_Module.GetResourceInstance(),
MAKEINTRESOURCE(IDB_ATLWINDOWING));
}
~CMainFrame() {
if(m_hBmp) {
::DeleteObject(m_hBmp);
m_hBmp = NULL;
}
}
BEGIN_MSG_MAP(CMainFrame)
MESSAGE_HANDLER(WM_PAINT, OnPaint)
MESSAGE_HANDLER(WM_CREATE, OnCreate)
MESSAGE_HANDLER(WM_SIZE, OnSize)
MESSAGE_HANDLER(WM_CLOSE, OnClose)
COMMAND_ID_HANDLER(ID_FILE_EXIT, OnFileExit)
COMMAND_ID_HANDLER(ID_APP_ABOUT, OnAbout)
END_MSG_MAP()
void OnFinalMessage(HWND ) {}
LRESULT OnCreate(UINT, WPARAM wParam, LPARAM lParam, BOOL& bHandled) {
DefWindowProc();
m_hWndStatusBar = ::CreateStatusWindow(WS_CHILD | WS_VISIBLE |
WS_CLIPCHILDREN | WS_CLIPSIBLINGS | SBARS_SIZEGRIP,
_T("Ready"), m_hWnd, 1);
return 0L;
}
LRESULT OnSize(UINT, WPARAM wParam, LPARAM lParam, BOOL& bHandled) {
DefWindowProc();
::SendMessage(m_hWndStatusBar, WM_SIZE, 0, 0);
return 0L;
}
LRESULT OnPaint(UINT, WPARAM, LPARAM, BOOL&) {
PAINTSTRUCT ps;
HDC hdc = BeginPaint(&ps);
RECT rect; GetClientRect(&rect);
//Bitmap
HDC hDCMem = ::CreateCompatibleDC(hdc);
HBITMAP hBmpOld = (HBITMAP)::SelectObject(hDCMem,m_hBmp);
BITMAP bmp;
::GetObject(m_hBmp, sizeof(BITMAP), &bmp);
SIZE size = { bmp.bmWidth,bmp.bmHeight };
::BitBlt(hdc, rect.left, rect.top, (size.cx), (size.cy), hDCMem,
0,0,SRCCOPY);
//cleanup
::SelectObject(hDCMem,hBmpOld);
::DeleteDC(hDCMem);
hDCMem = NULL;
EndPaint(&ps);
return 0;
}
LRESULT OnClose(UINT,WPARAM,LPARAM, BOOL&) {
DestroyWindow();
PostQuitMessage(0);
return 0L;
}
LRESULT OnFileExit(WORD,WORD wID, HWND,BOOL&) {
SendMessage(WM_CLOSE);
return 0L;
}
LRESULT OnAbout(WORD, WORD wID, HWND, BOOL&) {
CAboutDialog dlg;
dlg.DoModal();
return 0L;
}
private:
struct CAboutDialog : public CDialogImpl<CAboutDialog> {
enum { IDD = IDD_ABOUT };
BEGIN_MSG_MAP(CAboutDialog)
COMMAND_ID_HANDLER(IDOK, OnClose)
END_MSG_MAP()
LRESULT OnClose(WORD, WORD wID, HWND, BOOL&) {
EndDialog(wID);
return 0L;
}
};
HWND m_hWndStatusBar;
HBITMAP m_hBmp;
};
#endif
//---------------------------------------------------------------------
//AtlHelloWindowing.Cpp
#include "stdafx.h"
#include "resource.h"
#include "MainFrame.H"
CComModule _Module;
extern "C" int WINAPI
_tWinMain(HINSTANCE hInstance,HINSTANCE, LPTSTR lpCmdLine, int nShowCmd) {
lpCmdLine = GetCommandLine(); //this line necessary for _ATL_MIN_CRT
_Module.Init(0, hInstance,NULL);
//Load Common Controls
INITCOMMONCONTROLSEX iccx;
iccx.dwSize = sizeof(iccx);
iccx.dwICC = ICC_WIN95_CLASSES |ICC_BAR_CLASSES ;
::InitCommonControlsEx(&iccx);
HMENU hMenu = LoadMenu(_Module.GetResourceInstance(),
MAKEINTRESOURCE(IDR_MAINFRAME));
CMainFrame wndFrame;
wndFrame.GetWndClassInfo().m_wc.hIcon = ::LoadIcon(_Module.GetResourceInstance(),
MAKEINTRESOURCE(IDI_FORM));
wndFrame.GetWndClassInfo().m_wc.style = 0;//CS_HREDRAW|CS_VREDRAW;
wndFrame.Create(GetDesktopWindow(), CWindow::rcDefault,
_T("ATL Windowing is cool"), 0, 0, (UINT)hMenu);
wndFrame.ShowWindow(nShowCmd);
wndFrame.UpdateWindow();
MSG msg;
while (GetMessage(&msg, 0, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
_Module.Term();
return 0;
}
在 MainFrame.h 中 , CMainFrame 有兩個資料成員 , 狀态條視窗句柄和一個位圖句柄 , 在 WM_CREATE 消息映射函數中 CMainFrame 通過呼叫 API 函數 ( 位于 comctl32.dll 中 )CrateStatusWindow() 建立狀态條視窗 , 并在 WM_SIZE 消息映射函數中調整狀态條尺寸 . 在 WM_PAINT 消息映射函數中顯示一個 GDI 位圖 . ATL 并不提供對繪圖操作的支援 , 是以所有繪制操作都是調原生 Win32 API.
MESSAGE_HANDLER 宏要求你自己解密消息參數 ( 大部分消息映射函數中的消息參數 LPARAM 和 WPARAM 具體内容得自己根據 SDK 查閱 ), ATL 通過一系列宏對 WM_COMMAND 和 WM_NOTIFY 進行了解密。 MainFrame.h 中通過 COMMAND_ID_HANDLER 來映射菜單指令 .
最後, CAboutDialog 是繼承自 CDialogImpl 的對話框 , 在 OnAbout 消息映射函數中通過函數 DoModal () 啟動 CAboutDialog .
HelloAtlWindowing.cpp 中 , ATL 應用程式的入口點函數 _tWinMain (), 在 _tWinMain () 中建立了一個 CMainFrame 類的執行個體 , 并調用其 Create (), ShowWindow () 和 UpdateWindow () 成員函數 , 這些成員函數是對 Win32 API 函數 CreateWindow (), ShowWindow () 和 UpdateWindow () 的包裝 . 最後 , 因為這是一個視窗程式 , 是以程式通過标準的 GetMessage-DispatchMessage () 啟動消息循環處理 . 如果你熟悉 Win32 程式設計 , 那麼你會發現這裡缺少 RegisterClass () 和視窗過程函數 WndProc (), 但是你會發現其它一些十分漂亮的東西 . 如果你熟悉 MFC , 你會十分驚訝 , ATL 并不提供諸如自動支援建立狀态條 , 菜單 ( 在此先不提及工具條 ) 以及啟動消息循環的細節 . 很明顯 , ATL 很 有用但同時它在這方面走的并不遠 ( 對視窗架構的處理涉及不多 ), 别忘了 , ATL 牢牢紮根于 COM . 對視窗架構的支援 隻是 ATL 的一個副産品而不是它存在的最主要的目的 . ATL 并不支援 MDI , Multi-SDI , Explorer-style 應用程式 , 也不支援通用控件 , DDX , 以及 GDI , 正因為如此 , WTL 出現了 .
The WTL Way
To be continued...