什么是MFC的消息映射机制

 

Windows 程序的本质系借着消息来维持脉动。每一个消息都有一个代码,并以WM_ 开头的常数表示之。消息在传统SDK 程序方法中的流动以及处置方式。各种消息之中,来自菜单或工具栏者,都以WM_COMMAND 表示,所以这一类消息我们又称之为命令消息(Command Message),其wParam 记录着此一消息来自哪一个菜单项目。消息会循着Application Framework 规定的路线,游走于各个对象之间,直到找到它的依

(消息处理函数)。找不到的话,Framework 最终就把它交给::DefWindowProc 函数去处理。对于一般消息(如WM_MOVE、WM_SIZE、WM_CREATE 等)也是天经地义的。但是今天Application Framework 比传统的SDK 程序多出了一个Document/View 架构,试想,如果菜单上有个命令项关乎文件的处理,那么让这个命令消息流到Document 类别去不是最理想吗?一旦流入Document 大本营,我们(程序员)就可以很方便地取得Document 成员变量、调用Document 成员函数,做爱做的事。但是Document 不是窗口,也没有对应的窗口类别,怎么让消息能够七拐八弯地流往Document 类别去?甚至更往上流向Application 类别去?这就是所谓的命令绕行机制!

而为了让消息的流动有线路可循,MFC 必须做出一个巨大的网,实现所有可能的路线,这个网就是所谓的消息映射地图(Message map)。最后,MFC 还得实现一个消息推动引擎,让消息依Framework 的意旨前进,该拐的时候拐,该弯的时候弯,这个邦浦机制埋藏在各个类别的WindowProc、OnCommand、OnCmdMsg、DefWindowProc 虚拟函数中。

没有命令绕行机制,Document/View 架构就像断了条胳臂,会少掉许多功用。  

   很快你就会看到所有的秘密。很快地,它们统统不再对你构成神秘。

  消息分类Windows 的消息都是以WM_xxx 为名,WM_ 的意思是"Windows Message"。消息可以是来自硬件的「输入消息」,例如WM_LBUTTONDOWN,也可以是来自USER 模块的「窗口管理消息」,例如WM_CREATE。这些消息在MFC 程序中都是隐晦的(我的意思是不像在SDK 程序中那般显明),我们不必在MFC 程序中撰写switch case 指令,不必一一识别并处理由系统送过来的消息;所有消息都将依循Framework 制定的路线,并参照路中是否有拦路虎(你的消息映射表格)而流动。WM_PAINT 一定流往你的OnPaint 函数去,WM_SIZE 一定流往你的OnSize 函数去。

  所有的消息在MFC 程序中都是暗潮汹涌,但是表面无波。



MFC 把消息分为三大类:

■ 命令消息(WM_COMMAND):命令消息意味着「使用者命令程序做某些动作」。

凡由UI 对象产生的消息都是这种命令消息,可能来自菜单或加速键或工具栏

按钮,并且都以WM_COMMAND 呈现。如何分辨来自各处的命令消息?SDK程序主要靠消息的wParam 辨识之,MFC 程序则主要靠菜单项目的识别码(menu ID)辨识之-- 两者其实是相同的。什么样的类别有资格接受命令消息?凡衍生自CCmdTarget 之类别,皆有资格。从command target 的字面意义可知,这是命令消息的目的地。也就是说,凡衍生自CCmdTarget 者,它的骨子里就有了一种特殊的机制。把整张MFC 类别阶层图摊开来看,几乎构造应用程序的最重要的几个类别都衍生自CCmdTarget,剩下的不能接收消息的,是像CFile、CArchive、CPoint、CDao(数据库)、Collection Classes(纯粹数据处理)、GDI 等等「非主流」类别。

■ 标准消息- 除WM_COMMAND 之外,任何以WM_ 开头的都算是这一类。任何衍生自CWnd 之类别,均可接收此消息。

■ Control Notification - 这种消息由控制组件产生,为的是向其父窗口(通常是对话盒)通知某种情况。例如当你在ListBox 上选择其中一个项目,ListBox 就会产生LBN_SELCHANGE 传送给父窗口。这类消息也是以WM_COMMAND 形式呈现。

 

万流归宗Command Target(CCmdTarget)

  你可以在程序的许多类别之中设计拦路虎(我是指「消息映射表格」),接收并处理讯息。只要是CWnd 衍生类别,就可以拦下任何Windows 消息。与窗口无关的MFC 类别(例如CDocument 和CWinApp)如果也想处理消息,必须衍生自CCmdTarget,并且只可能收到WM_COMMAND 命令消息。

   会产生命令消息的,不外就是UI 对象:菜单项目和工具栏按钮都是。命令消息必须有一个对应的处理函数,把消息和其处理函数「绑」在一块儿,这动作称为CommandBinding,这个动作将由一堆宏完成。通常我们不直接手工完成这些宏内容,也就是说我们并不以文字编辑器一行一行地撰写相关的码,而是藉助于ClassWizard。一个Command Target 对象如何知道它可以处理某个消息?答案是它会看看自己的消息映射表。消息映射表使得消息和函数的对映关系形成一份表格,进而全体形成一张网,当Command Target 对象收到某个消息,便可由表格得知其处理函数的名称。

三个奇怪的宏,一张巨大的网

  试着思考这个问题:C++ 的继承与多态性质,使衍生类别与基础类别的成员函数之间有着特殊的关联。但这当中并没有牵扯到Windows 消息。的确,C++ 语言完全没有考虑Windows 消息这一回事(那当然)。如何让Windows 消息也能够在对象导向以及继承性质中扮演一个角色?既然语言没有支持,只好自求多福了。消息映射机制的三个相关宏就是MFC 自求多福的结果。

「消息映射」是MFC 内建的一个消息分派机制,只要利用数个宏以及固定形式的写法,类似填表格,就可以让Framework 知道,一旦消息发生,该循哪一条路递送。每一个类别只能拥有一个消息映射表格,但也可以没有。下面是Scribble Document 建立消息映射表的动作:首先你必须在类别声明档(.H)声明拥有消息映射表格:

class CScribbleDoc : public CDocument
{
...
DECLARE_MESSAGE_MAP()
};

然后在类别实作档(.CPP)实现此一表格:

BEGIN_MESSAGE_MAP(CScribbleDoc, CDocument)
//{{AFX_MSG_MAP(CScribbleDoc)
ON_COMMAND(ID_EDIT_CLEAR_ALL, OnEditClearAll)
ON_COMMAND(ID_PEN_THICK_OR_THIN, OnPenThickOrThin)
...
//}}AFX_MSG_MAP
END_MESSAGE_MAP()

这其中出现三个宏。第一个宏BEGIN_MESSAGE_MAP 有两个参数,分别是拥有此消息映射表之类别,及其父类别。第二个宏是ON_COMMAND,指定命令讯息的处理函数名称。第三个宏END_MESSAGE_MAP 作为结尾记号。至于夹在BEGIN_ 和END_ 之中奇奇怪怪的说明符号//}} 和//{{,是ClassWizard 产生的,也是用来给它自己看的。记住,前面我就说了,很少人会自己亲手键入每一行码,因为ClassWizard 的表现相当不俗。夹在BEGIN_ 和END_ 之中的宏,除了ON_COMMAND,还可以有许多种。标准的Windows 消息并不需要由我们指定处理函数的名称。标准消息的处理函数,其名称也是「标准」的(预设的),像是:

宏名称  对映消息  消息处理函数

ON_WM_CHAR     WM_CHAR    OnChar
ON_WM_CLOSE     WM_CLOSE  OnClose
ON_WM_CREATE  WM_CREATE  OnCreate
ON_WM_DESTROY WM_DESTROY OnDestroy
ON_WM_LBUTTONDOWN  WM_LBUTTONDOWN  OnLButtonDown
ON_WM_LBUTTONUP  WM_LBUTTONUP  OnLButtonUp
ON_WM_MOUSEMOVE  WM_MOUSEMOVE  OnMouseMove
ON_WM_PAINT  WM_PAINT  OnPaint
...


消息映射网的形成: BEGIN_/ON_/END_

前置准备工作完成了,接下来的课题是如何实现并填充图9-1 的数据结构内容。当然你马上就猜到了,使用的是另一组宏:

BEGIN_MESSAGE_MAP(CMyView, CView)
ON_WM_PAINT()
ON_WM_CREATE()
...
END_MESSAGE_MAP()

如果没有把BEGIN_MESSAGE_MAP 宏中的两个参数(也就是类别本身及其父类别的名称)按照规矩来写,可能会发生什么结果呢?消息可能在不应该流向某个类别时流了过去,在应该被处理时却又跳离了。总之,完美的机制有了破绽。程序没当掉算你幸运!
将C/C++开发进行到底~!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值