ATL对WINDOWS窗口的封装

ATL包装了WINAPI中与创建和操作窗口、对话框以及WINDOWS控制有关的部分。ATL窗口类还包含了诸如子类化和超类化这样的高级特性。

一、Windows应用程序的结构

入口点——_tWinMain,它提供应用程序的HINSTANCE、命令行参数和指示如何显示主窗口的标志。

调用RegisterClass注册主窗口类。

调用CreateWindow创建主窗口。

调用ShowWindowUpdateWindow来显示主窗口。

一个分发消息的消息循环。

一个处理主窗口消息的过程。

一组消息处理函数,用来处理窗口感兴趣的消息。

调用DefWindowProcWindows处理我们不感兴趣的消息。

一旦主窗口被销毁,调用PostQuitMessage

ATL 把以上的过程调用封装成一个对象。它包含窗口类(用WNDCLASSEX结构来表示)、窗口对象(用HWND来表示)和成员函数调用(通过对 WndProc的调用表示)。ATL提供了一组窗口类,CWindowCWindowImplCWinTraitsCWinTraitsOR CDialogImplCSimpleDialogCContainedWindowT。以及CWindowImplRoot CWindowImplBaseTCDialogImplBaseT等辅助类。

 

二、CWindow

1、封装HWND及相关的一些API函数

1Cwindow类包含了公共成员HWND hWnd

2CWindow类封装了User32 API函数。
2
CWindow类的使用

//Needed to use ATL windowing classes

#include <atlbase.h>

extern CComModule _Module;

#include <atlwin.h>

ATL窗口类定义在atlwin.h中,依赖于atlbase.h

_Module实例保存了应用的HINSTANCE实例以及ATL模块的初始化和终结。

_Module.Init(0,hinst);    //初始化ATL模块

 

CWindow win; //对象构造

win.Create( “button”, NULL, CWindow::rcDefault, “Click me”,WS_CHILD ); //窗口创建

 

_Module.Term();    //终结ATL模块

 

CWindows在窗口创建时就决定了窗口收到消息后的处理方法,无法改变。从CWindows类派生出来的CWindowsImpl类提供指定窗口的新行为。通过给窗口类添加消息映射达到了添加新行为的方法。扩展或改变一个窗口实例的行为的方法:

1)写一个新的ATL窗口类,并子类化这个已经存在的实例。

2)使这个实例成为被包含的窗口,所有到这个实例的消息都会通过容器窗口。

3)采用消息反映射,当一个窗口收到消息后不做处理,而反射给发送这个消息的窗口自己处理,这种技术可用于创建自包含的控件。

 

三、CWindowImpl

CWindowImpl类从CWindow派生出来的,并且提供了窗口类注册和消息处理两个特性。

1、窗口类注册

Create成员函数里注册了窗口类。

1)窗口信息

CWndClassInfo结构:

struct _ATL_WNDCLASSINFOA
{
WNDCLASSEXA m_wc;
LPCSTR m_lpszOrigName;
WNDPROC pWndProc;
LPCSTR m_lpszCursorID;
BOOL m_bSystemCursor;
ATOM m_atom;
CHAR m_szAutoName[32];
ATOM Register(WNDPROC* p)
{

return AtlModuleRegisterWndClassInfoA

(&_Module, this, p);

}

};

m_wc表示窗口类的结构,在手工注册类时,用它来进行注册。

m_atom用于确定类是否被注册。

DECLARE_WND_CLASS宏定义函数GetWndClassInfo()获得一个CWndClassInfo实例。

DECLARE_WND_CLASS_EX宏提供了但对DECLARE_WND_CLASS宏的扩展。

这两个宏对窗口类的信息都提供了默认值,要修改其他的值,可以用函数GetWndClassInfo函数修改CWndClassInfo结构。

2)窗口特性(Window Traits

CWinTraits类保存了一个风格(style)和扩展风格(extended style

3)窗口过程(Window Procedure

每个窗口都有一个窗口过程处理窗口消息。这个窗口过程是在窗口注册期间设置 WNDCLASSEX结构的lpfnWndProc成员。DECLARE_WND_CLASSDECLARE_WND_CLASS_EX宏中用 StartWindowProc函数设置lpnWndProc成员。StartWindowProc函数完成了HWND和对象的this指针之间的映射。

CreateWindow[Ex]时之前通过_Module.AddCreateWndData(&m_thunk.cd, this);将对象的this指针存在_Module维护的列表中。在调用窗口过程的时候在StartWindowProc中调用 _Module.ExtractCreateWndData()获得this指针。

ATL采用一组动态创建的汇编指令thunk实现了对this指针的存储和查找。每个CWindowImpl对象有自己的thunkthunk就是窗口的过程。thunk保存在_WndProcThunk结果里面。

struct _WndProcThunk
{
DWORD   m_mov;          // mov dword ptr

[esp+0x4], pThis (esp+0×4 is hWnd)
DWORD   m_this;         //
BYTE    m_jmp;          // jmp WndProc
DWORD   m_relproc;      // relative jmp
};

StartWindowProc函数里,通过pThis->m_thunk.Init(pThis->GetWindowProc(), pThis);将窗口的this指针和窗口消息处理函数WindowProc初始化到thunk静态结构里。

Windows消息路由路径:

Windows — WM_XXX –> CWindowImpl派生类的对象的thunk — 函数调用 –> CWindowImpl的静态成员函数WindowProc — 成员函数调用 –> CWindowImpl派生类的ProcessWindowMessage

4windows超类化

声明一个窗口类并且创建这个类的一个实例。超类化是复制已有窗口类的WNDCLASSEX结构并且赋予它自己的名字和自己的WndProc”。窗口收到一条消息后,消息被路由到新的WndProc,如果新的WndProc不处理该消息,消息将路由到原来的WndProc

2、消息处理

ATL的消息处理通过CWindowImpl派生类提供的ProcessWindowMessage函数分发处理各个类型的消息。ATL定义了一些宏便于组合处理消息链。

#define BEGIN_MSG_MAP(theClass) \
public: \
BOOL ProcessWindowMessage(HWND hWnd, UINT uMsg,

WPARAM wParam, LPARAM lParam, LRESULT& lResult, DWORD

dwMsgMapID = 0) \
{ \
BOOL bHandled = TRUE; \
hWnd; \
uMsg; \
wParam; \
lParam; \
lResult; \
bHandled; \
switch(dwMsgMapID) \
{ \
case 0:

#define END_MSG_MAP() \
break; \
default: \
ATLTRACE2(atlTraceWindowing, 0,

_T(“Invalid message map ID (%i)\n”), dwMsgMapID); \
ATLASSERT(FALSE); \
break; \
} \
return FALSE; \
}

通过BEGIN_MSG_MAPEND_MSG_MAP定义了消息链的框架。BOOL bHandled实现了一个是否这条消息还需要后续处理的标记。作为消息处理函数的参数,如果需要后续处理将该值设为false

 

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)和两个可选的消息映射表(标识为100101)。

如果我们在多个类中都要响应WM_CREATE消息,但是不同的类需要基类提供不同的处理,怎么办呢?为了解决这个问题,ATL使用了可选的消息映射:将消息映射表分成很多节,每一节用不同的数字标识,每一节都是一个可选的消息映射表。

当你链接消息映射表时,采用以下的方案的标识,如下:

class CDerived: public CBase {

BEGIN_MSG_MAP( CDerived )

CHAIN_MSG_MAP_ALT( CBase, 100 )

END_MSG_MAP()

}

CDerived的消息传入CBase后会被100的消息表内的函数处理。

CHAIN_MSG_MAP( CBase ) 可以把没被处理的消息从被派生的类传入基类CBase

CHAIN_MSG_MAP_ALT( CBase, 100 ) 可以把没被处理的消息从被派生的类传入基类CBase,由基类内消息标识为100的处理函数处理。

四、CDialogImpl

1、 模式

对话框有两种模式:有模式和无模式。
有模式:当对话框可见时,父窗口不可访问。
无模式:父窗口在对话框可见期间仍然可以被访问。
2
、交换数据

有模式对话框的交换数据的操作:

1)应用程序创建一个CDialogImpl派生类的实例。
2)应用程序向对话框对象的数据成员复制一些数据。
3)应用程序调用DoModal
4)对话框处理WM_INITDIALOG,把数据成员复制到子控制中。
5)对话框处理OK按钮时会确认子控制保存的数据的有效性。如果数据无效,那么对话框向用户报错并且让用户继续尝试,直至使之正确或者用户点击Cancel取消。
6)如果数据是有效的,那么数据被复制回到对话框的数据成员,并且终止对话框。
7)如果应用程序从DoModal得到了IDOK,应用把对话框的数据成员复制到自己的拷贝中。

无模式对话框的交互数据操作:

1)应用程序创建一个CDialogImpl派生类的实例。
2)应用程序向对话框对象的数据成员复制一些数据。
3)应用程序调用Create
4)对话框处理WM_INITDIALOG,把数据成员复制到子控制中。
5)对话框处理Apply按钮时会确认子控制保存的数据的有效性。如果数据是无效,那么对话框向用户报错并且让用户继续尝试,直至使之正确或者用户点击Cancel取消。
6)如果数据是有效的,那么数据被复制回到对话框的数据成员,并通知应用从对话框读取更新后的数据。
7)应用程序接到通知时,应用把对话框的数据成员复制到自己的拷贝中。

五、CContainedWindow

CContainedWindow 的对象让它的父窗口处理消息,让一个已有的窗口类处理父窗口传递的消息。父窗口可以创建一个子窗口类的实例,然后把它子类化,子窗口的消息会通过父窗口的 消息映射表路由。父窗口通过替代消息映射表辨别来自子窗口和自己的消息,每个CContainedWindow分配一个消息映射ID,并且它的消息被路由 到父窗口消息映射表的相应替代部分。

1)创建被包含的窗口
调用CContainedWindowCreate成员创建被包含的窗口。

2)子类化被包含的窗口
子类化不创建新的窗口,它仅仅是截取单个窗口的消息。我们创建某个类的一个窗口,并且使用SetWindowLong(GWL_INITDIALOG)把原来的窗口过程替换成我们自己的窗口过程。

posted @ 2011-01-27 01:40  alex618  阅读(3782)  评论(0编辑  收藏  举报