第 1 节 MFC定义了哪些消息
根据《深入浅出MFC》上所描述的,MFC的消息分为:消息和命令和Control Notification,消息的命令是以WM_作为开头的,命令是以ID开头的,那么这些消息或命令到底是在哪儿进行定义的呢?
首先,我们来看命令,我们在编译器中跳转到定义,发现它们被定义在afxres.h之中。如下:
分为Filecommands,编号从0xE100到0xE10C,Edit commands,编号从0xE120到0xE12C,Windows commands编号从0xE130到0xE135,Help and App commands编号从0xE140到0xE147,OLE commands编号从0xE200到0xE21F,View commands编号从0xE800到0xE815,RecordForm commands,编号从0xE900到0xE903。
那消息的定义在哪儿呢?在编译器里面跟踪,可以发现消息的定义在WinUser.h里面,如下图所示:
上网查资料得知,系统保留的消息标识符的取值范围为0x0000~0x03FF(0~1023),专门用于系统定义的消息;应用定义的消息不能使用这些值,应用定义的消息取值范围为0x0400~0x7FFF(0~32767)。
第 2 节 跟踪一条系统消息
MFC的消息机制在《深入浅出MFC》中已经讲得很清楚,但是为了更熟悉这个流程,我决定跟踪一条系统的消息,看看它被处理的过程。我用Visual Studio 2010建立了一个多文档的工程,命名为mfcmul,在mfcmulview是这个工程的视图类,在这个工程里面添加WM_LButtonDown的消息处理函数:
首先找到整个消息的源头,Windows的回调函数,在文件wincore.cpp里面AfxCallWndProc函数。此函数调用函数pWnd->WindowProc。但是不知道什么原因,跟踪AfxCallWndProc出现的困难,所以从pWnd->WindowProc开始。
在View里面的消息首先送到pWnd->WindowProc里面,然后pWnd->WindowProc调用虚函数pWnd->OnWndMsg
在OnWndMsg里面会根据消息的类型判断是不是WM_COMMAND,如果是WM_COMMAND,则调用OnCommand函数,再判断是不是WM_NOTIFY,如果是WM_NOTIFY则调用函数OnNotify,再判断消息是不是WM_ACTIVATE,显然WM_LButtonDown不属于这些消息,则继续查找,然后在该函数的一个地方循环消息的Map,然后调用相应的函数进行处理:
第 3 节 跟踪一条命令
建立了一个名为mfcmulview的工程,并在消息处理的总的函数CWnd::WindowProc里面打断点,逐步跟踪。点击“文件保存”按钮,进入了Cwnd::OnWndMsg函数,默认情况下,无论消息还是命令都会在该函数里面进行处理,这里面会区分消息、命令然后进行不同的处理,现在是命令,所以再调用OnCommand函数进行处理。其实可以把“命令”看作一种特殊的消息,无论是点击“文件保存”、“文件打开”所有的命令都是一样的,都是占用一个消息的地址,WM_COMMAND值为0x111。那么命令之间的区分可能是通过wParam和lParam两个参数来进行区分。
OnCommand是一个虚函数,现在进入了CMDIFrameWndEx::OnCommand,然后该函数又调用了CMDIFrameWnd::OnCommand。
第 4 节 消息和命令的不同
命令其实是一种特殊的消息,它的消息编号为:0x111,但是在Windows里面却将命令和消息进行了分开的不同的处理。消息一般只从子类向父类流动,它的处理函数一般是一个虚函数,它的指针一般指向子类,如果子类重载了这个函数,那么一般它先处理消息,然后再调用父类来处理消息。如果子类没有重载这个函数,那么父类来处理这个函数。
而命令有一个奇怪的流程,比如:“文件打开”这个命令,视图View可以处理它、文档Document也可以处理它,命令一般不会传递数据,且View如果处理了这个命令,那么Document就不会得到这个命令了。所以MFC的各个命令你可以选择一个位置如Document、如View来进行这个命令的处理。
第 5 节 消息和事件的关系
在我理解,其实事件就相当于一个函数,它是对消息进行的处理。消息是用户的操作或者说是发生了什么样的改变。一个消息往往会引发出多个事件,比如:遮住的窗口显示出来,这时会重绘,但是重绘会引发出一系列的事件,首先,会调用OnEraseBkgnd,然后再调用OnDraw,所以会引发一系列的改变。或者可能被遮掩的窗口被显示出来后,系统自动发送了一系列的消息。