本文是我从别的网站上下载下来的,现在我将它贴出来与大家共同分享!!!!!!!!!!
ATL3.0
中的窗口类
作者: Michael Park
译者:京山游侠
作者: Michael Park
译者:京山游侠
原文出处:
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnvc60/html/atlwindow.asp
摘要:讨论 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 程序设计看起来象这样:
摘要:讨论 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 程序设计看起来象这样:
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 对象被构造:
理解了 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 类派生自己的窗口类,如下:
不幸的是, 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 方法:
当窗口收到一个消息,它将从消息映射表的顶部开始查找匹配的消息处理函数,因此把最常用的消息放在消息映射表的前面是个不错的注意。如果没有找到匹配的消息处理函数,则这个消息被发送到默认的窗口过程进行处理。
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 文件中,加入下面几行:
一个简单而完整的示例:
这篇文章中的大部分示例都只是代码片段,但是下面列出的是一个完整的 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, hInstance );
CMyWindow wnd;
wnd.Create( NULL, CWindow::rcDefault, _T("Hello"),
WS_OVERLAPPEDWINDOW|WS_VISIBLE );
MSG msg;
while( GetMessage( &msg, NULL, 0, 0 ) ){
TranslateMessage( &msg );
DispatchMessage( &msg );
}
_Module.Term();
return msg.wParam;
}
在这个示例程序中,
CmyWindow
是从
CWindowImpl
派生的,它的消息映射捕获了两个消息
WM_PAINT
和
WM_DESTROY
,当收到
WM_PAINT
消息时,它的成员函数
OnPaint
处理这个消息并在窗口上输出
“Hello world”
,当收到
WM_DESTROY
消息时,也就是当用户关闭这个窗口的时候,调用
OnDestroy
函数处理这个消息,在
OnDestroy
函数中调用
PostQuitMessage
来结束消息循环。
WinMain 函数中创建了一个 CmyWindow 类的实例并实现了一个标准的消息循环。(有一些地方,我们必须遵循 ATL 的规范,比如在这里我们必须使用 _Module 。)
消息映射:
有三组用于消息映射的宏,他们分别是:
WinMain 函数中创建了一个 CmyWindow 类的实例并实现了一个标准的消息循环。(有一些地方,我们必须遵循 ATL 的规范,比如在这里我们必须使用 _Module 。)
消息映射:
有三组用于消息映射的宏,他们分别是:
- 窗口消息映射宏,用于所有的窗口消息(如WM_CREATE、WM_PAINT等);
- 命令消息映射宏,专用于WM_COMMAND消息(比如由控件或菜单发出的消息);
- 通知消息映射宏,专用于WM_NOTUFY消息(通常由通用控件发出此消息,比如工具栏控件或列表视图控件)
窗口消息映射宏:
有两个窗口消息映射宏,他们分别是:
有两个窗口消息映射宏,他们分别是:
- MESSAGE_HANDLER
- MESSAGE_RANGE_HANDLER
第一个宏将一个特定的消息映射到相应的处理函数;第二个宏将一组消息映射到一个处理函数。消息处理函数都要求具有如下的原形:
LRESULT MessageHandler(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
其中,参数
uMsg
是消息标识,
wParam
和
lParam
是两个附加与消息的参数,(他们的具体意义取决与消息类别。)
消息处理函数使用 bHandled 来标志消息是否已经被完全捕获,如果 bHandled 被设置成 FALSE ,程序将继续在消息映射表的后续部分查找这个消息的其它处理函数。这个特性使得我们对一个消息使用多个处理函数成为可能。什么时候需要对一个消息使用多个处理函数呢?可能是在对多个类链接时,也可能是我们只想对一个消息做出响应但是并不真正捕获它。在处理函数被调用之前, bHandled 被置为 TRUE ,所以如果我们不在函数的结尾显式地将它置为 FALSE ,则消息映射表的后续部分不会被继续查找,也不会有其它的处理函数被调用。
命令消息映射宏:
命令消息映射宏只处理命令消息( WM_COMMAND 消息),但是它能让我们根据消息类型或者发送命令消息的控件 ID 来指定消息处理函数。
消息处理函数使用 bHandled 来标志消息是否已经被完全捕获,如果 bHandled 被设置成 FALSE ,程序将继续在消息映射表的后续部分查找这个消息的其它处理函数。这个特性使得我们对一个消息使用多个处理函数成为可能。什么时候需要对一个消息使用多个处理函数呢?可能是在对多个类链接时,也可能是我们只想对一个消息做出响应但是并不真正捕获它。在处理函数被调用之前, bHandled 被置为 TRUE ,所以如果我们不在函数的结尾显式地将它置为 FALSE ,则消息映射表的后续部分不会被继续查找,也不会有其它的处理函数被调用。
命令消息映射宏:
命令消息映射宏只处理命令消息( WM_COMMAND 消息),但是它能让我们根据消息类型或者发送命令消息的控件 ID 来指定消息处理函数。
- COMMAND_HANDLER映射一个特定控件的一条特定消息到一个处理函数;
- COMMAND_ID_HANDLER映射一个特定控件的所有消息到一个处理函数;
- COMMAND_CODE_HANDLER映射任意控件的一个特定消息到一个处理函数;
- COMMAND_RANGE_HANDLER映射一定范围内的控件的所有消息到一个处理函数;
- COMMAND_RANGE_CODE_HANDLER映射一定范围内的控件的一条特定消息到一个处理函数。
命令消息处理函数应该具有如下的原形:
LRESULT CommandHandler(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled);
其中,参数
wNotifyCode
代表消息代码,
wID
代表发送消息的控件的
ID
,
hWndCtl
代表发送消息的控件的窗口句柄,
bHandled
的意义如前所述。
通知消息映射宏:
通知消息映射宏用来处理通知消息( WM_NOTUFY 消息),它根据通知消息的类型和发送通知消息的控件的不同将消息映射到不同的处理函数,这些宏与前面讲的命令消息映射宏是等价的,唯一的不同就是它处理的是通知消息而不是命令消息。
通知消息映射宏:
通知消息映射宏用来处理通知消息( WM_NOTUFY 消息),它根据通知消息的类型和发送通知消息的控件的不同将消息映射到不同的处理函数,这些宏与前面讲的命令消息映射宏是等价的,唯一的不同就是它处理的是通知消息而不是命令消息。
- NOTIFY_HANDLER
- NOTIFY_ID_HANDLER
- NOTIFY_CODE_HANDLER
- NOTIFY_RANGE_HANDLER
- NOTIFY_RANGE_CODE_HANDLER
通知消息处理函数都需要如下的原形:
LRESULT NotifyHandler(int idCtrl, LPNMHDR pnmh, BOOL& bHandled);
其中,参数
idCtrl
代表发送通知消息的控件的
ID
,参数
pnmh
是指向一个
NMHDR
结构的指针,
bHandled
的意义如前所述。
通知消息包含了一个指向消息细节的结构的指针,例如,当一个列表视图控件发送一个通知消息,这个消息就包含了一个指向 NMLVDISPINFO 结构的指针,所有类似于 NMLVDISPINFO 的结构都包含一个 NMHDR 结构的头, pnmh 就指向这个头,如果需要访问这种结构中头部以外的其它数据成员,可以将 pnmh 转化成相应类型的指针。
例如,我们如果要处理列表视图控件发出的 LVN_ENDLABELEDIT 通知消息,我们可以把下面这行代码放到消息映射表中:
通知消息包含了一个指向消息细节的结构的指针,例如,当一个列表视图控件发送一个通知消息,这个消息就包含了一个指向 NMLVDISPINFO 结构的指针,所有类似于 NMLVDISPINFO 的结构都包含一个 NMHDR 结构的头, pnmh 就指向这个头,如果需要访问这种结构中头部以外的其它数据成员,可以将 pnmh 转化成相应类型的指针。
例如,我们如果要处理列表视图控件发出的 LVN_ENDLABELEDIT 通知消息,我们可以把下面这行代码放到消息映射表中:
NOTIFY_HANDLER( ID_LISTVIEW, LVN_ENDLABELEDIT, OnEndLabelEdit)
这个通知消息附带的额外信息包含在一个
NMLVDISPINFO
结构中,因此,消息处理函数看起来应该象下面这个样子:
LRESULT OnEndLabelEdit(int idCtrl, LPNMHDR pnmh, BOOL& bHandled)
{
// The item is -1 if editing is being canceled.
if ( ((NMLVDISPINFO*)pnmh)->item.iItem == -1) return FALSE;
...
可以看出,
pnmh
指针被转化成
NMLVDISPINFO*
类型,以便访问头部结构以外的数据。
为现有的窗口类添加功能:
有许多向现有的窗口添加功能的方法。如果这个类是 ATL 窗口类,我们可以从这个窗口类派生自己的类,就象 Base Class Chaining 中描述的一样。这种方法主要是一个 C++ 类的继承加上一点消息映射的链接。
如果我们想扩展一个预定义的窗口类(如按纽类或列表框类)的功能,我们可以超类化它。就是创建一个基于这个预定义类的新类,并在消息映射表中添加消息映射以增强它的功能。
有些时候,我们需要改变一个已经存在的窗口实例的行为,而不是一个窗口类 —— 或许我们要让一个对话框上的编辑框做点什么特别的事情。在这种情况下,我们可以写一个新的 ATL 窗口类,并子类化这个已经存在的编辑框。任何本该发送到这个编辑框的消息都会先被发送到这个子类的对象。
另外一种可选的方法:我们也可以让这个编辑框成为一个被包含的窗口,所有发送到这个编辑框的消息都会经过它的容器窗口;我们可以在这个容器窗口中为这个被包含的窗口实现特殊的消息处理。
最后的一种方法就是消息反射,当一个窗口收到一个消息后不处理它,而是反射给发送这个消息的窗口自己处理,这种技术可以用来创建自包含的控件。
基类消息链( Base Class Chaining ):
如果我们已经有一些实现了特定功能的 ATL 窗口类,我们可以从它们派生新类以充分利用继承的优点。比如:
为现有的窗口类添加功能:
有许多向现有的窗口添加功能的方法。如果这个类是 ATL 窗口类,我们可以从这个窗口类派生自己的类,就象 Base Class Chaining 中描述的一样。这种方法主要是一个 C++ 类的继承加上一点消息映射的链接。
如果我们想扩展一个预定义的窗口类(如按纽类或列表框类)的功能,我们可以超类化它。就是创建一个基于这个预定义类的新类,并在消息映射表中添加消息映射以增强它的功能。
有些时候,我们需要改变一个已经存在的窗口实例的行为,而不是一个窗口类 —— 或许我们要让一个对话框上的编辑框做点什么特别的事情。在这种情况下,我们可以写一个新的 ATL 窗口类,并子类化这个已经存在的编辑框。任何本该发送到这个编辑框的消息都会先被发送到这个子类的对象。
另外一种可选的方法:我们也可以让这个编辑框成为一个被包含的窗口,所有发送到这个编辑框的消息都会经过它的容器窗口;我们可以在这个容器窗口中为这个被包含的窗口实现特殊的消息处理。
最后的一种方法就是消息反射,当一个窗口收到一个消息后不处理它,而是反射给发送这个消息的窗口自己处理,这种技术可以用来创建自包含的控件。
基类消息链( Base Class Chaining ):
如果我们已经有一些实现了特定功能的 ATL 窗口类,我们可以从它们派生新类以充分利用继承的优点。比如:
class CBase: public CWindowImpl< CBase >
// simple base window class: shuts down app when closed
{
BEGIN_MSG_MAP( CBase )
MESSAGE_HANDLER( WM_DESTROY, OnDestroy )
END_MSG_MAP()
LRESULT OnDestroy( UINT, WPARAM, LPARAM, BOOL& )
{
PostQuitMessage( 0 );
return 0;
}
};
class CDerived: public CBase
// derived from CBase; handles mouse button events
{
BEGIN_MSG_MAP( CDerived )
MESSAGE_HANDLER( WM_LBUTTONDOWN, OnButtonDown )
END_MSG_MAP()
LRESULT OnButtonDown( UINT, WPARAM, LPARAM, BOOL& )
{
ATLTRACE( "button down/n" );
return 0;
}
};
// in WinMain():
...
CDerived win;
win.Create( NULL, CWindow::rcDefault, "derived window" );
可是,上面的代码有一个问题。当我们在调试模式下运行这个程序,一个窗口出现了,如果我们在这个窗口中单击,
“button down”
将出现在输出窗口中,这是
CDrived
类的功能,可是,当我们关闭这个窗口的时候,程序并不退出,尽管
CBase
类处理了
WM_DESTROY
消息并且
CDrived
类是从
CBase
类派生的。
Why ?因为我们必须明确地将一个消息映射表链接到另外一个。如下:
Why ?因为我们必须明确地将一个消息映射表链接到另外一个。如下:
BEGIN_MSG_MAP( CDerived )
MESSAGE_HANDLER( WM_LBUTTONDOWN, OnButtonDown )
CHAIN_MSG_MAP( CBase ) //
链接到基类
END_MSG_MAP()
现在,任何在
CDrived
类中没有被处理的消息都会被传到
CBase
类中。
为什么不自动将派生类的消息映射和它的基类的消息映射链接起来呢?这是因为在 ATL 的体系结构中有很多多重继承的情况,这种情况下没有办法知道究竟应该链接到哪个基类,所以只好让程序员自己来做决定。
可选的消息映射:
消息映射链允许多个类同时进行消息处理,同时也带来了问题:如果我们在多个类中都要响应 WM_CREATE 消息,但是不同的类需要基类提供不同的处理,怎么办呢?为了解决这个问题, ATL 使用了可选的消息映射:将消息映射表分成很多节,每一节用不同的数字标识,每一节都是一个可选的消息映射表。
为什么不自动将派生类的消息映射和它的基类的消息映射链接起来呢?这是因为在 ATL 的体系结构中有很多多重继承的情况,这种情况下没有办法知道究竟应该链接到哪个基类,所以只好让程序员自己来做决定。
可选的消息映射:
消息映射链允许多个类同时进行消息处理,同时也带来了问题:如果我们在多个类中都要响应 WM_CREATE 消息,但是不同的类需要基类提供不同的处理,怎么办呢?为了解决这个问题, ATL 使用了可选的消息映射:将消息映射表分成很多节,每一节用不同的数字标识,每一节都是一个可选的消息映射表。
// in class CBase:
BEGIN_MSG_MAP( CBase )
MESSAGE_HANDLER( WM_CREATE, OnCreate1 )
MESSAGE_HANDLER( WM_PAINT, OnPaint1 )
ALT_MSG_MAP( 100 )
MESSAGE_HANDLER( WM_CREATE, OnCreate2 )
MESSAGE_HANDLER( WM_PAINT, OnPaint2 )
ALT_MSG_MAP( 101)
MESSAGE_HANDLER( WM_CREATE, OnCreate3 )
MESSAGE_HANDLER( WM_PAINT, OnPaint3 )
END_MSG_MAP()
如上,基类的消息映射表由
3
节组成:一个默认的消息映射表(隐含的标识为
0
)和两个可选的消息映射表(标识为
100
和
101
)。
当你链接消息映射表时,指定你所希望的方案的标识,如下:
当你链接消息映射表时,指定你所希望的方案的标识,如下:
class CDerived: public CBase {
BEGIN_MSG_MAP( CDerived )
CHAIN_MSG_MAP_ALT( CBase, 100 )
END_MSG_MAP()
...
CDrived
类的消息映射表链接到
CBase
类中标识号为
100
的可选节,因此当
WM_PAINT
到达时,
CBase::OnPaint2
被调用。
(译者注:我觉得这种方法不太合乎 C++ 的思想,基类的编写者不一定总能知道派生自它的类会有哪些需求,而且把所有不同的版本都在基类中实现,基类中无用的代码量会大大增加。更好的办法应该是把基类中的消息处理函数声明为虚函数。总之,我觉得这一小节并不能体现出可选消息映射的真正用途。)
其它类型的链:
除了基类消息映射链, ATL 也提供了成员链( member chaining )和动态链 (dynamic chaining) ,这些很少使用到的链技术超出了我们这篇文章的讨论范围,但是可以简单提一下。成员链允许把消息映射链接到一个类的成员变量,动态链允许在运行时进行动态链接。如果你想了解更多,请参考 ATL 文档中的 CHAIN_MSG_MAP_DYNAMIC 和 CHAIN_MSG_MAP_MEMBER 的相关内容。
窗口的超类化:
超类化定义一个类,并为预定义的窗口类(如按钮类或列表框类)添加新的功能,下面的例子超类化一个按钮,让这个按钮在被单击的时候发出蜂鸣。
(译者注:我觉得这种方法不太合乎 C++ 的思想,基类的编写者不一定总能知道派生自它的类会有哪些需求,而且把所有不同的版本都在基类中实现,基类中无用的代码量会大大增加。更好的办法应该是把基类中的消息处理函数声明为虚函数。总之,我觉得这一小节并不能体现出可选消息映射的真正用途。)
其它类型的链:
除了基类消息映射链, ATL 也提供了成员链( member chaining )和动态链 (dynamic chaining) ,这些很少使用到的链技术超出了我们这篇文章的讨论范围,但是可以简单提一下。成员链允许把消息映射链接到一个类的成员变量,动态链允许在运行时进行动态链接。如果你想了解更多,请参考 ATL 文档中的 CHAIN_MSG_MAP_DYNAMIC 和 CHAIN_MSG_MAP_MEMBER 的相关内容。
窗口的超类化:
超类化定义一个类,并为预定义的窗口类(如按钮类或列表框类)添加新的功能,下面的例子超类化一个按钮,让这个按钮在被单击的时候发出蜂鸣。
class CBeepButton: public CWindowImpl< CBeepButton >
{
public:
DECLARE_WND_SUPERCLASS( _T("BeepButton"), _T("Button") )
BEGIN_MSG_MAP( CBeepButton )
MESSAGE_HANDLER( WM_LBUTTONDOWN, OnLButtonDown )
END_MSG_MAP()
LRESULT OnLButtonDown( UINT, WPARAM, LPARAM, BOOL& bHandled )
{
MessageBeep( MB_ICONASTERISK );
bHandled = FALSE; // alternatively: DefWindowProc()
return 0;
}
}; // CBeepButton
DECLARE_WND_SUPERCLASS
宏声明了这个窗口的类名(
“BeepButton”
)和被超类化的类名(
“Button”
)。它的消息映射表只有一个入口项,将
WM_LBUTTONDOWN
消息映射到
OnLButtonDown
函数。其余的消息都让默认的窗口过程处理,除了可以发出蜂鸣外,
CbeepButton
需要和其它的按钮表现相同,因此在
OnLButtonDown
函数的最后,需要将
bHandled
设置为
FALSE
,让默认的窗口过程在
OnLButtonDown
函数完成后对
WM_LBUTTONDOWN
消息进行其它的处理。(另外的一种方法是直接调用
DefWindowProc
函数。)
到目前为止,我们所做的只是定义了一个新类;我们依然需要创建一些真正的 CbeepButton 窗口,下面的类定义了两个 CbeepButton 类型的成员变量,因此,当这个类的窗口被创建时,将会创建两个 CbeepButton 类型的子窗口。
到目前为止,我们所做的只是定义了一个新类;我们依然需要创建一些真正的 CbeepButton 窗口,下面的类定义了两个 CbeepButton 类型的成员变量,因此,当这个类的窗口被创建时,将会创建两个 CbeepButton 类型的子窗口。
const int ID_BUTTON1 = 101;
const int ID_BUTTON2 = 102;
class CMyWindow: public CWindowImpl< CMyWindow, CWindow,
CWinTraits<WS_OVERLAPPEDWINDOW|WS_VISIBLE> >
{
CBeepButton b1, b2;
BEGIN_MSG_MAP( CMyWindow )
MESSAGE_HANDLER( WM_CREATE, OnCreate )
COMMAND_CODE_HANDLER( BN_CLICKED, OnClick )
END_MSG_MAP()
LRESULT OnClick(WORD wNotifyCode, WORD wID, HWND hWndCtl,
BOOL& bHandled)
{
ATLTRACE( "Control %d clicked/n", wID );
return 0;
}
LRESULT OnCreate( UINT, WPARAM, LPARAM, BOOL& )
{
RECT r1 = { 10, 10, 250, 80 };
b1.Create(*this, r1, "beep1", WS_CHILD|WS_VISIBLE, 0, ID_BUTTON1);
RECT r2 = { 10, 110, 250, 180 };
b2.Create(*this, r2, "beep2", WS_CHILD|WS_VISIBLE, 0, ID_BUTTON2);
return 0;
}
}; // CMyWindow
窗口的子类化:
子类化允许我们改变一个已经存在的窗口的行为,我们经常用它来改变控件的行为。它的实现机制是插入一个消息映射表来截取发向控件的消息。举例说明:假设有一个对话框,对话框上有一个编辑框控件,我们想让这个控件只接受不是数字的字符。我们可以截获发往这个控件的 WM_CHAR 消息并抛弃接收到的数字字符。下面的类实现这个功能:
子类化允许我们改变一个已经存在的窗口的行为,我们经常用它来改变控件的行为。它的实现机制是插入一个消息映射表来截取发向控件的消息。举例说明:假设有一个对话框,对话框上有一个编辑框控件,我们想让这个控件只接受不是数字的字符。我们可以截获发往这个控件的 WM_CHAR 消息并抛弃接收到的数字字符。下面的类实现这个功能:
class CNoNumEdit: public CWindowImpl< CNoNumEdit >
{
BEGIN_MSG_MAP( CNoNumEdit )
MESSAGE_HANDLER( WM_CHAR, OnChar )
END_MSG_MAP()
LRESULT OnChar( UINT, WPARAM wParam, LPARAM, BOOL& bHandled )
{
TCHAR ch = wParam;
if( _T(''0'') <= ch && ch <= _T(''9'') )
MessageBeep( 0 );
else
bHandled = FALSE;
return 0;
}
};
这个类只处理一个消息
WM_CHAR
,如果这个字符是数字的话,则调用
MessageBeep( 0 )
并返回,这样可以有效地忽略这个字符。如果不是数字,则将
bHandled
设置为
FALSE
,指明默认的窗口过程这个消息需要进一步处理。
现在我们将子类化一个编辑框控件,以便 CnoNumEdit 能够抢先处理发到这个编辑框得消息。(下面得例子用到了 CdialogImpl 类,这个类我们将在 ATL 中的对话框类一节中介绍。)在这个例子中, CmyDialog 类中用到了一个对话框资源( ID 号为 IDD_DIALOG1 ),对话框中有一个编辑框控件( ID 号为 IDC_EDIT1 ),当对话框初始化的时候,编辑框经过 SubclassWindow 而变成一个不接受数字的编辑框:
现在我们将子类化一个编辑框控件,以便 CnoNumEdit 能够抢先处理发到这个编辑框得消息。(下面得例子用到了 CdialogImpl 类,这个类我们将在 ATL 中的对话框类一节中介绍。)在这个例子中, CmyDialog 类中用到了一个对话框资源( ID 号为 IDD_DIALOG1 ),对话框中有一个编辑框控件( ID 号为 IDC_EDIT1 ),当对话框初始化的时候,编辑框经过 SubclassWindow 而变成一个不接受数字的编辑框:
class CMyDialog: public CDialogImpl<CMyDialog>
{
public:
enum { IDD = IDD_DIALOG1 };
BEGIN_MSG_MAP( CMyDialog )
MESSAGE_HANDLER( WM_INITDIALOG, OnInitDialog )
END_MSG_MAP()
LRESULT OnInitDialog( UINT, WPARAM, LPARAM, BOOL& )
{
ed.SubclassWindow( GetDlgItem( IDC_EDIT1 ) );
return 0;
}
CNoNumEdit ed;
};
被包含的窗口:
一个被包含的窗口是一个不响应任何消息的窗口,它将收到的所有消息重新发送到另外一个窗口的消息映射,这个另外的窗口就是它的容器窗口。通常情况下,被包含的窗口是它的容器窗口的子窗口,但情况并不是总是这样。容器窗口并不是必须等同于父窗口,包含与被包含的关系取决于 C++ 类,被包含的窗口是容器窗口类的一个数据成员,而父窗口和子窗口的关系体现在屏幕上,它们的关系是创建窗口时确定的。
一个被包含的窗口建立在已注册的窗口类的基础之上,比如编辑框控件。如果一个编辑框被包含,那么发送到它的消息实际上被它的容器窗口的消息映射处理。使用这种方法,可以改变编辑框控件的标准行为。这有点类似于子类化但是不需要定义新类来子类化控件。和前面那个定义 CnoNumEdit 类响应 WM_CHAR 消息的例子相比,处理 WM_CHAR 消息的容器窗口类看起来如下:
一个被包含的窗口是一个不响应任何消息的窗口,它将收到的所有消息重新发送到另外一个窗口的消息映射,这个另外的窗口就是它的容器窗口。通常情况下,被包含的窗口是它的容器窗口的子窗口,但情况并不是总是这样。容器窗口并不是必须等同于父窗口,包含与被包含的关系取决于 C++ 类,被包含的窗口是容器窗口类的一个数据成员,而父窗口和子窗口的关系体现在屏幕上,它们的关系是创建窗口时确定的。
一个被包含的窗口建立在已注册的窗口类的基础之上,比如编辑框控件。如果一个编辑框被包含,那么发送到它的消息实际上被它的容器窗口的消息映射处理。使用这种方法,可以改变编辑框控件的标准行为。这有点类似于子类化但是不需要定义新类来子类化控件。和前面那个定义 CnoNumEdit 类响应 WM_CHAR 消息的例子相比,处理 WM_CHAR 消息的容器窗口类看起来如下:
class CMyWindow: public CWindowImpl
{
CContainedWindow m_contained;
public:
CMyWindow(): m_contained( _T("edit"), this, 99 )
{
}
...
CmyWindow
是一个容器窗口类,它的构造函数对
CcontainedWindow
类型的成员做这样的初始化:被包含的窗口是编辑框,发送它的消息到
“this”
(它的父窗口),使用可选消息映射表
99
。
BEGIN_MSG_MAP( CMyWindow )
MESSAGE_HANDLER( WM_CREATE, OnCreate )
MESSAGE_HANDLER( WM_DESTROY, OnDestroy )
ALT_MSG_MAP( 99 ) // contained window''s messages come here...
MESSAGE_HANDLER( WM_CHAR, OnChar )
END_MSG_MAP()
当父窗口被创建的时候,被包含的窗口也被创建(在
WM_CREATE
消息的响应函数中)。因为被包含的控件是以编辑框为基础的,所以它在屏幕上看起来象一个编辑框:
LRESULT OnCreate( UINT, WPARAM, LPARAM, BOOL& )
{
RECT rc = { 10, 10, 200, 35 };
m_contained.Create( *this, rc, _T("non-numeric edit"),
WS_CHILD|WS_VISIBLE|WS_BORDER, 0, 666 );
return 0;
}
在这个例子中,容器窗口同时也是被包含窗口的父窗口。
当被包含的窗口收到 WM_CHAR 消息时,容器窗口的 OnChar 成员函数被调用。这个函数和前面的 CnoNumEdit 例子中的相同,但是在这个例子中,它时容器类的成员函数。
当被包含的窗口收到 WM_CHAR 消息时,容器窗口的 OnChar 成员函数被调用。这个函数和前面的 CnoNumEdit 例子中的相同,但是在这个例子中,它时容器类的成员函数。
LRESULT OnChar( UINT, WPARAM wParam, LPARAM, BOOL& bHandled )
{
TCHAR ch = wParam;
if( _T(''0'') <= ch && ch <= _T(''9'') )
MessageBeep( 0 );
else
bHandled = FALSE;
return 0;
}
LRESULT OnDestroy( UINT, WPARAM, LPARAM, BOOL& )
{
PostQuitMessage( 0 );
return 0;
}
};
我们同样也可以用被包含的窗口来子类化对话框中已经存在的控件,和正规的子类化不同,被子类化的窗口的消息时被容器窗口捕获的。在下面的例子中,一个对话框子类化了一个编辑框控件,把它转化成了被包含的窗口;那个对话框(容器)捕获
WM_CHAR
消息并忽略掉数字字符,然后在发送到编辑框控件。(
CdialogImpl
在
ATL
中的对话框类一节讲述。)
class CMyDialog: public CDialogImpl<CMyDialog>
{
public:
enum { IDD = IDD_DIALOG1 };
// contained window is an edit control:
CMyDialog(): m_contained( "edit", this, 123 )
{
}
BEGIN_MSG_MAP( CMyDialog )
MESSAGE_HANDLER( WM_INITDIALOG, OnInitDialog )
ALT_MSG_MAP( 123 ) // contained window''s messages come here...
MESSAGE_HANDLER( WM_CHAR, OnChar )
END_MSG_MAP()
LRESULT OnInitDialog( UINT, WPARAM, LPARAM, BOOL& bHandled )
{
// when the dialog box is created, subclass its edit control:
m_contained.SubclassWindow( GetDlgItem(IDC_EDIT1) );
bHandled = FALSE;
return 0;
}
LRESULT OnChar( UINT, WPARAM wParam, LPARAM, BOOL& bHandled )
{
TCHAR ch = wParam;
if( _T(''0'') <= ch && ch <= _T(''9'') )
MessageBeep( 0 );
else
bHandled = FALSE;
return 0;
}
CContainedWindow m_contained;
};
消息反射:
前面讲述了一些扩展窗口功能的方法,这些方法是通过使窗口响应发往窗口的消息实现的。和前面的方法相反,消息反射是使窗口能够响应从它们自己发出的消息。
当用户和控件交互的时候,控件通常使发送一个 WM_COMMAND 或者 WM_NOTIFY 消息给它的父窗口;然后父窗口做出响应,比如:
前面讲述了一些扩展窗口功能的方法,这些方法是通过使窗口响应发往窗口的消息实现的。和前面的方法相反,消息反射是使窗口能够响应从它们自己发出的消息。
当用户和控件交互的时候,控件通常使发送一个 WM_COMMAND 或者 WM_NOTIFY 消息给它的父窗口;然后父窗口做出响应,比如:
class CParentWindow: CWindowImpl<CParentWindow>
{
//
假设这个窗口有一个按钮型的子窗口,
//
并且其 ID 为 ID_BUTTON
BEGIN_MSG_MAP( CParentWindow )
COMMAND_ID_HANDLER( ID_BUTTON, OnButton )
MESSAGE_HANDLER( WM_CTLCOLORBUTTON, OnColorButton )
...
当按钮被按下的时候,它发送一个命令消息给父窗口,然后
CParentWindow::OnButton
被调用。同理,当按钮需要被绘制的时候,它发送
WM_CTLCOLORBUTTON
消息给父窗口,
CParentWindow::OnColorButton
响应这个消息,它使用特定的画刷绘制控件。
某些情况下,让控件自己响应它发送出去的消息比让父窗口响应要好得多。 ATL 提供了消息反射的机制:当控件向父窗口发送消息的时候,父窗口能够将消息反射给控件。
某些情况下,让控件自己响应它发送出去的消息比让父窗口响应要好得多。 ATL 提供了消息反射的机制:当控件向父窗口发送消息的时候,父窗口能够将消息反射给控件。
class CParentWindow: CWindowImpl
{
BEGIN_MSG_MAP( CParentWindow )
MESSAGE_HANDLER( WM_CREATE, OnCreate )
MESSAGE_HANDLER( WM_DESTROY, OnDestroy )
...other messages that CParentWindow will handle...
REFLECT_NOTIFICATIONS()
END_MSG_MAP()
...
当父窗口收到一个消息,先查找它的消息映射表,如果没有和这个消息相匹配的入口,则
REFLECT_NOTIFICATIONS
宏使得该消息被反射给发送这个消息的控件。控件可以提供响应反射消息的处理函数,如下:
class CHandlesItsOwnMessages: CWindowImpl<CHandlesItsOwnMessage>
{
public:
DECLARE_WND_SUPERCLASS( _T("Superbutton"), _T("button") )
BEGIN_MSG_MAP( CHandlesItsOwnMessage )
MESSAGE_HANDLER( OCM_COMMAND, OnCommand )
MESSAGE_HANDLER( OCM_CTLCOLORBUTTON, OnColorButton )
DEFAULT_REFLECTION_HANDLER()
END_MSG_MAP()
...
注意,反射消息的消息标志以
OCM_
开头,而不是
WM_
。这可以让你区分这个消息究竟是否是被反射回来的。
这个控件要么是这个类的实例,要么是一个被子类化的按钮控件。例如:
这个控件要么是这个类的实例,要么是一个被子类化的按钮控件。例如:
// in CParentWindow:
CHandlesItsOwnMessages m_button;
LRESULT OnCreate( UINT, WPARAM, LPARAM, BOOL& )
{
RECT rc; // initialize appropriately
m_button.Create( *this, rc, _T("click me"), WS_CHILD|WS_VISIBLE );
...
或者,如果这个按钮控件是已存在的(例如,父窗口是一个对话框):
m_button.SubclassWindow( GetDlgItem(ID_BUTTON) );
下面的例子定义了一个
CstaticLink
类,它是一个
Static
控件,当点击它的时候,将打开一个指定的网页。所有从
CstaticLink
发送出去的消息都被它的父窗口反射回来(在这个例子中,用到对话框,请看
ATL
中的对话框类这一节)。除了响应反射回的命令消息,
CstaticLink
还处理反射回的
WM_CTLCOLORSTATIC
消息以便它能够让自己在点击前和点击后显示不同的颜色。
#include "stdafx.h"
#include "resource.h"
CComModule _Module;
class CStaticLink : public CWindowImpl<CStaticLink> {
/*
Based on CStaticLink by Paul DiLascia, C++ Q&A, Microsoft Systems
Journal 12/1997.
Turns static controls into clickable "links" -- when the control is
clicked, the file/program/webpage named in the control''s text (or
set by SetLinkText()) is opened via ShellExecute(). Static control
can be either text or graphic (bitmap, icon, etc.).
*/
public:
DECLARE_WND_SUPERCLASS( _T("StaticLink"), _T("Static") )
CStaticLink() :
m_colorUnvisited( RGB(0,0,255) ),
m_colorVisited( RGB(128,0,128) ),
m_bVisited( FALSE ),
m_hFont( NULL )
{
}
void SetLinkText( LPCTSTR szLink ) {
USES_CONVERSION;
m_bstrLink = T2OLE( szLink );
}
BEGIN_MSG_MAP(CStaticLink)
// uses message reflection: WM_* comes back as OCM_*
MESSAGE_HANDLER( OCM_COMMAND, OnCommand )
MESSAGE_HANDLER( OCM_CTLCOLORSTATIC, OnCtlColor )
MESSAGE_HANDLER( WM_DESTROY, OnDestroy ) // not a reflected message
DEFAULT_REFLECTION_HANDLER()
END_MSG_MAP()
LRESULT OnDestroy( UINT, WPARAM, LPARAM, BOOL& ) {
if( m_hFont ) DeleteObject( m_hFont );
return 0;
}
LRESULT OnCommand( UINT, WPARAM wParam, LPARAM, BOOL& ) {
USES_CONVERSION;
int code = HIWORD( wParam );
if( code == STN_CLICKED || code == STN_DBLCLK ){
if( m_bstrLink.Length() == 0 ){
GetWindowText( &m_bstrLink );
}
if( (int)ShellExecute( *this, _T("open"),
OLE2T(m_bstrLink), NULL, NULL, SW_SHOWNORMAL ) > 32 ){
m_bVisited = TRUE; // return codes > 32 => success
Invalidate();
}else{
MessageBeep( 0 );
ATLTRACE( _T("Error: CStaticLink couldn''t open file") );
}
}
return 0;
}
LRESULT OnCtlColor( UINT, WPARAM wParam, LPARAM, BOOL& ) {
// notify bit must be set to get STN_* notifications
ModifyStyle( 0, SS_NOTIFY );
HBRUSH hBr = NULL;
if( (GetStyle() & 0xff) <= SS_RIGHT ){
// it''s a text control: set up font and colors
if( !m_hFont ){
LOGFONT lf;
GetObject( GetFont(), sizeof(lf), &lf );
lf.lfUnderline = TRUE;
m_hFont = CreateFontIndirect( &lf );
}
HDC hDC = (HDC)wParam;
SelectObject( hDC, m_hFont );
SetTextColor( hDC, m_bVisited ? m_colorVisited
: m_colorUnvisited );
SetBkMode( hDC, TRANSPARENT );
hBr = (HBRUSH)GetStockObject( HOLLOW_BRUSH );
}
return (LRESULT)hBr;
}
private:
COLORREF m_colorUnvisited;
COLORREF m_colorVisited;
BOOL m_bVisited;
HFONT m_hFont;
CComBSTR m_bstrLink;
}; // CStaticLink
class CReflectDlg : public CDialogImpl<CReflectDlg> {
public:
enum { IDD = IDD_DIALOG1 };
BEGIN_MSG_MAP(CReflectDlg)
COMMAND_RANGE_HANDLER( IDOK, IDCANCEL, OnClose )
MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog)
REFLECT_NOTIFICATIONS() // reflect messages back to static links
END_MSG_MAP()
LRESULT OnInitDialog(UINT, WPARAM, LPARAM, BOOL&)
{
CenterWindow( GetParent() );
// a textual static control:
s1.SubclassWindow( GetDlgItem(IDS_TEST1) );
// a static control displaying an icon
s2.SubclassWindow( GetDlgItem(IDS_TEST2) );
// set the icon''s link
s2.SetLinkText( _T("http://www.microsoft.com") );
return 1;
}
LRESULT OnClose(UINT, WPARAM wID, HWND, BOOL& )
{
::EndDialog( m_hWnd, wID );
return 0;
}
private:
CStaticLink s1, s2;
}; // CReflectDlg
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE, LPSTR, int nCmdShow)
{
_Module.Init( NULL, hInstance );
CReflectDlg dlg;
dlg.DoModal();
_Module.Term();
return 0;
}
ATL
中的对话框类:
现在我们对 ATL 中的窗口类有了一定的了解,接着我们来学习对话框类。在你的项目中,可能有很多对话框资源,从最简单的 “ 关于 ” 模式对话框到复杂的满是控件的非模式对话框。 ATL 提供了 CSimpleDialog 类和 CDialogImpl 类来简化我们使用对话框资源的过程。
CSimpleDialog
CSimpleDialog 是一个从模版创建模式对话框的类。它提供了一些标准按纽(如 OK 和 CANCEL )的处理过程。你可以将 CSimpleDialog 想象成是一种消息对话框( Message Box ),不同的是你可以在对话框编辑器中编辑它的外观。
要显示这样一个对话框,比如当你点击 “ 帮助 ” 菜单中的 “ 关于 ” 菜单项时显示关于对话框,你需要在主窗口类中添加如下的消息映射:
现在我们对 ATL 中的窗口类有了一定的了解,接着我们来学习对话框类。在你的项目中,可能有很多对话框资源,从最简单的 “ 关于 ” 模式对话框到复杂的满是控件的非模式对话框。 ATL 提供了 CSimpleDialog 类和 CDialogImpl 类来简化我们使用对话框资源的过程。
CSimpleDialog
CSimpleDialog 是一个从模版创建模式对话框的类。它提供了一些标准按纽(如 OK 和 CANCEL )的处理过程。你可以将 CSimpleDialog 想象成是一种消息对话框( Message Box ),不同的是你可以在对话框编辑器中编辑它的外观。
要显示这样一个对话框,比如当你点击 “ 帮助 ” 菜单中的 “ 关于 ” 菜单项时显示关于对话框,你需要在主窗口类中添加如下的消息映射:
BEGIN_MSG_MAP( CMyMainWindow )
COMMAND_ID_HANDLER( ID_HELP_ABOUT, OnHelpAbout )
...
LRESULT OnHelpAbout( WORD, WORD, HWND, BOOL& )
{
CSimpleDialog<IDD_DIALOG1> dlg;
int ret = dlg.DoModal();
return 0;
}
我们可以看到对话框资源的
ID
(
IDD_DIALOG1
)被作为一个模版参数传递给
CSimpleDialog
类,
DoModal
方法显示对话框。当用户点击
OK
按钮时,
CSimpleDialog
类关闭对话框并返回按钮的
ID
。(
CSimpleDialog
类实现了对按钮
IDOK
,
IDCANCEL
,
IDABORT
,
IDRETRY
,
IDIGNORE
,
IDYES
和
IDNO
的响应。)
CDialogImpl
CSimpleDialog 类只能够处理简单的模式对话框,对于更加复杂的对话框或者模式对话框,就要用到 CDialogImpl 类。(其实 CSimpleDialog 是 CDialogImpl 中的一种特例。)
如果我们需要实现一个非模式对话框,我们必须从 CDialogImpl 派生出一个新类,并将新类的类名作为模板参数传递给 CDialogImpl 类,就象前面的 CWindowImpl 一样:
CDialogImpl
CSimpleDialog 类只能够处理简单的模式对话框,对于更加复杂的对话框或者模式对话框,就要用到 CDialogImpl 类。(其实 CSimpleDialog 是 CDialogImpl 中的一种特例。)
如果我们需要实现一个非模式对话框,我们必须从 CDialogImpl 派生出一个新类,并将新类的类名作为模板参数传递给 CDialogImpl 类,就象前面的 CWindowImpl 一样:
class CMyModelessDialog: public CDialogImpl
{
和
CSimpleDialog
不同,我们不需要将对话框资源的
ID
作为模板参数传递给它,但是我们必须将这个类和对话框资源联系起来,我们通过在类中定义一个枚举变量实现:
public:
enum { IDD = IDD_DIALOG1 };
然后定义消息映射表:
BEGIN_MSG_MAP( CMyDialog )
MESSAGE_HANDLER( WM_INITDIALOG, OnInitDialog )
MESSAGE_HANDLER( WM_CLOSE, OnClose )
...
END_MSG_MAP()
响应函数的定义和前面的一样,但是有一点需要注意,如果你实现的是一个非模式对话框,那么在
WM_CLOSE
消息的响应函数中必须调用
DestroyWindow
:
LRESULT OnClose( UINT, WPARAM, LPARAM, BOOL& )
{
DestroyWindow();
return 0;
}
...
}; // CMyModelessDialog
要在屏幕上创建这样一个对话框,需要创建这个类的一个实例并调用
Create
方法:
CMyModelessDialog dlg;
dlg.Create( wndParent );
如果对话框资源没有选中
WS_VISIBLE
属性,我们需要这样让对话框显示出来:
dlg.ShowWindow( SW_SHOW );
下面的例子有一个非模式的对话框可以接受用户输入的字符串,然后在主窗口中显示这个字符串。对话框中有一个编辑框控件和一个按钮;当按钮被点击时,对话框调用它所属窗口的
DoSomething
方法对编辑框中的字符串进行处理,它所属的窗口是一个超类化的列表框控件,
DoSomething
方法的功能是将字符串添加到列表框中。
#include "atlbase.h"
CComModule _Module;
#include "atlwin.h"
#include "resource.h"
class CMyWindow: public CWindowImpl<CMyWindow>
{
public:
DECLARE_WND_SUPERCLASS( "MyWindow", "listbox" )
BEGIN_MSG_MAP( CMyWindow )
MESSAGE_HANDLER( WM_DESTROY, OnDestroy )
END_MSG_MAP()
LRESULT OnDestroy( UINT, WPARAM, LPARAM, BOOL& )
{
PostQuitMessage( 0 );
return 0;
}
void DoSomething( LPCTSTR s )
{
SendMessage( LB_ADDSTRING, 0, reinterpret_cast<LPARAM>(s) );
}
};
class CMyDialog: public CDialogImpl<CMyDialog>
{
public:
enum { IDD = IDD_DIALOG1 };
BEGIN_MSG_MAP( CMyDialog )
COMMAND_ID_HANDLER( IDC_BUTTON1, OnButton )
MESSAGE_HANDLER( WM_INITDIALOG, OnInitDialog )
MESSAGE_HANDLER( WM_CLOSE, OnClose )
END_MSG_MAP()
LRESULT OnButton(WORD, WORD, HWND, BOOL&)
{
char buf[100];
m_ed.GetWindowText( buf, 100 );
m_owner.DoSomething( buf );
return 0;
}
LRESULT OnInitDialog( UINT, WPARAM, LPARAM, BOOL& )
{
m_owner.Attach( GetParent() );
CenterWindow( m_owner );
m_ed = GetDlgItem( IDC_EDIT1 );
return TRUE;
}
LRESULT OnClose( UINT, WPARAM, LPARAM, BOOL& )
{
DestroyWindow();
m_owner.Detach();
return 0;
}
CMyWindow m_owner;
CWindow m_ed;
};
CMyDialog dlg;
int APIENTRY WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
_Module.Init( NULL, hInstance );
CMyWindow win;
win.Create( NULL, CWindow::rcDefault, _T("modeless dialog test"),
WS_OVERLAPPEDWINDOW|WS_VISIBLE );
dlg.Create( win );
dlg.ShowWindow( SW_SHOW );
MSG msg;
while( GetMessage( &msg, NULL, 0, 0 ) ){
if( !IsWindow(dlg) || !dlg.IsDialogMessage( &msg ) ){
DispatchMessage( &msg );
}
}
_Module.Term();
return 0;
}
指定窗口类的信息:
这篇文章的大部分内容都是在讲述怎样处理窗口类的行为 —— 窗口怎样响应消息。在行为之外,一个窗口类还具有一些其它的重要的属性,比如样式、类名、背景颜色和指针等等。这一节介绍怎样使用 ATL 中提供的宏来指定这些属性。
使用 Window Traits 指定窗口的样式
到目前为止,所有例子中的窗口样式都是在调用 Create 方法时指定的:
这篇文章的大部分内容都是在讲述怎样处理窗口类的行为 —— 窗口怎样响应消息。在行为之外,一个窗口类还具有一些其它的重要的属性,比如样式、类名、背景颜色和指针等等。这一节介绍怎样使用 ATL 中提供的宏来指定这些属性。
使用 Window Traits 指定窗口的样式
到目前为止,所有例子中的窗口样式都是在调用 Create 方法时指定的:
CMyWindow wnd;
wnd.Create( NULL, CWindow::rcDefault, _T("Hello"),
WS_OVERLAPPEDWINDOW|WS_VISIBLE );
如果你不指定任何样式和扩展样式,
ATL
将使用默认的样式;这些默认的样式是作为窗口的特征定义的,默认特征是
CControlWinTraits
,定义如下:
typedef CWinTraits<WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN
|WS_CLIPSIBLINGS, 0> CControlWinTraits;
CWinTraits
是一个模板类,它需要
2
个参数:窗口样式、扩展窗口样式。
在 CWindowImpl 的定义中, CControlWinTraits 作为默认的模板参数传递给 CWindowImpl :
在 CWindowImpl 的定义中, CControlWinTraits 作为默认的模板参数传递给 CWindowImpl :
template <class T,
class TBase = CWindow,
class TWinTraits = CControlWinTraits>
class CWindowImpl : public ...
所以在默认情况下,从
CWindowImpl
派生的窗口都具有可视、子窗口、裁剪兄弟窗口、裁减子窗口的属性。
我们也能定义自己的窗口特征:
我们也能定义自己的窗口特征:
typedef CWinTraits<WS_OVERLAPPEDWINDOW|WS_VISIBLE,0>
MyTraits;
然后,从
CWindowImpl
派生一个窗口类,指定自己的窗口特征:
class CMyWindow: public CWindowImpl<CMyWindow,CWindow,MyTraits>
{...};
或者象下面这样更加直接:
class CMyWindow: public CWindowImpl<
CMyWindow,
CWindow,
CWinTraits<WS_OVERLAPPEDWINDOW|WS_VISIBLE,0>
>
{...};
注意,我们必须提供全部的三个模板参数:派生类,基类(
CWindow
)和特征类。
CMyWindow 窗口现在具有的默认的样式为 “ 可见的弹出窗口 ” ,所以我们可以在 Create 方法中省略样式参数:
CMyWindow 窗口现在具有的默认的样式为 “ 可见的弹出窗口 ” ,所以我们可以在 Create 方法中省略样式参数:
CMyWindow wnd;
wnd.Create( NULL, CWindow::rcDefault, _T("Hello") );
// style: WS_OVERLAPPEDWINDOW|WS_VISIBLE
我们也可以重写窗口特征:
ovwnd.Create( NULL, CWindow::rcDefault, _T("Hello"),
WS_OVERLAPPEDWINDOW ); // not visible
窗口特征也可以包含扩展样式:
class CClientWindow: public CWindowImpl<CClientWindow, CWindow,
CWinTraits< WS_OVERLAPPEDWINDOW|WS_VISIBLE, WS_EX_CLIENTEDGE > >
{...};
DECLARE_WND_CLASS
使用 DECLARE_WND_CLASS 宏可以指定窗口的类名:
使用 DECLARE_WND_CLASS 宏可以指定窗口的类名:
DECLARE_WND_CLASS("my window class");
这等价于:
DECLARE_WND_CLASS_EX(
"my window class",
CS_HREDRAW|CS_VREDRAW|CS_DBLCLKS, // default style
COLOR_WINDOW // default color
);
DECLARE_WND_CLASS_EX
使用 DECLARE_WND_CLASS_EX 宏可以指定窗口类名、样式和背景颜色:
使用 DECLARE_WND_CLASS_EX 宏可以指定窗口类名、样式和背景颜色:
class CMyWindow: public CWindowImpl<CMyWindow>
{
public:
DECLARE_WND_CLASS_EX(
"my window class", // class name
CS_HREDRAW|CS_VREDRAW, // class style
COLOR_WINDOW // background color
);
BEGIN_MSG_MAP(CMyWindow)
...
所谓的窗口类名是指注册的窗口类的名字,如果我们不指定窗口类名,
ATL
将自动生成一个,但是当我们使用
Spy++
之类的工具的时候,你将会发现我们自己取的类名比
"ATL:00424bd0"
之类的名字要有用得多。
类样式是按照按位或组合的。
背景颜色必须是标准系统颜色之一。
CWndClassInfo
我们也可以定义超出 DECLARE_WND_ 宏能力之外的窗口类。如果你看看 DECLARE_WND_CLASS 的定义你就会发现它定义了一个 CWndClassInfo 结构,并且一个函数返回这种结构类型的值:
类样式是按照按位或组合的。
背景颜色必须是标准系统颜色之一。
CWndClassInfo
我们也可以定义超出 DECLARE_WND_ 宏能力之外的窗口类。如果你看看 DECLARE_WND_CLASS 的定义你就会发现它定义了一个 CWndClassInfo 结构,并且一个函数返回这种结构类型的值:
#define DECLARE_WND_CLASS(WndClassName) /
static CWndClassInfo& GetWndClassInfo() /
{ /
static CWndClassInfo wc = /
{ /
{ sizeof(WNDCLASSEX), CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS, /
StartWindowProc, /
0, 0, NULL, NULL, NULL, (HBRUSH)(COLOR_WINDOW + 1), NULL, /
WndClassName, NULL }, /
NULL, NULL, IDC_ARROW, TRUE, 0, _T("") /
}; /
return wc; /
}
CWndClassInfo
结构提供了更灵活的自定义的可能,它是这样定义的:
struct CWndClassInfo
{
struct WNDCLASSEX
{
UINT cbSize;
UINT style;
WNDPROC lpfnWndProc;
int cbClsExtra;
int cbWndExtra;
HINSTANCE hInstance;
HICON hIcon;
HCURSOR hCursor;
HBRUSH hbrBackground;
LPCSTR lpszMenuName;
LPCSTR lpszClassName;
HICON hIconSm;
} m_wc;
LPCSTR m_lpszOrigName;
WNDPROC pWndProc;
LPCSTR m_lpszCursorID;
BOOL m_bSystemCursor;
ATOM m_atom;
CHAR m_szAutoName[13];
ATOM Register(WNDPROC* p);
};
例如,要指定一个窗口的指针,我们可以将
m_lpszCursorID
设置为指针的名字,如果它是一个系统指针,将
m_bSystemCursor
设置为
TRUE
,否则设置为
FALSE
。注意
DECLARE_WND_CLASS
宏是怎样将这两个成员变量分别设置为
IDC_ARROW
和
TRUE
的。既然
DECLARE_WND_
宏不能让我们改写这些默认的值,我们可以这样做:
class CMyWindow: public CWindowImpl<CMyWindow>
{
public:
static CWndClassInfo& GetWndClassInfo()
{
// a manual DECLARE_WND_CLASS macro expansion
// modified to specify an application-defined cursor:
static CWndClassInfo wc =
{
{ sizeof(WNDCLASSEX), CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS,
StartWindowProc,
0, 0, NULL, NULL, NULL, (HBRUSH)(COLOR_WINDOW + 1), NULL,
"MyWindow", NULL },
NULL, NULL, MAKEINTRESOURCE(IDC_CURSOR1), FALSE, 0, _T("")
};
return wc;
}
...
结论:
ATL 提供了一种简单的、雅致的并且功能强大的窗口编程模式。在那些方便的封装好了的函数、消息映射和宏之外,还有一些技术诸如链接、窗口的子类化和超类化、被包含的窗口和消息反射等也使得设计和实现窗口和对话框非常灵活。或许 ATL 给人最深的印象就是:功能强大、灵活性好,但是不会占用太多的内存和系统开销。
ATL 提供了一种简单的、雅致的并且功能强大的窗口编程模式。在那些方便的封装好了的函数、消息映射和宏之外,还有一些技术诸如链接、窗口的子类化和超类化、被包含的窗口和消息反射等也使得设计和实现窗口和对话框非常灵活。或许 ATL 给人最深的印象就是:功能强大、灵活性好,但是不会占用太多的内存和系统开销。