Party I ATL GUI Classes
ATL-style templates
class CMyWnd : public CWindowImpl<CMyWnd>
{
...
};
CMyWnd在class CMyWnd语句后即被定义,可以在其后的继承关系列表中使用。子类名(当前声明类)在继承列表中出现的用意在于,实现“编译时虚函数调用”。
template <class T>
class B1
{
public:
void SayHi()
{
// 将自身转换成子类
T* pT = static_cast<T*>(this); // HUH?? I'll explain this below
pT->PrintClassName();
}
void PrintClassName() { cout << "This is B1"; }
};
class D1 : public B1<D1>
{
// No overridden functions at all
};
class D2 : public B1<D2>
{
void PrintClassName() { cout << "This is D2"; }
};
int main()
{
D1 d1;
D2 d2;
d1.SayHi(); // prints "This is B1"
d2.SayHi(); // prints "This is D2"
return 0;
}
此项技术的好处有:
1. 不必要使用对象指针;
2. 节省了虚函数表所占空间;
3. 保证了在运行期调用未初始化虚函数表空间的空指针错误;
4. 所有的函数调用在编译期即明确,能够被优化。
ATL Windowing Classes
Class CWindow
{
Public:
HWND m_wnd;
Operator HWND() { return m_wnd; }
}
是对HWND的封装,只有一个数据成员,其成员函数实现在:
Class CWindowImpl : public CWindow
{
}
中,包括窗体类注册、窗体子类化、消息映射,及WindowProc()。
Defining a Window Implementation
非对话框窗体继承自CWindowImpl。需要包含:
1. 窗体类定义
2. 消息映射
3. 窗体默认样式,叫作窗口特征(window traits)
窗体类定义由DECLARE_WND_CLASS 或 DECLARE_WND_CLASS_EX宏实现。他们都定义了一个封装了WNDCLASSEX结构的ATL结构CWndClassInfo。
DECLARE_WND_CLASS用于指定新窗体类的名称其他成员采用默认值;
DECLARE_WND_CLASS_EX除了可以指定窗体类名称还可以指定窗体背景颜色。
当窗体类名称指定为NULL时,ATL会自动生成一个名称。
Class CMyWindow : public CWindowImpl<CMyWindow>
{
Public:
DECLARE_WND_CLASS(_T(“My Window Calss”))
};
消息映射宏,其被解释成一个switch-case结构,当匹配到正确的handler时调用相应的方法。
Class CMyWindow : public CWindowImpl<CMyWindow>
{
Public:
DECLARE_WND_CLASS(_T(“My Window Class”))
BEGIN_MSG_MAP(CMyWindow)
END_MSG_MAP()
};
窗口特征,由窗口类型即窗口类型扩展组合而成,在创建窗体的时候被使用。窗口类型作为窗口特征定义的模板参数,这样调用放不需要为使用正确的窗口类型而烦扰。
Typedef CWinTraits<WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN, WS_EX_APPWINDOW> CMyWindowTraits;
Class CMyWindow : public CWindowImpl<CMyWindow, CWindow, CMyWindowTraits>
{
Public:
DECLARE_WND_CLASS(_T(“My Window Class”))
BEGIN_MSG_MAP(CMyWindow)
END_MSG_MAP()
};
ATL预定义了一些CWinTraits规范。
Filling in the message map
ATL的消息映射对于开发者并不好用,但是WTL在这方面有改进。ATL消息映射不存在像MFC中的特殊消息宏及自动参数解析功能。只有三种类型消息句柄:WM_NOTIFY、WM_COMMAND及其他。
Class CMyWindow : public CWindowImpl<CMyWindow, CWindow, CFrameWinTraits>
{
Public:
DECLARE_WND_CLASS(_T(“My Window Class”))
BEGIN_MSG_MAP(CMyWindow)
MESSAGE_HANDLER(WM_CLOSE, OnClose) // WM_MESSAGE
MESSAGE_HANDLER(WM_DESTROY, OnDestroy)
COMMAND_ID_HANDLER(IDC_ABOUT, OnAbout) // WM_COMMAND
// WM_NOTIFY
END_MSG_MAP()
LRESULT OnClose(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
DestroyWindow();
Return 0;
}
LRESULT OnDestroy(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
PostQuitMessage(0);
Return 0;
}
LRESULT OnAbout(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
{
MessageBox(_T(“Sample ATL Window”), _T(“About MyWindow”));
Return 0;
}
每个消息对应着wParam,lParam参数,如果需要的话。这两个参数需要调用者自己解析。第四个参数,ATL在调用前将其置true,如果希望退后后由默认WindowProc处理,则可将其置false。这中机制和MFC中不同,在MFC通过显示调用基类中对应消息处理方法来实现。
COMMAND_HANDER宏将消息参数解析了,同样的WM_NOTIFY的消息参数也解析了。
Advanced Message Maps and Mix-in Classes
ATL主要特点之一是每个类都可以处理消息,而MFC中只有CWnd和CCmdTarget,以及一些含有PreTranslateMessage函数的类才能处理。这一特性使我们可以方便得开发嵌入类,加入窗体的继承列表为窗体添加新的特性。
带有消息处理的基类一般把子类作为模板参数,这样在基类中就可以使用子类的成员(如CWindow中的HWND)。
Template<class T, COLORRE t_crBrushColor> // 注意,第二个参数是值
Class CPaintBkgnd
{
Public:
CPaintBkgnd() { m_hbrBkgnd = CreateSolidBrush(t_crBrushColor); }
~CPaintBkgnd() { DeleteObject(m_hbrBkgnd); }
BEGIN_MSG_MAP(CPaintBkgnd)
MESSAGE_HANDLER(WM_ERASEBKGND, OnEraseBkgnd)
END_MSG_MAP()
LRESULT OnEraseBkgnd(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
T* pT = static_cast<T*>(this);
HDC dc = (HDC)wParam;
RECT rcClient;
pT->GetClientRect(&rcClient); // 编译时,如果T不含有GetClientRect方法就会报错,保证了子类必须有该方法
FillRect(dc, &rcClient, m_hbrBkgnd);
Return 1;
}
Protected:
HBRUSH m_hbrBkgnd;
};
如何使用嵌入类呢,首先,把嵌入类加入子类的继承列表中:
Class CMyWindow : public CWindowImpl<CMyWindow, CWindow, CFrameWinTraits>,
Public CPaintBkgnd<CMyWindow, RGB(0,0,255)>
其次,需要从子类将消息传递到父类中,这叫做链消息映射。使用CHAIN_MSG_MAP宏。
Class CMyWindow : public CWindowImpl<CMyWindow, CWindow, CFrameWinTraits>,
Public CPaintBkgnd<CMyWindow, RGB(0,0,255)>
{
…
Typedef CPaintBkgnd<CMyWindow, RGB(0,0,255)> CPaintBkgndBase;
BEGIN_MSG_MAP(CMyWindow)
MESSAGE_HANDLER(WM_CLOSE, OnClose)
MESSAGE_HANDLER(WM_DESTROY, OnDestroy)
COMMAND_HANDLER(IDC_ABOUT, OnAbout) // 以上三个消息将不会往下传递,因为都已处理,即bHandled==true
CHAIN_MSG_MAP(CPaintBkgndBase) // 预处理宏,只有一个参数,所以前面需要typedef,否则从“,”认为是多个参数
END_MSG_MAP()
…
};
有别于MFC,ATL链消息机制,使得子类可以继承自多个嵌入类,消息可以传递到过个类中,而MFC只能通过显示调用基类(只有一个)的方法来传递未处理的消息。
Structure of an ATL EXE
In VC 6
一个ATL可执行文件包含一个CComModule类型全局变量_Module,stdafx.h中如:
// stdafx.h:
#define STRICT
#define WIN32_LEAN_AND_MEAN
#include <atlbase.h> // Base ATL classes
extern CComModule _Module; // Global _Module
#include <atlwin.h> // ATL windowing classes
Atlbase.h包含基本Windows头文件,所以不需要另外包含windows.h、tchar.h等,在cpp文件中声明:
// main.cpp:
CComModule _Module;
CComModule需要显示初始化和销毁,:
// main.cpp
CComModule _Module;
Int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hInstPrev, LPSTR szCmdLine, int nCmdShow)
{
_Module.Init(NULL, hInst);
_Module.Term();
}
Init()中第一个参数只在Com服务器的时候使用,EXE的话传NULL就可。ATL本身不提供如在MFC中的WinMain()或消息泵机制,所以需要创建CMyWindow对象及消息泵来运行程序。
// main.cpp
#include “MyWindow.h”
CComModule _Module;
Int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hInstPrev, LPSTR szCmdLine, int nCmdShow)
{
_Module.Init(NULL, hInst);
CMyWindow wndMain;
MSG msg;
// Create & show our main window
If(NULL == wndMain.Create(NULL, CWindow::rcDefault, _T(“My Firsh ATL Window”))
{
// Bad news, window creation failed
Return 1;
}
wndMain.ShowWindow(nCmdShow);
wndMain.UpdateWindow();
// Run the message loop
While(GetMessage(&msg , NULL, 0, 0) > 0)
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
_Module.Term();
Return msg.wParam;
}
In VC7
ATL 7将Module管理的代码分散到多个类中。ATL头文件中自动声明了module全局实例,并且Init()和Term()方法也自动调用了。
// stdafx.h:
#define STRICT
#define WIN32_LEAN_AND_MEAN
#include <atlbase.h> // Base ATL classes
#include <atlwin.h> // ATL windowing classes
// main.cpp:
#include "MyWindow.h"
int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hInstPrev,
LPSTR szCmdLine, int nCmdShow)
{
CMyWindow wndMain;
MSG msg;
// Create & show our main window
if ( NULL == wndMain.Create ( NULL, CWindow::rcDefault,
_T("My First ATL Window") ))
{
// Bad news, window creation failed
return 1;
}
wndMain.ShowWindow(nCmdShow);
wndMain.UpdateWindow();
// Run the message loop
while ( GetMessage(&msg, NULL, 0, 0) > 0 )
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}
Dialogs in ATL
ATL中有两种对话框类CDialog和CAxDialog。用CDialogImpl来实现About对话框。对话框实现和Frame窗体实现相似,仅有两点差异:
1. 基类是CDialogImple而不是CWindowImple;
2. 需要定义一个共有的IDD来保存对话框的资源ID.
class CAboutDlg : public CDialogImpl<CAboutDlg>
{
public:
enum { IDD = IDD_ABOUT };
BEGIN_MSG_MAP(CAboutDlg)
MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog)
MESSAGE_HANDLER(WM_CLOSE, OnClose)
COMMAND_ID_HANDLER(IDOK, OnOKCancel)
COMMAND_ID_HANDLER(IDCANCEL, OnOKCancel)
END_MSG_MAP()
LRESULT OnInitDialog(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
CenterWindow();
return TRUE; // let the system set the focus
}
LRESULT OnClose(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
EndDialog(IDCANCEL);
return 0;
}
LRESULT OnOKCancel(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
{
EndDialog(wID);
return 0;
}
};
没有内置OK/Cancel按钮的消息处理,都必须手工编写。
现在回到CMyWindow,添加菜单,并响应点击事件,弹出对话框:
class CMyWindow : public CWindowImpl<CMyWindow, CWindow, CFrameWinTraits>,
public CPaintBkgnd<CMyWindow,RGB(0,0,255)>
{
public:
BEGIN_MSG_MAP(CMyWindow)
MESSAGE_HANDLER(WM_CREATE, OnCreate)
COMMAND_ID_HANDLER(IDC_ABOUT, OnAbout)
// ...
CHAIN_MSG_MAP(CPaintBkgndBase)
END_MSG_MAP()
LRESULT OnCreate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
HMENU hmenu = LoadMenu ( _Module.GetResourceInstance(), // _AtlBaseModule in VC7
MAKEINTRESOURCE(IDR_MENU1) );
SetMenu ( hmenu );
return 0;
}
LRESULT OnAbout(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
{
CAboutDlg dlg;
dlg.DoModal();
return 0;
}
// ...
};
与MFC中模式对话框不一样,MFC中将父窗体句柄从对话框构造函数传递。ATL中可以通过DoModal()的第一个参数参数传递父窗体,或者不指定父窗体,ATL调用GetActiveWindow()获取父窗体。