MFC消息映射机制实现
2016-07-23 16:49
351 查看
Windows程序的本质是依靠消息来维持运行的。每一个消息都有一个代码,并以WM_开头的常量表示。
MFC把消息分为三大类:
1.命令消息(WM_COMMAND):命令消息意味着“使用者命令程序做某些操作”。凡由UI对象产生的消息都是这种命令消息,可能来自菜单或加速键或工具栏按钮,并且都以WM_COMMAND呈现。
什么样的类有资格接受命令消息?凡派生自CCmdTarget的类皆有资格。从command target字面意义也可以看出来,这是命令消息的目的地。也就是说,凡派生自CCmdTarget者,它的骨子里就有了一种特殊的机制。
2.Control Notification:这种消息由控件产生,为的是向其父窗口(通常是对话框)通知某种情况。这种消息也是以WM_COMMAND形式呈现。
Windows9x后的新控件传送的是WM_NOTIFY,而旧有的控件为了兼容还是传送WM_COMMAND消息。
3.标准消息:除了前面说到的两类,任何以WM_开头的都可归于这一类。任何派生自CWnd的类均可接收此类消息。
当消息处理时,消息会循着Application Framework规定的路线,游走于各个对象之间,直到找到它的归宿(消息处理函数)。找不到的话,Framework最终就把它交给::DefWindowProc函数去处理。
“消息映射”是MFC內建的一个消息分派机制,只要利用数个宏以及固定形式的写法,就可以让Framework知道,一旦消息发生,该循哪一条路递送。每一个类只能拥有一个消息映射表,但也可以没有。
消息映射实现的标准操作:
在类声明中:
{
……
DECLARE_MESSAGE_MAP()
}
在类实现文件中:
在BEGIN_和END_之中的宏,除了ON_COMMAND,还可以有许多种。标准的Windows消息并不需要我们指定处理函数的名称。因为标准函数的处理函数名也是“标准的”。
例如:
宏名-> ON_WM_CHAR,对应消息-> WM_CHAR,处理函数-> OnChar
即有,宏名的规则是在对应消息的前面加上ON_,处理函数则是在宏名的基础上去掉中间的_WM_并将单词中除了首字母外全变为小写即可。
上面用到的几个宏都定义于AFXWIN.H中,下面我们就来鉴赏一下吧。
DECLARE_MESSAGE_MAP
其中static修饰词限定了数据的配置,使得每个“类”仅有一份数据,而不是每个“对象”各有一份数据。
宏中还包含了两个结构体,其定义如下:
很明显,它的作用主要是将nMessage对应的消息与pfn对应的函数关联起来。其中AFX_PMSG的定义如下:
对于AFX_MSG_CALL:
即该宏也只是个占位符,没有具体内容。
另一个结构体:
暂不介绍DLL相关内容,所以第一个就不管了。pBaseMap是指向“基类消息映射表”的指针,它提供了一个走访整个继承链表的方法,有效地实现消息映射的继承性。
通过DECLARE_MESSAGE_MAP宏,我们相当于建立了如下的数据结构:
BEGIN.../ON.../END...宏
在AFXWIN.H中有:
其中PASCAL表示stdcall的函数调用方式,AFX_COMDAT和AFX_DATADEF在此处只是一个占位符,没有其它意义。
其中,AfxSig_end被定义为0;
在AFXMSG.H中又有:
倘若我们有如下的宏:
展开后将得到如下的代码:
然后,我们就得到了如下的执行结果:
所有能够接收消息的类都应该派生自CCmdTarget,但是不一定派生自CCmdTarget类就能接收消息。其中CWinThread就是个特例。那么继承自CWinThread的CWinApp又该如何解释?看下面就能明白了:
是的,CWinApp是直接将messageMap中的pBaseMap指针指向了CCmdTarget。
如果我们“按照规矩”创建消息网,那么最后应该得到下图这样的情景:
如果BEGIN_MESSAGE_MAP宏中的两个参数没有按照规矩来写,消息可能会在不该流向某个类的时候流了过去,在应该被处理的时候却又跳离了。总之,情况会变得超出我们的控制。
综上所述,Message Map即可以说是一套宏,也可以说是宏展开后所代表的一套数据结构;甚至也可以说Message Map是一种操作,这个操作就是在我们所搭建的数据结构中查找与消息相吻合的项目,从而获得消息处理的函数指针。
MFC把消息分为三大类:
1.命令消息(WM_COMMAND):命令消息意味着“使用者命令程序做某些操作”。凡由UI对象产生的消息都是这种命令消息,可能来自菜单或加速键或工具栏按钮,并且都以WM_COMMAND呈现。
什么样的类有资格接受命令消息?凡派生自CCmdTarget的类皆有资格。从command target字面意义也可以看出来,这是命令消息的目的地。也就是说,凡派生自CCmdTarget者,它的骨子里就有了一种特殊的机制。
2.Control Notification:这种消息由控件产生,为的是向其父窗口(通常是对话框)通知某种情况。这种消息也是以WM_COMMAND形式呈现。
Windows9x后的新控件传送的是WM_NOTIFY,而旧有的控件为了兼容还是传送WM_COMMAND消息。
3.标准消息:除了前面说到的两类,任何以WM_开头的都可归于这一类。任何派生自CWnd的类均可接收此类消息。
当消息处理时,消息会循着Application Framework规定的路线,游走于各个对象之间,直到找到它的归宿(消息处理函数)。找不到的话,Framework最终就把它交给::DefWindowProc函数去处理。
“消息映射”是MFC內建的一个消息分派机制,只要利用数个宏以及固定形式的写法,就可以让Framework知道,一旦消息发生,该循哪一条路递送。每一个类只能拥有一个消息映射表,但也可以没有。
消息映射实现的标准操作:
在类声明中:
{
……
DECLARE_MESSAGE_MAP()
}
在类实现文件中:
BEGIN_MESSAGE_MAP(CMyWinApp, CWinApp) //{{AFX_MSG_MAP(CMFCApp) ON_COMMAND(ID_APP_ABOUT, OnAppAbout) //}}AFX_MSG_MAP END_MESSAGE_MAP()
在BEGIN_和END_之中的宏,除了ON_COMMAND,还可以有许多种。标准的Windows消息并不需要我们指定处理函数的名称。因为标准函数的处理函数名也是“标准的”。
例如:
宏名-> ON_WM_CHAR,对应消息-> WM_CHAR,处理函数-> OnChar
即有,宏名的规则是在对应消息的前面加上ON_,处理函数则是在宏名的基础上去掉中间的_WM_并将单词中除了首字母外全变为小写即可。
上面用到的几个宏都定义于AFXWIN.H中,下面我们就来鉴赏一下吧。
DECLARE_MESSAGE_MAP
#define DECLARE_MESSAGE_MAP() \ private: \ static const AFX_MSGMAP_ENTRY _messageEntries[]; \ protected: \ static AFX_DATA const AFX_MSGMAP messageMap; \ virtual const AFX_MSGMAP* GetMessageMap() const; \
其中static修饰词限定了数据的配置,使得每个“类”仅有一份数据,而不是每个“对象”各有一份数据。
宏中还包含了两个结构体,其定义如下:
struct AFX_MSGMAP_ENTRY { UINT nMessage; // windows message UINT nCode; // control code or WM_NOTIFY code UINT nID; // control ID (or 0 for windows messages) UINT nLastID; // used for entries specifying a range of control id's UINT nSig; // signature type (action) or pointer to message # AFX_PMSG pfn; // routine to call (or special value) };
很明显,它的作用主要是将nMessage对应的消息与pfn对应的函数关联起来。其中AFX_PMSG的定义如下:
typedef void (AFX_MSG_CALL CWinThread::*AFX_PMSGT)(void);
对于AFX_MSG_CALL:
#ifndef AFX_MSG_CALL #define AFX_MSG_CALL #endif
即该宏也只是个占位符,没有具体内容。
另一个结构体:
struct AFX_MSGMAP { #ifdef _AFXDLL const AFX_MSGMAP* (PASCAL* pfnGetBaseMap)();//DLL工程中使用 #else const AFX_MSGMAP* pBaseMap; #endif const AFX_MSGMAP_ENTRY* lpEntries; };
暂不介绍DLL相关内容,所以第一个就不管了。pBaseMap是指向“基类消息映射表”的指针,它提供了一个走访整个继承链表的方法,有效地实现消息映射的继承性。
通过DECLARE_MESSAGE_MAP宏,我们相当于建立了如下的数据结构:
BEGIN.../ON.../END...宏
在AFXWIN.H中有:
#define BEGIN_MESSAGE_MAP(theClass, baseClass) \ const AFX_MSGMAP* PASCAL theClass::_GetBaseMessageMap() \ { return &baseClass::messageMap; } \ const AFX_MSGMAP* theClass::GetMessageMap() const \ { return &theClass::messageMap; } \ AFX_COMDAT AFX_DATADEF const AFX_MSGMAP theClass::messageMap = \ { &theClass::_GetBaseMessageMap, &theClass::_messageEntries[0] }; \ AFX_COMDAT const AFX_MSGMAP_ENTRY theClass::_messageEntries[] = \ { \
其中PASCAL表示stdcall的函数调用方式,AFX_COMDAT和AFX_DATADEF在此处只是一个占位符,没有其它意义。
#define END_MESSAGE_MAP() \ {0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 } \ }; \
其中,AfxSig_end被定义为0;
在AFXMSG.H中又有:
#define ON_COMMAND(id, memberFxn) \ { WM_COMMAND, CN_COMMAND, (WORD)id, (WORD)id, AfxSig_vv, (AFX_PMSG)&memberFxn }, #define ON_WM_CREATE() \ { WM_CREATE, 0, 0, 0, AfxSig_is, \ (AFX_PMSG)(AFX_PMSGW)(int (AFX_MSG_CALL CWnd::*)(LPCREATESTRUCT))&OnCreate }, #define ON_WM_DESTROY() \ { WM_DESTROY, 0, 0, 0, AfxSig_vv, \ (AFX_PMSG)(AFX_PMSGW)(void (AFX_MSG_CALL CWnd::*)(void))&OnDestroy }, #define ON_WM_PAINT() \ { WM_PAINT, 0, 0, 0, AfxSig_vv, \ (AFX_PMSG)(AFX_PMSGW)(void (AFX_MSG_CALL CWnd::*)(void))&OnPaint }, #define ON_WM_CLOSE() \ { WM_CLOSE, 0, 0, 0, AfxSig_vv, \ (AFX_PMSG)(AFX_PMSGW)(void (AFX_MSG_CALL CWnd::*)(void))&OnClose }, ……
倘若我们有如下的宏:
BEGIN_MESSAGE_MAP(CMyView, CView) ON_WM_CREATE() ON_WM_PAINT() END_MESSAGE_MAP()
展开后将得到如下的代码:
const AFX_MSGMAP* PASCAL CMyView::_GetBaseMessageMap() { return & CView::messageMap; } const AFX_MSGMAP* CMyView::GetMessageMap() const { return & CMyView::messageMap; } const AFX_MSGMAP CMyView::messageMap = { & CMyView::_GetBaseMessageMap, & CMyView::_messageEntries[0] }; const AFX_MSGMAP_ENTRY theClass::_messageEntries[] = { { WM_CREATE, 0, 0, 0, AfxSig_is, (AFX_PMSG)(AFX_PMSGW)(int (AFX_MSG_CALL CWnd::*)(LPCREATESTRUCT))&OnCreate }, { WM_PAINT, 0, 0, 0, AfxSig_vv, (AFX_PMSG)(AFX_PMSGW)(void (AFX_MSG_CALL CWnd::*)(void))&OnPaint }, {0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 } };
然后,我们就得到了如下的执行结果:
所有能够接收消息的类都应该派生自CCmdTarget,但是不一定派生自CCmdTarget类就能接收消息。其中CWinThread就是个特例。那么继承自CWinThread的CWinApp又该如何解释?看下面就能明白了:
//AFXWIN.H中 class CWinApp : public CWinThread { DECLARE_DYNAMIC(CWinApp) …… } //APPCORE.CPP中 BEGIN_MESSAGE_MAP(CWinApp, CCmdTarget) //{{AFX_MSG_MAP(CWinApp) // Global File commands ON_COMMAND(ID_APP_EXIT, OnAppExit) // MRU - most recently used file menu ON_UPDATE_COMMAND_UI(ID_FILE_MRU_FILE1, OnUpdateRecentFileMenu) ON_COMMAND_EX_RANGE(ID_FILE_MRU_FILE1, ID_FILE_MRU_FILE16, OnOpenRecentFile) //}}AFX_MSG_MAP END_MESSAGE_MAP()
是的,CWinApp是直接将messageMap中的pBaseMap指针指向了CCmdTarget。
如果我们“按照规矩”创建消息网,那么最后应该得到下图这样的情景:
如果BEGIN_MESSAGE_MAP宏中的两个参数没有按照规矩来写,消息可能会在不该流向某个类的时候流了过去,在应该被处理的时候却又跳离了。总之,情况会变得超出我们的控制。
综上所述,Message Map即可以说是一套宏,也可以说是宏展开后所代表的一套数据结构;甚至也可以说Message Map是一种操作,这个操作就是在我们所搭建的数据结构中查找与消息相吻合的项目,从而获得消息处理的函数指针。
相关文章推荐
- 我的书单
- 我眼中BA(业务需求分析师)的技能广度和深度
- @Transactional spring 配置事务 注意事项
- 2016夏季练习——最小生成树
- phpstrom + ideavim 详细配置
- oop 连连看游戏
- 16.7.23
- Java基础之 原码、反码、补码
- "su"命令和"su -"的不同之处
- Codeforces Round #364 (Div. 2) D. As Fast As Possible
- scp免密码传送文件
- 排名(sort结构体)
- 【POJ 3040】Allowance
- 字符串处理函数
- 使用日期选择器DatePicker(1)
- 华为 BGP 选路练习
- 地球第一强国冈比亚是个啥样的国家?
- 织梦DedeCMS安装UEditor编辑器方法
- 如何让Android Studio运行Eclipse结构的工程(保持原有结构)
- Quartz2D的简单使用概述(一)