📄 04.1.2 消息映射机制.txt
字号:
4.1.2 消息映射机制
读者可以浏览一下 Draw工程的源程序,将会发现无论利用ClassWizard,还是利用第 3章介绍的【Add Windows Message Handler...】菜单命令为视类增加了一个鼠标左键按下这一消息响应函数之后,在源文件中都会增加三处代码。
(1)消息响应函数原型
在CDrawView类的头文件中,有如例4-3所示的这段代码。
~IJ 4-3
// Generated message map functions
protected: //{{AFX_MSG(CDrawView) afx_msg void OnLButtonDown(UINT nFlags , CPoint point); / /} }AFX二.MSG DECLARE_MESSAGE_MAP()
例 4-3所示这段代码中,在 DECLARE_MESSAGE_MAPO宏之上有两个 AFX一MSG注释宏。它们实际上就是宏,但因为前面加了注释符,所以称之为注释宏。这两个注释宏之间有一个函数原型OnLButtonDown,因为它位于两个注释宏之间,所以是以灰色显示的。该函数声明的前部有一个afx_ffisg限定符,这也是一个宏。该宏表明这个函数是一个消息响应函数的声明。
(2) ON_WM_LBUTIONDOWN消息映射宏
在CDrawView类的源文件中,有如例4-4所示的这段代码。
锣IJ 4-4
BEGIN_MESSAGE_MAP(CDrawView, CView) //{{AFX_MSG_MAP(CDrawView) ON_WM_LBUTTONDOWN() //}}AFX_MSG_MAP // Standard printing commands
.
ON_COMMAND(ID_FILE_PRINT , CView::OnFilePrint)
ON_COMMAND(ID_FILE_PRINT_DIRECT, CView::OnFilePrint)
ON_COMMAND(ID_FILE_PR工NT_PREVIEW, CView::OnFilePrintPreview)
END_MESSAGE_MAP()
.
上述例4-4所示代码中, BEGIN_MESSAGE_MAP和END_MESSAGE_MAPO这两个宏之间定义了CDrawView类的消息映射表,其中有一个ON_WM_LBUTIONDOWN这消息映射宏,这个宏的作用就是把鼠标左键按下消息 (WM_LBUTIONDOWN)与一个消息响应函数关联起来(本例中就把WM_LBU'I"I'ONDOWN消息与Or止ButtonDown函数关联
起来)。通过这种机制,一旦有消息产生,程序就会调用相应的消息响应函数来进行处理。
(3)消息响应函数的定义在CDrawView类的源文件中,可以看到OnLButtonDown函数的定义(如例4-2所示)。经过以上分析,可以知道,一个 MFC消息响应函数在程序中有三处相关信息:函数
原型、函数实现,以及用来关联消息和消息响应函数的宏。头文件中在两个AFX_MSG注释宏之间是消息响应函数原型的声明。源文件中有两处:一处是在两个 AFX_MSG_MAP注释宏之间的消息映射宏,通过这个宏把消息与消息响应函数关联起来:另一处是源文件中的消息响应函数的实现代码。本例中, CDrawView类对WM_LBUTIONDOWN消息响应的三处信息分别如下所示。
11 DrawUiew.h : interface of the CDrawUiew class
11
1111111111111111111111/11///////////////////////1////////1///////////////////
class CDraWUiew : public CUiew
<
protected: // create from serialization on19
CDrawUiew( >Z
DECL自REJVMRE自TE(CDrawUie叫
….. .
/1 Generated message map functions
protected:
II{{由FX_HSG(CDrawUiew) ①
afx_msg uoid OnLButtonDown(UIHT nFlags , CPoint point); /
II}}自FX MSG
'EELaRE-"ESS自EE-"愉P(】
};
11 DrawUiew.cpp : implementation of the CDrawUiew class
.include "DrawUiew.h"
….. .
11111111111111/111111/11111111////1111111111/1///////111111/1/111111111111111 11 CDrawUiew
EMPLEPENT-BV"ERE自TE(CDrawUieu, CUiew>
BEGIH旺ssasL"自P(CDrawUiew, CUiew> @
II{{自FE-MSG-H自P{CDrawUiew)
。"一WI1一lBUTTOHDOWH()
II}}自FX-MSG-"自P
110 I胁~..
11 Standard printing co~~ands
ON-Eom咱自NBEID-FELE-PRINT , EUie"zzonF11eprint>
BN-CBF刷aNDEIB-FILE-PRINT-DIRECT , EU1e":=nnF11eprint}
。N_COMMAND(ID_FILE_PRINT_PREUIEW. CUiew::OnFilePrintPreuiew>
END-MESS自EE-F嗣P()
.
…. . .
11111111111111111111111111111111111111111111111111111111111111111111111111111 11 CDrawUiew ~essage handlers
。oid CDrawU iew::OnLButtonDown(UINT nFlags. CPoint point> /@
f
/1 TODO:自dd your ~essage handler code here and/or call default
TlessageBox('"Uiew Clicked''");
CUiew::OnLButtonDown(nFlags. point);
}
在第1章中讲述消息循环时,曾介绍过,当有消息产生时,操作系统会把这条消息放到应用程序的消息队列中,应用程序通过 GetMessage函数从这个队列中取出一条具体的消息,并通过DispatchMessage函数把消息交给操作系统,后者调用应用程序的窗口过程,即窗口过程函数Wnd阶'OC进行处理。该函数利用 switch-case结构来对消息进行判别并分类处理。然而,我们看到在 MFC程序中,并不是按照这种途径进行处理的,只要遵照上述步骤,定义了与消息有关的三处信息后,就可以实现消息的响应处理。 MFC中采用的这种消息处理机制称为MFC消息映射机田。
实际上,消息路由可以有多种实现方式。其中一种可能的实现方式是,在基类中针对每种消息定义一个虚函数。当子类需要对某个消息进行处理时,只需要重写基类相应的虚函数即可。当在程序中调用这个函数时,根据多态性原理,如果子类重写了该函数,就调用子类的这个函数,否则就调用父类的相应函数。通过这种方式也可以完成消息的路由。这样做从原理上讲是没有问题的,但是从编程角度讲,是不可取的。
为什么不可取?因为虚函数必须由→个虚函数表 Cvtable)来实现。在应用程序中使用的每个派生类,系统都要为它们分配一个 vtable.并且不管基类中虚函数是否确实在派生类中被重写,这个vtable表中都要为基类的每一个虚函数提供一个4字节的输入项。而我们知道. MFC中类的派生层次很多,这样做,将使MFC类及其派生类背着一个很大的虚拟函数表的包袱,这对内存资源是→种浪费。而且,每次扫描虚拟函数表也是非常消耗时间的操作,这种定义虚拟函数的做法显然是不合适的。所以. MFC没有采用虚拟函数这种机制,而是采用一种称之为消息映射的机制来完成消息路由。
MFC消息映射机制的具体实现方法是z在每个能接收和处理消息的类中,定义一个消息和消息画数静态对照衰,即消息映射表。在消息映射表中,消息与对应的捎息处理画数指针是成对出现的。某个类能处理的所有消息及其对应的消息处理函数的地址都列在这个类所对应的静态表中。当有消息需要处理时,程序只要搜索该消息静态表,查看表中是否含有该消息,就可知道该类能否处理此消息。如果能处理该消息,则同样依照静态表能很容易找到并调用对应的消息处理函数。
下面让我们看看MFC消息映射机制的实际实现过程。 MFC在后台维护了一个窗口句柄与对应的C++对象指针的对照表。以本例中的CDrawView类为例,与CDrawView对象
" ‘ I 111
第4
相关的有一个窗口,窗口当然有它的窗口句柄,该句柄与 CDrawView对象的一个指针( t1 P CDrawView叫存在着一一对应关系,在窗口句柄与 C++对象对照表中就维护了这种对应关系。当收到某一消息时,消息的第一个参数就指明该消息与哪个窗口句柄相关,通过对照表,就可以找到与之相关的 C++对象指针。然后把这个指针传递给应用程序框架窗口类的基类,后者会调用一个名为 WindowProc的函数。该函数的定义位于 WinCore.cpp文件,代码如例 4-5所示。
例 4-5
LRESULT CWnd : :WindowProc(UINT message , WPARAM wParam, LPARAM lParam)
{ 11 OnWndMsg does most of the work, except for DefWindowProc call LRESULT 1Resu1t = 0;
if (!OnWndMsg(message , wParam, lParam, &lResult))
1Resu1t = DefWindowProc(message , wParam, 1Param) ;
return lResult ;
根据这个 WindowProc函数的定义,我们发现它是一个虚函数。同时,也可以发现, CWnd:: WindowProc函数内部调用了一个 OnWndMsg函数,真正的消息路由,也就是消息映射就是由此函数完成的。 OnWndMsg函数的定义也位于 WinCore.cpp文件中,部分代码如例 4-6所示。
例 4-6
BOOL CWnd : :OnWndMsg(UINT message , WPARAM wPararn, LPARAM lParam, LRESULT* pResult)
LRESULT lResult二 0;
11 special case for commands
if (message WM一COMMAND)
{
工f (OnCommand(wParam , lPararn))
lResult = 1;
goto LReturnTrue:
}
return FALSE ;
11 specia1 case for notifies
if (message WM_NOTIFY)
{
NMHDR* pNMHDR = (NMHDR*)lParam; if (pNMHDR->hwndFrorn ! = NULL && OnNotify (wParam, 1Param, &lResu1t) ) goto LReturnTrue ; return FALSE;
112 I ~~如
// specia1 case for activation if (message == WM_ACTIVATE) _AfxHand1eActivate(this , wParam, CWnd::FromHandle((HWND)lParam));
// specia1 case for set cursor HTERROR if (message WM_SETCURSOR && _AfxHandleSetCursor(this, (short) LOWORD(lParam) , H工WORD (lParam) ) )
1Resu1t = 1;
goto LReturnTrue;
return TRUE;
OnWndMsg函数的处理过程是:首先判断消息是否有消息响应函数。判断方法是在相应窗口类中查找所需的消息响应函数。因为传递给 WindowProc函数
的是窗口子类指针,所以, OnWndMsg函数会到相应的子类头文件中查找,看看 DECL础E_ MESSAGE_MAPO宏之上,两个 AFX_MSG注释宏之间是否有相应的消息响应函数原型的声明:再到子类的源文件中,看看 BEGIN_MESSAGE_MAP和 END_MESSAGEY1APO这两个宏之间是否有相应的消息映射宏。
如果通过上述步骤,找到了消息响应函数,那么接着就会调用该响应函数,对消息进
行处理。如果在子类中没有找到消息响应函数,那么就交由基类进行处理。通过以上步骤, MFC就实现了具体的消息映射,从而完成对消息的响应。
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -