深入剖析WTL—WTL框架窗口分析 (2)

ATL的消息处理宏


消息映射的目的是实现ProcessWindowMessage()。ProcessWindowMessage()函数是窗口函数的关键逻辑。

一共有三种消息处理宏,分别对应三类窗口消息——普通窗口消息(如WM_CREATE),命令消息(WM_COMMANS)和通知消息(WM_NOTIFY)。

消息处理宏的目的是将消息和相应的处理函数(该窗口的成员函数)联系起来。

· 普通消息处理宏

有两个这样的宏:MESSAGE_HANDLER和MESSAGE_RANGE_HANDLER。

第一个宏将一个消息和一个消息处理函数连在一起。第二个宏将一定范围内的消息和一个消息处理函数连在一起。

消息处理函数通常是下面这样的:

     
     LRESULT MessageHandler(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);


注意最后一个参数bHandled。它的作用是该处理函数是否处理该消息。如果它为FALSE,消息MAP的其它处理函数会来处理这个消息。

我们看一下MESSAGE_HANDLE的定义:

     
     #define MESSAGE_HANDLER(msg, func) /
	if(uMsg == msg) /
	{ /
		bHandled = TRUE; /
		lResult = func(uMsg, wParam, lParam, bHandled); /
		if(bHandled) /
			return TRUE; /
	}


在上面的代码中,首先判断是否是想要处理的消息。如果是的,那么调用第二个参数表示的消息处理函数来处理该消息。

注意bHandled,如果在消息处理函数中设置为TRUE,那么,在完成该消息处理后,会进入return TRUE语句,从ProcessWindowMessage()函数中返回。

如果bHandled在调用消息处理函数时,设置为FALSE,则不会从ProcessWindowMessage中返回,而是继续执行下去。

· 命令消息处理宏和通知消息处理宏

命令消息处理宏有五个——COMMAND_HANDLER,COMMAND_ID_HANDLER,COMMAND_CODE_HANDLER,COMMAND_RANGE_HANDLER和COMMAND_RANGE_CODE_HANDLER。

通知消息处理宏有五个--NOTIFY_HANDLER,NOTIFY_ID_HANDLER,NOTIFY_CODE_HANDLER,NOTIFY_RANGE_HANDLER和NOTIFY_RANGE_CODE_HANDLER

我们不再详细分析。

通过上面的分析,我们知道了ATL是怎样实现窗口函数逻辑的。那么ATL是怎样封装窗口函数的呢?为了能理解ATL的封装方法,还必须了解ATL中的窗口subclass等技术。我们将在分析完这些技术之后,再分析ATL对窗口消息处理函数的封装。

扩展窗口类的功能


我们知道Windows窗口的功能由它的窗口函数指定。通常在创建Windows应用程序时,我们要开发一个窗口函数。通过定义对某些消息的相应来实现窗口的功能。

在每个窗口处理函数的最后,我们一般用下面的语句:

     
          default:
      return DefWindowProc(hWnd, message, wParam, lParam);


它的意思是,对于没有处理的消息,我们将它传递给Windows的确省窗口函数。

Windows除了提供这个缺省的窗口函数,还为某些标准的控制提供了一些预定义的窗口函数。

我们在注册窗口类的时候,指定了该窗口类的窗口处理函数。

扩展窗口类的功能,就是要改变窗口函数中对某些消息的处理逻辑。

下面我们来看几种扩展窗口功能的技术,以及看看ATL是怎样实现的。

派生和CHAIN_MSG_MAP()

很自然,我们会在一个窗口类的基础上派生另一个。然后通过定义不同的消息处理函数。

下面是一个简单的实例(该例子摘自MSDN)。

     
     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 )
      CHAIN_MSG_MAP( CBase ) // chain to base class
   END_MSG_MAP()

   LRESULT OnButtonDown( UINT, WPARAM, LPARAM, BOOL& )
   {
      ATLTRACE( "button down/n" );
      return 0;
   }
};

在上面的例子中,CDerived从CBase中派生出来。CDerived类通过定义一个WM_LBUTTONDOWN消息处理函数来改变CBase类代表的窗口的功能。

这样,CBase类的消息映射定义了一个ProcessWindowMessage()函数,而CDerived类的消息映射也定义了一个ProcessWindowMessage()函数。

那么,我们在窗口处理函数逻辑中怎样把这两个类的ProcessWindowMessage()连起来呢?(想想为什么要连起来?)

在CDerived的消息映射中,有一个宏CHAIN_MSG_MAP()。它的作用就是把两个类对消息的处理连起来。

看一下这个宏的定义:

     
     #define CHAIN_MSG_MAP(theChainClass) /
	{ /
if(theChainClass::ProcessWindowMessage(hWnd, uMsg, wParam, lParam, lResult)) /
			return TRUE; /
	}


很简单,它仅仅调用了基类的ProcessWindowMessage()函数。

也就是说,CDerived类的ProcessWindowMessage()包含两部分,一部分是调用处理WM_LBUTTONDOWN的消息处理函数,该函数是该类的成员函数。第二部分是调用CBase类的ProcessWindowMessage()函数,该函数用于处理WM_DESTROY消息。

在后面对窗口函数的封装中,我们会知道,对于其他消息处理,CDerived会传递给缺省窗口函数。

派生和ALT_MSG_MAP()

如果我们希望在CBase类上再派生一个新的窗口类。该类除了要对WM_RBUTTONDOWN做不同的处理外,还希望CBase对WM_DESTROY消息的响应与前一个例子不同。比如希望能提示关闭窗口信息。

那怎么处理呢?ATL提供了一种机制,它由ALT_MSG_MAP()实现。它使得一个类的消息映射能处理多个Windows窗口类。

下面是具体的示例:

     
     // in class CBase:
   BEGIN_MSG_MAP( CBase )
      MESSAGE_HANDLER( WM_DESTROY, OnDestroy1 )
      ALT_MSG_MAP( 100 )
      MESSAGE_HANDLER( WM_DESTROY, OnDestroy2 )
   END_MSG_MAP()


ALT_MSG_MAP()将消息映射分成两个部分。每个部分的消息映射都有一个ID。上面的消息映射的ID分别为0和100。

分析一下ALT_MSG_MAP():

     
     #define ALT_MSG_MAP(msgMapID) /
		break; /
		case msgMapID:


很简单,它结束了前面的msgMapID的处理,开始进入另一个msgMapID的处理。

那么,在CDerived类的消息映射中,是怎样将两个类的ProcessWindowMessage()函数的逻辑连在一起的呢?

     
     // in class CDerived:
   BEGIN_MSG_MAP( CDerived )
      CHAIN_MSG_MAP_ALT( CBase, 100 )
   END_MSG_MAP()


这里使用CHAIN_MSG_MAP_ALT()宏。它的具体定义如下:

     
     #define CHAIN_MSG_MAP_ALT(theChainClass, msgMapID) /
{ /
if(theChainClass::ProcessWindowMessage(hWnd, uMsg, wParam, lParam, lResult, 
msgMapID)) /
	return TRUE; /
	}


不再分析其原理。请参考前面对CHAIN_MSG_MAP()宏的分析。

superclass

superclass是一种生成新的窗口类的方法。它的中心思想是依靠现有的窗口类,克隆出另一个窗口类。被克隆的类可以是Windows预定义的窗口类,这些预定义的窗口类有按钮或下拉框控制等等。也可以是一般的类。克隆的窗口类使用被克隆的类(基类)的窗口消息处理函数。

克隆类可以有自己的窗口消息处理函数,也可以使用基类的窗口处理函数。

需要注意的是,superclass是在注册窗口类时就改变了窗口的行为。即通过指定基类的窗口函数或是自己定义的窗口函数。这与后面讲到的subclass是不同的。后者是在窗口创建完毕后,通过修改窗口函数的地址等改变一个窗口的行为的。

请看示例(摘自MSDN):

     
     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


该类实现一个按钮,在点击它时,会有响声。

该类的消息映射处理WM_LBUTTONDOWN消息。其它的消息由Windows缺省窗口函数处理。

在消息映射前面,有一个宏--DECLARE_WND_SUPERCLASS()。它的作用就是申明BeepButton是Button的一个superclass。

分析一下这个宏:

     
     #define DECLARE_WND_SUPERCLASS(WndClassName, OrigWndClassName) /
static CWndClassInfo& GetWndClassInfo() /
{ /
	static CWndClassInfo wc = /
	{ /
		{ sizeof(WNDCLASSEX), 0, StartWindowProc, /
		  0, 0, NULL, NULL, NULL, NULL, NULL, WndClassName, NULL }, /
		OrigWndClassName, NULL, NULL, TRUE, 0, _T("") /
	}; /
	return wc; /
}


这个宏定义了一个静态函数GetWndClassInfo()。这个函数返回了一个窗口类注册时用到的数据结构CWndClassInfo。该结构的详细定义如下:

     
     struct _ATL_WNDCLASSINFOA
{
	WNDCLASSEXA m_wc;
	LPCSTR m_lpszOrigName;
	WNDPROC pWndProc;
	LPCSTR m_lpszCursorID;
	BOOL m_bSystemCursor;
	ATOM m_atom;
	CHAR m_szAutoName[13];
	ATOM Register(WNDPROC* p)
	{
		return AtlModuleRegisterWndClassInfoA(&_Module, this, p);
	}
};
struct _ATL_WNDCLASSINFOW
{
 … …
	{
		return AtlModuleRegisterWndClassInfoW(&_Module, this, p);
	}
};
typedef _ATL_WNDCLASSINFOA CWndClassInfoA;
typedef _ATL_WNDCLASSINFOW CWndClassInfoW;
#ifdef UNICODE
#define CWndClassInfo CWndClassInfoW
#else
#define CWndClassInfo CWndClassInfoA
#endif


这个结构调用了一个静态函数AtlModuleRegisterWndClassInfoA(&_Module, this, p);。这个函数的用处就是注册窗口类。

它指定了WndClassName是OrigWdClassName的superclass。

subclass

subclass是普遍采用的一种扩展窗口功能的方法。它的大致原理如下。

在一个窗口创建完了之后,将该窗口的窗口函数替换成新的窗口消息处理函数。这个新的窗口函数可以对某些需要处理的特定的消息进行处理,然后再将处理传给原来的窗口函数。

注意它与superclass的区别。

Superclass是以一个类为原版,进行克隆。既在注册新的窗口类时,使用的是基类窗口的窗口函数。

而subclass是在某一个窗口注册并创建后,通过修改该窗口的窗口消息函数的地址而实现的。它是针对窗口实例。

看一个从MSDN来的例子:

     
     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;
   }
};


这里定义了一个只接收数字的编辑控件。即通过消息映射,定义了一个特殊的消息处理逻辑。

然后,我们使用CWindowImplT. SubclassWindow()来subclass一个编辑控件。

     
     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;
};


上述代码中,ed.SubclassWindow( GetDlgItem( IDC_EDIT1 ) )语句是对IDC_EDIT1这个编辑控件进行subclass。该语句实际上是替换了编辑控件的窗口函数。

由于SubClassWindows()实现的机制和ATL封装窗口函数的机制一样,我们会在后面介绍ATL是怎么实现它的。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值