友情提示:如果本网页打开太慢或显示不完整,请尝试鼠标右键“刷新”本网页!
深入浅出MFC第2版(PDF格式)-第76部分
快捷操作: 按键盘上方向键 ← 或 → 可快速上下翻页 按键盘上的 Enter 键可回到本书目录页 按键盘上方向键 ↑ 可回到本页顶部! 如果本书没有阅读完,想下次继续接着阅读,可使用上方 "收藏到我的浏览器" 功能 和 "加入书签" 功能!
//{{AFX_MSG_MAP(CScribbleDoc)
ON_MAND(ID_EDIT_CLEAR_ALL; OnEditClearAll)
ON_MAND(ID_PEN_THICK_OR_THIN; OnPenThickOrThin)
。。。
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
551
…………………………………………………………Page 614……………………………………………………………
第篇 深入 MFC 程式設計
这其中出现三个宏。第一个宏BEGIN_MESSAGE_MAP 有两个参数,分别是拥
有此消息映射表之类别,及其父类别。第二个宏是ON_MAND,指定命令讯
息的处理函数名称。第三个宏END_MESSAGE_MAP 作为结尾记号。至于夹在
BEGIN_ 和END_ 之中奇奇怪怪的说明符号//}} 和//{{,是ClassWizard 产生
的,也是用来给它自己看的。记住,前面我就说了,很少人会自己亲手键入每一行码,
因为ClassWizard 的表现相当不俗。
夹在BEGIN_ 和END_ 之中的宏,除了ON_MAND,还可以有许多种。标准的
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
。。。
DECLARE_ MESSAGE_ MAP 宏
消息映射的本质其实是一个巨大的数据结构,用来为诸如WM_PAINT 这样的标准消息
决定流动路线,使它得以流到父类别去;也用来为WM_MAND 这个特殊消息决定
流动路线,使它能够七拐八弯地流到类别阶层结构的旁支去。
观察机密的最好方法就是挖掘源代码:
552
…………………………………………………………Page 615……………………………………………………………
第9章 訊息映射與命令繞行
// in AFXWIN。H
#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 修饰词限制了资料的配置,使得每个「类别」仅有一份资料,而不是每一
个「对象」各有一份资料。
我们看到两个陌生的类型:AFX_MSGMAP_ENTRY 和AFX_MSGMAP 。继续挖源代码,
发现前者是一个struct :
// in AFXWIN。H
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 。其中pfn 的
数据类型AFX_PMSG 被定义为一个函数指针:
typedef void (AFX_MSG_CALL CCmdTarget::*AFX_PMSG)(void);
出现在DECLARE_MESSAGE_MAP 宏中的另一个struct ,AFX_MSGMAP ,定义如下:
// in AFXWIN。H
struct AFX_MSGMAP
{
const AFX_MSGMAP* pBaseMap;
const AFX_MSGMAP_ENTRY* lpEntries;
};
其中pBaseMap 是一个指向「基础类别之消息映射表」的指针,它提供了一个走访整个
继承串链的方法,有效地实作出消息映射的继承性。衍生类别将自动地「继承」其基础
553
…………………………………………………………Page 616……………………………………………………………
第篇 深入 MFC 程式設計
类别中所处理的消息,意思是,如果基础类别处理过A消息,其衍生类别即使未设计A
消息之消息映射表项目,也具有对A消息的处理能力。当然啦,衍生类别也可以针对A
消息设计自己的消息映射表项。
喝,真像虚拟函数!但Message Map 没有虚拟函数所带来的巨大的overhead (额外负担)
透过DECLARE_MESSAGE_MAP 这么简单的一个宏,相当于为类别声明了图9…1 的
数据类型。注意,只是声明而已,还没有真正的实体。
pBaseMap
_messageEntries''
lpEntries
nMessage; nCode; nID; nLastID; nSig; pfn
messageMap
图9…1 DECLARE_MESSAGE_MAP 宏相当于声明了这样的数据结构。
消息映射网的形成 : 宏
BEGIN_/ON_/END_
前置准备工作完成了,接下来的课题是如何实现并填充图 的数据结构内容。当然你
9…1
马上就猜到了,使用的是另一组宏:
BEGIN_MESSAGE_MAP(CMyView; CView)
ON_WM_PAINT()
ON_WM_CREATE()
。。。
END_MESSAGE_MAP()
奥秘还是在源代码中:
554
…………………………………………………………Page 617……………………………………………………………
第9章 訊息映射與命令繞行
// 以下源代码在AFXWIN。H
#define BEGIN_MESSAGE_MAP (theClass; baseClass)
const AFX_MSGMAP* theClass::GetMessageMap() const
{ return &theClass::messageMap; }
AFX_DATADEF const AFX_MSGMAP theClass::messageMap =
{ &baseClass::messageMap; &theClass::_messageEntries'0' };
const AFX_MSGMAP_ENTRY theClass::_messageEntries'' =
{
#define END_MESSAGE_MAP ()
{0; 0; 0; 0; AfxSig_end; (AFX_PMSG)0 }
};
注意:AfxSig_end 在AFXMSG_。H 中被定义为0
// 以下源代码在AFXMSG_。H
#define ON_MAND (id; memberFxn)
{ WM_MAND; CN_MAND; (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_MOVE ()
{ WM_MOVE; 0; 0; 0; AfxSig_vvii;
(AFX_PMSG)(AFX_PMSGW)(void (AFX_MSG_CALL CWnd::*)(int; int))OnMove };
#define ON_WM_SIZE ()
{ WM_SIZE; 0; 0; 0; AfxSig_vwii;
(AFX_PMSG)(AFX_PMSGW)(void (AFX_MSG_CALL CWnd::*)(UINT; int; int))OnSize };
#define ON_WM_ACTIVATE ()
{ WM_ACTIVATE; 0; 0; 0; AfxSig_vwWb;
(AFX_PMSG)(AFX_PMSGW)(void (AFX_MSG_CALL CWnd::*)(UINT; CWnd*;
BOOL))OnActivate };
#define ON_WM_SETFOCUS ()
{ WM_SETFOCUS; 0; 0; 0; AfxSig_vW;
(AFX_PMSG)(AFX_PMSGW)(void (AFX_MSG_CALL CWnd::*)(CWnd*))OnSetFocus };
#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 };
。。。
555
…………………………………………………………Page 618……………………………………………………………
第篇 深入 MFC 程式設計
于是,这样的宏:
BEGIN_MESSAGE_MAP(CMyView; CView)
ON_WM_CREATE()
ON_WM_PAINT()
END_MESSAGE_MAP()
便被展开成为这样的码:
const AFX_MSGMAP* CMyView::GetMessageMap() const
{ return &CMyView::messageMap; }
AFX_DATADEF const AFX_MSGMAP CMyView::messageMap =
{ &CView::messageMap; &CMyView::_messageEntries'0' };
const AFX_MSGMAP_ENTRY CMyView::_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 }
};
其中AFX_DATADEF 和AFX_MSG_CALL 又是两个看起来很奇怪的常数。你可以在两
个文件中找到它们的定义:
// in DEVSTUDIOVCMFCINCLUDEAFXVER_。H
#define AFX_DATA
#define AFX_DATADEF
// in DEVSTUDIOVCMFCINCLUDEAFXWIN。H
#define AFX_MSG_CALL
显然它们就像afx_msg 一样(我曾经在第6章的HellpMFC 源代码一出现之后解释
过),都只是个〃intentional placeholder〃 (刻意保留的空间),可能在将来会用到,目前
则为「无物」。
556
…………………………………………………………Page 619……………………………………………………………
第9章 訊息映射與命令繞行
以图表示BEGIN_! K/ON_! K/END_! K 宏的结果为:
CView::messageMap
pBaseMap
CMyView::_messageEntries''
lpEntries
WM_CREATE; 0; 0; 0; AfxSig_is; OnCreate
WM_PAINT; 0; 0; 0; AfxSig_vv; OnPaint CMyView::messageMap
0; 0; 0; 0; AfxSig_end; 0
注意:图中的AfxSig_vv 和AfxSig_is 都代表签名符号(Signature)。这些常数在AFXMSG_。H
中定义,稍后再述。
前面我说过了,所有能够接收消息的类别,都应该衍生自CCmdTarget。那么我们这么推
论应该是合情合理的: 每一个衍生自CCmdTarget 的类别都应该有
DECLARE_/BEGIN_/END_ 宏组?
唔,错了,CWinThread 就没有!
可是这么一来,CWinApp 通往CCmdTarget 的路径不就断掉了吗?呵呵,难道CWinApp
不能跳过CWinThread 直接连上CCmdTarget 吗?看看下面的MFC 源代码:
// in AFXWIN。H
class CWinApp : public CWinThread
{
。。。
DECLARE_MESSAGE_MAP()
};
// in APPCORE。CPP
BEGIN_MESSAGE_MAP(CWinApp; CCmdTarget) //注意第二个参数是CCmdTarget,
//{{AFX_MSG_MAP(CWinApp) //而不是CWinThread。
// Global File mands
557
…………………………………………………………Page 620……………………………………………………………
第篇 深入 MFC 程式設計
ON_MAND(ID_APP_EXIT; OnAppExit)
// MRU most recently used file menu
ON_UPDATE_MAND_UI(ID_FILE_MRU_FILE1; OnUpdateRecentFileMenu)
ON_MAND_EX_RANGE(ID_FILE_MRU_FILE1; ID_FILE_MRU_FILE16; OnOpenRecentFile)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
让我们看看具体的情况。图9…2 就是MFC 的消息映射表。当你的衍生类别使用了
DECLARE_/BEGIN_/END_ 宏,你也就把自己的消息映射表挂上去了…当然是挂在尾
端。
如果没有把BEGIN_MESSAGE_MAP 宏中的两个参数(也就是类别本身及其父类别的
名称)按照规矩来写,可能会发生什么结果呢?消息可能在不应该流向某个类别时流了
过去,在应该被处理时却又跳离了。总之,完美的机制有了破绽。程序没当掉算你幸运!
558
…………………………………………………………Page 621……………………………………………………………
第9章 訊息映射與命令繞行
CWinThread CWinApp CMyWinApp
; ; ; ; ; ; ; ; ; ;
0;0;0;0;0;0 0;0;0;0;0;0
CView CMyView
m
e
CCmdTarget CWnd CFrameWnd CMyFrameWnd
s
; ; ; ; ; ; ; ; ; ;
s
0;0;0;0;0;0 0;0;0;0;0;0 a
; ; ; ; ; ; ; ; ; ; ; ; ; ; ;
; ; ; ; ; g
0;0;0;0;0;0 0;0;0;0;0;0 0;0;0;0;0;0 e
0;0;0;0;0;0
CDocument CMyDocument
; ; ; ; ; ; ; ; ; ;
0;0;0;0;0;0 0;0;0;0;0;0
图9…2 MFC 消息映射表 ( 也就是消息流动网)
我们终于了解,Message Map 既可说是一套宏,也可以说是宏展开后所代表的一套
数据结构;甚至也可以说Message Map 是一种动作,这个动作,就是在刚刚所提的资料
结构中寻找与消息相吻合的项目,从而获得消息的处理例程的函数指针。
虽然,C++ 程序员看到多态(Polymorphism),直觉的反应就是虚拟函数,但请注意,
各个Message Map 中的各个同名函数虽有多态的味道,却不是虚拟函数。乍想之下使用
虚拟函数是合理的:你产生一个与窗口有关的C++ 类别,然后为此窗口所可能接收的
任何消息都提供一个对应的虚拟函数。这的确散发着C++ 的味道和对象导向的精神,
但现实与理想之间总是有些距离。
55
快捷操作: 按键盘上方向键 ← 或 → 可快速上下翻页 按键盘上的 Enter 键可回到本书目录页 按键盘上方向键 ↑ 可回到本页顶部!
温馨提示: 温看小说的同时发表评论,说出自己的看法和其它小伙伴们分享也不错哦!发表书评还可以获得积分和经验奖励,认真写原创书评 被采纳为精评可以获得大量金币、积分和经验奖励哦!