Thinking in MFC---消息机制2
引言
在这一篇,我们会介绍一下在窗口类中,如何将消息路由到消息处理函数中去。
一、AFX_MSGMAP_ENTRY
在开始之前,我们有必要了解这个结构体。
struct AFX_MSGMAP_ENTRY
{
UINTnMessage; //windows message
UINTnCode; //control code or WM_NOTIFY code
UINTnID; //control ID (or 0 for windows messages)
UINTnLastID; //used for entries specifying a range of control id's
UINT_PTRnSig; //signature type (action) or pointer to message #
AFX_PMSGpfn; //routine to call (or special value)
};
其中pfn便是消息处理函数指针。
对于这个结构体中的其他成员变量,从它的命名和注释应该可以了解大概。接下来就看一个相关的宏。
二、GetMessageMap
这个函数就如它的命名,获得消息映射。
我们在使用MFC ClassWizard添加消息响应函数的时候,会自动在我们的窗口类中添加几行代码。
DECLARE_MESSAGE_MAP()
BEGIN_MESSAGE_MAP(yourclass, baseclass)
END_MESSAGE_MAP()
让我们一个一个看上面三个宏。
第一个DECLARE_MESSAGE_MAP(),从它的命名上就能明白它的作用,它是申明MessageMap。我们去它的定义处看看。
#define DECLARE_MESSAGE_MAP() \
protected: \
static constAFX_MSGMAP* PASCAL GetThisMessageMap(); \
virtual constAFX_MSGMAP* GetMessageMap() const; \
这里申明了两个函数。至于这两个函数是做什么,接下来就看它们的定义。就要看接下来两个宏。
#define BEGIN_MESSAGE_MAP(theClass, baseClass) \
PTM_WARNING_DISABLE\
const AFX_MSGMAP* theClass::GetMessageMap() const \
{ return GetThisMessageMap(); } \
const AFX_MSGMAP* PASCALtheClass::GetThisMessageMap() \
{ \
typedef theClass ThisClass; \
typedef baseClass TheBaseClass; \
static constAFX_MSGMAP_ENTRY _messageEntries[] = \
{
上面的第一个函数GetMessageMap,其实在调用GetThisMessageMap,所以我们重点就看第二个函数。
GetThisMessageMap其实就是定义了 AFX_MSGMAP_ENTRY结构体数组,而这个结构体就是我们第一节中的消息结构体。
接下来看一下
#define END_MESSAGE_MAP() \
{0, 0,0, 0, AfxSig_end, (AFX_PMSG)0 } \
}; \
static constAFX_MSGMAP messageMap = \
{&TheBaseClass::GetThisMessageMap, &_messageEntries[0] }; \
return &messageMap; \
} \
PTM_WARNING_RESTORE
上面这个宏主要是对GetThisMessageMap的结束处理。
从BEGIN_MESSAGE_MAP和END_MESSAGE_MAP这两个宏来看,GetThisMessageMap便是返回该类的消息映射结构体的数组。
接下来我们就要看MFC是怎么使用这个消息映射结构体的。
这里还有两个宏PTM_WARNING_DISABLE和PTM_WARNING_RESTORE,这两个宏作用就是在定义上面两个函数的时候,不会产生warning。
它们两个的源码是这样的
#define PTM_WARNING_DISABLE \
__pragma(warning( push )) \
__pragma(warning( disable : 4867 ))
#define PTM_WARNING_RESTORE \
__pragma(warning( pop ))
有兴趣的读者可以看一下关于warning的一些知识,这个不是我们这里的重点,所以就不做介绍了。
接下来看一下这个宏
ON_WM_PAINT()
这个宏你在class Wizard中添加WM_PAINT事件之后,便会自动添加在
BEGIN_MESSAGE_MAP(yourclass, baseclass)
ON_WM_PAINT()
END_MESSAGE_MAP()
让我们转向ON_WM_PAINT()这个宏定义处看看
#define ON_WM_PAINT() \
{ WM_PAINT, 0, 0, 0, AfxSig_vv, \
(AFX_PMSG)(AFX_PMSGW) \
(static_cast<void (AFX_MSG_CALL CWnd::*)(void) > ( &ThisClass :: OnPaint)) },
我们联系之前所讲的BEGIN_MESSAGE_MAP中的代码,其实这个ON_WM_PAINT()为messageEntries赋值,这里是一个结构体AFX_MSGMAP_ENTRY。你可以看到第一个参数是WM_PAINT便是nMessage,最后重要的是消息最终路由到那个函数处理,ThisClass :: OnPaint便是处理函数。
类似ON_WM_PAINT()其它宏都是注册了消息处理事件,告诉MFC什么消息交给什么函数处理。
三、窗口消息路由
至于怎么路由,笔者在网上大致找到了一下这么一段代码,说明这段代码只是用来介绍,并非MFC的源码。
BOOLCWnd::OnWndMsg(……)//
{
if(message == WM_COMMAND)
OnCommand(wParam,lParam);
if(message == WM_NOTIFY)
OnNotify(wParam,lParam,&lResult);
pMessage = GetMessageMap();
for(; pMessageMap!=NULL; pMessageMap =pMessageMap->pBaseMap)
{
if((lpEntry=AfxFindMessageEntry(pMessageMap->lpEntries,
message,0,0))!=NULL)
break;
}
(this->*(lpEntry->pnf))(……);//调用消息响应函数
}
AFX_MSGMAP_ENTRYAfxFindMessageEntry(……)
{
while(lpEntry->nSign!=AfxSig_end)
{
if(lpEntry->nMessage==nMsg&&lpEntry->nCode==nCode&&nID>=lpEntry->nID
&&nID<=lpEntry->nLastID)
{
return lpEntry;
}
lpEntry++;
}
}
在上面的代码中可以找到GetMessageMap的声影,它返回一个消息映射并存放在pMessage变量中。
接下来进行遍历,找到符合要求的消息映射,找到之后使用存放在其中的消息处理函数来调用消息处理函数。
上面提到的OnCommand和OnNotify,笔者这里不做介绍,留在下一篇介绍。
四、总结
开始编程不久,便接触到了MFC。用了MFC写了自己第一个视窗程序,写了第一个游戏,在学校课题演示的时候也是喜欢用MFC做演示。
现在工作了,项目的界面部分也在使用MFC开发的,值得庆幸的是,现在微软对于MFC的界面做了很大的改善,想起刚开始不断重写控件类,那个真是记忆犹新。
在平时编程的时候,VS这个集成工具确实很方便。但现在想把一些MFC零零碎碎的知识整合起来。所以这个系列可能会深入地去介绍MFC。