摘要:讨论Active Template Library (ATL) 3.0中的一些类,这些类围绕着Windows API建立了一个面向对象的编程框架,使用这个框架,可以简化Microsoft® Windows®编程并且只需要很少的系统开销。内容包括:考察对窗口做了简单封装的CWindow类;使用CWindowImpl进行消息处理和消息映射;使用ATL中的对话框类以及扩展现有窗口类的功能的方法。
内容:
简介
CWindow
CWindowImpl
一个简单而完整的示例
消息映射
为现有的窗口类添加功能
基类消息链
窗口的超类化
窗口子类化
被包含的窗口
消息反射
ATL中的对话框类
指定窗口类的信息
结论
简介:
虽然Active Template Library (ATL)主要是为了支持COM开发而设计的,但它确实包含了很多可用于窗口设计的类。这些窗口类和ATL中的其它类一样,都是基于模版的,并且只需要花费很少系统开销。这篇文章就向我们演示了使用ATL创建窗口和对话框并进行消息处理的基本方法。
这篇文章假设读者熟悉C++语言和Windows程序设计;但是并不一定要求读者具有COM方面的知识。
CWindow:
在ATL窗口类中,CWindow是最基本的。这个类对Windows API进行了面向对象的包装,它封装了一个窗口句柄,并提供一些成员函数来操作它,这些函数包装了相应的Windows API。
标准的Windows程序设计看起来象这样:
理解了ATL中的窗口对象和Windows系统中窗口的区别,就更加容易理解CWindow对象的构造与窗口的创建是两个分开的过程。我们再看看前面的代码,就会发现,首先是一个CWindow对象被构造:
不幸的是,CWindow类不能让我们自己决定窗口如何响应消息。当然,我们可以使用CWindow类提供的方法来使一个窗口居中或隐藏,甚至可以向一个窗口发送消息,但是当窗口收到消息后怎么处理则取决于创建这个窗口时使用的窗口类,如果我们是创建的是”button”类的窗口,那么它的表现就象个按钮,如果用”listbox”类创建,那它就具有跟列表框相同的行为,使用CWindow类我们没有办法改变这点。幸好,ATL为我们提供了另外一个类CWindowImpl,它允许我们指定窗口的新行为。
CWindowImpl:
CWindowImpl类是从CWindow类派生的,所以我们依然可以使用CWindow类中的成员函数,但是CWindowImpl类的功能更强大,它允许我们指定窗口怎样处理消息。在传统的窗口编程中,如果我们要处理窗口消息,我们必须使用窗口函数;但是使用ATL,我们只需要在我们的ATL窗口类中定义一个消息映射。
首先,从CWindowImpl类派生自己的窗口类,如下:
然后在类的定义里面定义如下的消息映射:
最后就是定义处理消息的函数了,如下:
当窗口收到一个消息,它将从消息映射表的顶部开始查找匹配的消息处理函数,因此把最常用的消息放在消息映射表的前面是个不错的注意。如果没有找到匹配的消息处理函数,则这个消息被发送到默认的窗口过程进行处理。
ATL的消息映射表封装了Windows的消息处理过程,它比传统的窗口函数中的大量switch分支或者if语句看起来更加直观。
要创建一个基于CWindowImpl派生类的窗口,请调用CWindowImpl类的Create方法:
一个简单而完整的示例:
这篇文章中的大部分示例都只是代码片段,但是下面列出的是一个完整的Hello world的示例程序。虽然我们使用的是ATL,但是没有涉及到COM,因此在使用Visual C++®建立项目的时候,我们选择Win32® application而不是ATL COM:
在stdafx.h文件中,加入下面几行:
内容:
简介
CWindow
CWindowImpl
一个简单而完整的示例
消息映射
为现有的窗口类添加功能
基类消息链
窗口的超类化
窗口子类化
被包含的窗口
消息反射
ATL中的对话框类
指定窗口类的信息
结论
简介:
虽然Active Template Library (ATL)主要是为了支持COM开发而设计的,但它确实包含了很多可用于窗口设计的类。这些窗口类和ATL中的其它类一样,都是基于模版的,并且只需要花费很少系统开销。这篇文章就向我们演示了使用ATL创建窗口和对话框并进行消息处理的基本方法。
这篇文章假设读者熟悉C++语言和Windows程序设计;但是并不一定要求读者具有COM方面的知识。
CWindow:
在ATL窗口类中,CWindow是最基本的。这个类对Windows API进行了面向对象的包装,它封装了一个窗口句柄,并提供一些成员函数来操作它,这些函数包装了相应的Windows API。
标准的Windows程序设计看起来象这样:
HWND hWnd = ::CreateWindow( "button", "Click me", WS_CHILD, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL ); ::ShowWindow( hWnd, nCmdShow ); ::UpdateWindow( hWnd );使用ATL中的CWindow类后,等效代码如下:
CWindow win; win.Create( "button", NULL, CWindow::rcDefault, "Click me", WS_CHILD ); win.ShowWindow( nCmdShow ); win.UpdateWindow();我们应该在我们的大脑中我们应该保持这样一个概念:ATL的窗口对象与Windows系统中的窗口是不同的。Windows系统中的窗口指的是操作系统中维持的一块数据,操作系统靠这块数据来操作屏幕上的一块区域。而一个ATL窗口对象,是CWindow类的一个实例,它是一个C++对象,它的内部没有保存任何有关屏幕区域或者窗口数据结构的内容,只保存了一个窗口的句柄,这个句柄保存在它的数据成员m_hWnd中,CWindow对象和它在屏幕上显示出来的窗口就是靠这个句柄联系起来的。
理解了ATL中的窗口对象和Windows系统中窗口的区别,就更加容易理解CWindow对象的构造与窗口的创建是两个分开的过程。我们再看看前面的代码,就会发现,首先是一个CWindow对象被构造:
CWindow win;然后创建它的窗口:
win.Create( "button", NULL, CWindow::rcDefault, "Click me", WS_CHILD );我们也可以构造一个CWindow对象,然后把它和一个已经存在的窗口关联起来,这样我们就可以通过CWindow类的成员函数来操作这个已经存在的窗口。这种方法非常有用,因为CWindow类提供的函数都是封装好了的,用起来很方便,比如CWindow类中的CenterWindow, GetDescendantWindow等函数用起来就比直接使用Windows API方便得多。
HWND hWnd = CreateWindow( szWndClass, "Main window", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL ); // 下面的方法中可以任选一种: // CWindow win( hWnd ); // 通过构造函数关联 // 或 // CWindow win; // win = hWnd; // 通过赋值操作符关联 // 或 // CWindow win; // win.Attach( hWnd ); // 使用Attach()方法关联 win.CenterWindow(); // 现在可以使用win对象来代替hWnd进行操作 win.ShowWindow( nCmdShow ); win.UpdateWindow();CWindow类也提供了一个HWND操作符,可以把CWindow类的对象转化为窗口句柄,这样,任何要求使用HWND的地方都可以使用CWindow类的对象代替:
::ShowWindow( win, nCmdShow ); // 此API函数本来要求HWND类型的参数CWindow类使得对窗口的操作更简单,而且不会增加系统开销——它经过编译和优化后的代码与使用纯API编程的代码是等价的。
不幸的是,CWindow类不能让我们自己决定窗口如何响应消息。当然,我们可以使用CWindow类提供的方法来使一个窗口居中或隐藏,甚至可以向一个窗口发送消息,但是当窗口收到消息后怎么处理则取决于创建这个窗口时使用的窗口类,如果我们是创建的是”button”类的窗口,那么它的表现就象个按钮,如果用”listbox”类创建,那它就具有跟列表框相同的行为,使用CWindow类我们没有办法改变这点。幸好,ATL为我们提供了另外一个类CWindowImpl,它允许我们指定窗口的新行为。
CWindowImpl:
CWindowImpl类是从CWindow类派生的,所以我们依然可以使用CWindow类中的成员函数,但是CWindowImpl类的功能更强大,它允许我们指定窗口怎样处理消息。在传统的窗口编程中,如果我们要处理窗口消息,我们必须使用窗口函数;但是使用ATL,我们只需要在我们的ATL窗口类中定义一个消息映射。
首先,从CWindowImpl类派生自己的窗口类,如下:
class CMyWindow : public CWindowImpl {注意,我们自己的类名必须作为一个模版参数传递给CWindowImpl类。
然后在类的定义里面定义如下的消息映射:
BEGIN_MSG_MAP(CMyWindow) MESSAGE_HANDLER(WM_PAINT,OnPaint) MESSAGE_HANDLER(WM_CREATE,OnCreate) MESSAGE_HANDLER(WM_DESTROY,OnDestroy) END_MSG_MAP()下面这句
MESSAGE_HANDLER(WM_PAINT,OnPaint)的意思是,当WM_PAINT消息到达时,将调用CMyWindow::OnPaint成员函数。
最后就是定义处理消息的函数了,如下:
LRESULT OnPaint( UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled ) { ... } LRESULT OnCreate( UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled ) { ... } LRESULT OnDestroy( UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled ) { ... } }; // CmyWindow这些函数中的参数意义为:第一个是消息ID,中间的两个参数的意义取决于消息类型,第四个参数是一个标志,用它来决定这个消息是已经处理完了还是需要进一步的处理。关于这些参数,我们在Message Map小结有更详细的讨论。
当窗口收到一个消息,它将从消息映射表的顶部开始查找匹配的消息处理函数,因此把最常用的消息放在消息映射表的前面是个不错的注意。如果没有找到匹配的消息处理函数,则这个消息被发送到默认的窗口过程进行处理。
ATL的消息映射表封装了Windows的消息处理过程,它比传统的窗口函数中的大量switch分支或者if语句看起来更加直观。
要创建一个基于CWindowImpl派生类的窗口,请调用CWindowImpl类的Create方法:
CMyWindow wnd; // 构造一个 CMyWindow 类的对象 wnd.Create( NULL, CWindow::rcDefault, _T("Hello"), WS_OVERLAPPEDWINDOW|WS_VISIBLE );注意,CWindowImpl类的Create方法与CWindow类的Create方法略有不同,在CWindow类的Create中,我们必须指定一个注册了的窗口类,但是CWindowImpl则不同,它创建一个新的窗口类,因此,不需要为它指定窗口类。
一个简单而完整的示例:
这篇文章中的大部分示例都只是代码片段,但是下面列出的是一个完整的Hello world的示例程序。虽然我们使用的是ATL,但是没有涉及到COM,因此在使用Visual C++®建立项目的时候,我们选择Win32® application而不是ATL COM:
在stdafx.h文件中,加入下面几行:
#include <atlbase.h> extern CComModule _Module; #include <atlwin.h>在hello.cpp文件中,写如下代码:
#include "stdafx.h" CComModule _Module; class CMyWindow : public CWindowImpl<CMyWindow> { BEGIN_MSG_MAP( CMyWindow ) MESSAGE_HANDLER( WM_PAINT, OnPaint ) MESSAGE_HANDLER( WM_DESTROY, OnDestroy ) END_MSG_MAP() LRESULT OnPaint( UINT, WPARAM, LPARAM, BOOL& ){ PAINTSTRUCT ps; HDC hDC = GetDC(); BeginPaint( &ps ); TextOut( hDC, 0, 0, _T("Hello world"), 11 ); EndPaint( &ps ); return 0; } LRESULT OnDestroy( UINT, WPARAM, LPARAM, BOOL& ){ PostQuitMessage( 0 ); return 0; } }; int APIENTRY WinMain( HINSTANCE hInstance, HINSTANCE, LPSTR, int ) { _Module.Init( NULL, hInst