ATL封装IE内核启示:使用Win32/ATL建立窗口
开发大型GUI界面程序MFC当仁不让,但如果是开发图形应用程序,并不需要大规模界面控件,没有必要链接庞大的MFC库,直接使用platform sdk会很麻烦,这时ATL中的关于Windows的封装就是最好的选择
ATL的窗口架构是这样的——
【两个底层封装类】
CWindow
窗口句柄和API封装类
只封装了hWnd窗口句柄和与之有关的WinAPI,CWindow和hWnd可以方便地进行转换。
CMessageMap
消息映射接口
该基类有一个待实现的函数ProcessWindowMessage,用以分发消息,可使用宏实现:
BEGIN_MSG_MAP(CMyClass)
END_MSG_MAP()
【两个窗口类实现模板】(最终多继承自CWindow和CMessageMap)
CWindowImpl<T>
自定义窗口模板(实现了WNDCLASS和WndProc)
可选参数:<T, TBase = CWindow, TWinTraits = CControlWinTraits>
通过继承CWindowImpl<CMyWindow>,并实现消息映射,可以实现一个自定义窗口CMyWindow。
CDialogImpl<T>
自定义对话框模板(实现了DlgProc)
可选参数:<T, TBase = CWindow>
通过继承CDialogImpl<CMyDialog>,并实现消息映射、资源绑定,可以实现一个自定义对话框CMyDialog。
资源绑定的实现:enum { IDD = IDD_DIALOG };
【两个即刻可用的窗口类】
CSimpleDialog<IDD_DIALOG>
简单对话框
可选参数:<IDD_DIALOG, bCenter = TRUE>
用来创建只有确定和取消的简单对话框,使用这个类就不需要每次都从CDialogImpl<T>派生了。
CContainedWindow
被容纳的窗口
可选参数:CContainedWindowT<TBase = CWindow, TWinTraits = CControlWinTraits>
可以用来创建子窗口(控件),也可以SubclassWindow来绑定它们,这样就不用每次从CWindowImpl<T>派生了。
这个类将消息路由到父窗口的ALT_MSG_MAP(n),方便接收子窗口消息,自己并不进行消息分发。
以及一些附加的类和模板,如CWinTraits<>、CWinTraitsOR<>、CWndClassInfo等。
一、新建一个支持ATL的Win32项目
新建一个项目,选择Visual C++ -> Win32 -> Win32 项目
点击确定,再点击下一步,选上ATL支持(注意此时MFC是灰色的)点击完成以新建工程
二、打开MyAtlWindowTest.cpp,删减示例代码
原因是我们不需要采用传统方法来新建窗口
剩下的代码如下:
1 // MyAtlWindowTest.cpp : 定义应用程序的入口点。
2 //
3
4 #include "stdafx.h"
5 #include "MyAtlWindowTest.h"
6
7 // 全局变量:
8 HINSTANCE hInst; // 当前实例
9
10 // TODO: 实现窗口类CMainWindow
11
12 int APIENTRY _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
13 LPTSTR lpCmdLine, int nCmdShow)
14 {
15 MSG msg;
16 hInst = hInstance; // 将实例句柄存储在全局变量中
17
18 // TODO: 初始化窗口
19
20 // 主消息循环:
21 while (GetMessage(&msg, NULL, 0, 0)) // 消息循环 - 等待消息
22 {
23 TranslateMessage(&msg); // 消息循环 - 翻译键盘消息
24 DispatchMessage(&msg); // 消息循环 - 分发消息
25 }
26
27 return (int) msg.wParam;
2 //
3
4 #include "stdafx.h"
5 #include "MyAtlWindowTest.h"
6
7 // 全局变量:
8 HINSTANCE hInst; // 当前实例
9
10 // TODO: 实现窗口类CMainWindow
11
12 int APIENTRY _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
13 LPTSTR lpCmdLine, int nCmdShow)
14 {
15 MSG msg;
16 hInst = hInstance; // 将实例句柄存储在全局变量中
17
18 // TODO: 初始化窗口
19
20 // 主消息循环:
21 while (GetMessage(&msg, NULL, 0, 0)) // 消息循环 - 等待消息
22 {
23 TranslateMessage(&msg); // 消息循环 - 翻译键盘消息
24 DispatchMessage(&msg); // 消息循环 - 分发消息
25 }
26
27 return (int) msg.wParam;
三、在stdafx.h添加头文件atlwin.h
向导只给我们添加了基本的atlbase.h和atlstr.h支持,并没有给我们添加窗口支持,因此要手动添加:
复制代码
1 #include <atlwin.h>
四、添加CMainWindow实现
ATL窗口最基本的形式如下:
class 自己的窗口类 : public CWindowImpl<自己的窗口类, 基类=CWindow, 特性类=CControlWinTraits> {
public:
BEGIN_MSG_MAP(自己的窗口类) // 利用宏实现ProcessWindowMessage消息分发函数
END_MSG_MAP()
};
因此最简单的代码如下:
1 // TODO: 实现窗口类CMainWindow
2 class CMainWindow : public CWindowImpl<CMainWindow> { // 主窗口,基于CWindowImpl模板
3 public:
4 BEGIN_MSG_MAP(CMainWindow) // 利用宏实现ProcessWindowMessage函数,用以分发消息
5 END_MSG_MAP()
6 };
2 class CMainWindow : public CWindowImpl<CMainWindow> { // 主窗口,基于CWindowImpl模板
3 public:
4 BEGIN_MSG_MAP(CMainWindow) // 利用宏实现ProcessWindowMessage函数,用以分发消息
5 END_MSG_MAP()
6 };
在这里我们实现了如下的代码(当然你也可以使用上边的代码):
1 // TODO: 实现窗口类CMainWindow
2 class CMainWindow : public CWindowImpl<CMainWindow> { // 主窗口,基于CWindowImpl模板
3 public:
4 BEGIN_MSG_MAP(CMainWindow) // 利用宏实现ProcessWindowMessage函数,用以分发消息
5 COMMAND_ID_HANDLER(IDM_ABOUT, OnAbout) // if命令分发分支
6 COMMAND_ID_HANDLER(IDM_EXIT, OnExit) // if命令分发分支
7 MESSAGE_HANDLER(WM_PAINT, OnPaint) // if消息分发分支
8 MESSAGE_HANDLER(WM_DESTROY, OnDestroy) // if消息分发分支
9 END_MSG_MAP()
10 LRESULT OnPaint(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { // ATL消息处理函数的标准形式
11 PAINTSTRUCT ps;
12 this->BeginPaint(&ps); // 开始绘图
13 // 在这里进行绘图操作
14 this->EndPaint(&ps); // 结束绘图
15 // bHandled如果不手动赋值FALSE的话,默认为TRUE
16 return 0;
17 }
18 LRESULT OnDestroy(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) {
19 PostQuitMessage(0); // 退出消息循环
20 return 0;
21 }
22 LRESULT OnAbout(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled) { // ATL命令处理函数的标准形式
23 CSimpleDialog<IDD_ABOUTBOX> dlg;
24 dlg.DoModal(); // 显示『关于』对话框
25 return 0;
26 }
27 LRESULT OnExit(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled) {
28 this->DestroyWindow(); // 点击文件->关闭时,销毁窗口
29 return 0;
30 }
31 };
2 class CMainWindow : public CWindowImpl<CMainWindow> { // 主窗口,基于CWindowImpl模板
3 public:
4 BEGIN_MSG_MAP(CMainWindow) // 利用宏实现ProcessWindowMessage函数,用以分发消息
5 COMMAND_ID_HANDLER(IDM_ABOUT, OnAbout) // if命令分发分支
6 COMMAND_ID_HANDLER(IDM_EXIT, OnExit) // if命令分发分支
7 MESSAGE_HANDLER(WM_PAINT, OnPaint) // if消息分发分支
8 MESSAGE_HANDLER(WM_DESTROY, OnDestroy) // if消息分发分支
9 END_MSG_MAP()
10 LRESULT OnPaint(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { // ATL消息处理函数的标准形式
11 PAINTSTRUCT ps;
12 this->BeginPaint(&ps); // 开始绘图
13 // 在这里进行绘图操作
14 this->EndPaint(&ps); // 结束绘图
15 // bHandled如果不手动赋值FALSE的话,默认为TRUE
16 return 0;
17 }
18 LRESULT OnDestroy(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) {
19 PostQuitMessage(0); // 退出消息循环
20 return 0;
21 }
22 LRESULT OnAbout(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled) { // ATL命令处理函数的标准形式
23 CSimpleDialog<IDD_ABOUTBOX> dlg;
24 dlg.DoModal(); // 显示『关于』对话框
25 return 0;
26 }
27 LRESULT OnExit(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled) {
28 this->DestroyWindow(); // 点击文件->关闭时,销毁窗口
29 return 0;
30 }
31 };
五、在WinMain中加载窗口
加载一个Win32窗口很麻烦,但是加载一个ATL窗口是很简单的事情
——根本不用操心窗口类的注册,因为Create函数会自动为我们注册一个。
在WinMain中加载CMainWindow窗口:
1 int APIENTRY _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
2 LPTSTR lpCmdLine, int nCmdShow)
3 {
4 MSG msg;
5 hInst = hInstance; // 将实例句柄存储在全局变量中
6
7 // TODO: 初始化窗口
8 // 加载菜单资源
9 HMENU hMenu = LoadMenu(hInstance, MAKEINTRESOURCE(IDC_MYATLWINDOWTEST));
10
11 // 创建窗口
12 CMainWindow wnd;
13 wnd.Create(NULL, CWindow::rcDefault, _T("My Window"), WS_OVERLAPPEDWINDOW, WS_EX_CLIENTEDGE, hMenu);
14
15 // 显示并更新窗口
16 wnd.ShowWindow(nCmdShow);
17 wnd.UpdateWindow();
18
19 // 主消息循环:
20 while (GetMessage(&msg, NULL, 0, 0)) // 消息循环 - 等待消息
21 {
22 TranslateMessage(&msg); // 消息循环 - 翻译键盘消息
23 DispatchMessage(&msg); // 消息循环 - 分发消息
24 }
25
26 return (int) msg.wParam;
27 }
2 LPTSTR lpCmdLine, int nCmdShow)
3 {
4 MSG msg;
5 hInst = hInstance; // 将实例句柄存储在全局变量中
6
7 // TODO: 初始化窗口
8 // 加载菜单资源
9 HMENU hMenu = LoadMenu(hInstance, MAKEINTRESOURCE(IDC_MYATLWINDOWTEST));
10
11 // 创建窗口
12 CMainWindow wnd;
13 wnd.Create(NULL, CWindow::rcDefault, _T("My Window"), WS_OVERLAPPEDWINDOW, WS_EX_CLIENTEDGE, hMenu);
14
15 // 显示并更新窗口
16 wnd.ShowWindow(nCmdShow);
17 wnd.UpdateWindow();
18
19 // 主消息循环:
20 while (GetMessage(&msg, NULL, 0, 0)) // 消息循环 - 等待消息
21 {
22 TranslateMessage(&msg); // 消息循环 - 翻译键盘消息
23 DispatchMessage(&msg); // 消息循环 - 分发消息
24 }
25
26 return (int) msg.wParam;
27 }
六、运行
七、发布
将默认目标改为Release,右击项目->属性->C/C++->代码生成,运行库设置为『多线程 (/MT)』,以便可以免运行库:
按F7生成,然后打开项目父目录,找到Release文件夹(不是项目子目录下的Release),可以找到我们可以发布的程序:
八、总结
通过ATL,我们使用很短的代码就实现了一个标准的Windows窗口,比用传统的Win32方法不知道高到哪里去了,然而程序的体积并没有大幅度的增长,相对于MFC,还算是轻量级的。
注:内部使用IWebBrowser2,实现多进程多线程通讯,cookies共享等等问题,可以尝试解决定制浏览器的问题。
网上的参考:
1 #include <atlbase.h>
2 #include <atlwin.h>
3 class CMyWindow
4 : public CWindowImpl<CMyWindow, CWindow, CWinTraits<WS_OVERLAPPEDWINDOW, 0> >
5 {
6 public:
7 DECLARE_WND_CLASS(_T("CMyWindow"))
8 BEGIN_MSG_MAP(CMyWindow)
9 MESSAGE_HANDLER(WM_PAINT, OnPaint)
10 END_MSG_MAP()
11 LRESULT OnPaint(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL & bHandled)
12 {
13 PAINTSTRUCT ps;
14 ::BeginPaint(m_hWnd, &ps);
15 ::EndPaint(m_hWnd, &ps);
16 bHandled = TRUE;
17 return 0;
18 }
19 void OnFinalMessage(HWND hwnd)
20 {
21 ::PostQuitMessage(0);
22 }
23 };
24 int APIENTRY _tWinMain(HINSTANCE hInstance,
25 HINSTANCE hPrevInstance,
26 LPTSTR lpCmdLine,
27 int nCmdShow)
28 {
29 CMyWindow myWnd;
30 myWnd.Create(NULL, CMyWindow::rcDefault, _T("Hello, world"));
31 myWnd.ShowWindow(nCmdShow);
32 myWnd.UpdateWindow();
33 MSG msg;
34 msg.message = ~(UINT)WM_QUIT;
35 while(msg.message != WM_QUIT)
36 {
37 if(::GetMessage(&msg, NULL, 0, 0))
38 {
39 TranslateMessage(&msg);
40 DispatchMessage(&msg);
41 }
42 }
43 return (int)msg.wParam;
44 }
2 #include <atlwin.h>
3 class CMyWindow
4 : public CWindowImpl<CMyWindow, CWindow, CWinTraits<WS_OVERLAPPEDWINDOW, 0> >
5 {
6 public:
7 DECLARE_WND_CLASS(_T("CMyWindow"))
8 BEGIN_MSG_MAP(CMyWindow)
9 MESSAGE_HANDLER(WM_PAINT, OnPaint)
10 END_MSG_MAP()
11 LRESULT OnPaint(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL & bHandled)
12 {
13 PAINTSTRUCT ps;
14 ::BeginPaint(m_hWnd, &ps);
15 ::EndPaint(m_hWnd, &ps);
16 bHandled = TRUE;
17 return 0;
18 }
19 void OnFinalMessage(HWND hwnd)
20 {
21 ::PostQuitMessage(0);
22 }
23 };
24 int APIENTRY _tWinMain(HINSTANCE hInstance,
25 HINSTANCE hPrevInstance,
26 LPTSTR lpCmdLine,
27 int nCmdShow)
28 {
29 CMyWindow myWnd;
30 myWnd.Create(NULL, CMyWindow::rcDefault, _T("Hello, world"));
31 myWnd.ShowWindow(nCmdShow);
32 myWnd.UpdateWindow();
33 MSG msg;
34 msg.message = ~(UINT)WM_QUIT;
35 while(msg.message != WM_QUIT)
36 {
37 if(::GetMessage(&msg, NULL, 0, 0))
38 {
39 TranslateMessage(&msg);
40 DispatchMessage(&msg);
41 }
42 }
43 return (int)msg.wParam;
44 }