Lesson3:MFC消息映射机制
本文通过绘图程序向读者介绍了MFC程序的消息映射机制,并对比了前面所讲的消息循环机制。通过消息映射能进一步理解MFC程序的响应流程,对后面可以不用向导而手动修改MFC程序打下基础工作。
1. 消息映射机制
Windows程序的一个特点就是消息响应,通过发出一个消息然后捕获这个消息,最后响应这个消息。我们先通过在窗口中点击鼠标,产生一个提示鼠标点击的对话框这个例子来切身感受一下Windows的消息响应过程。在视图类里增加Windows的消息处理器,添加一个WM_LBUTTONDOWN消息,然后在MFC自动生成的函数体中添加自己的代码,如下。
voidCDrawView::OnLButtonDown(UINTnFlags,CPoint point) //鼠标左键按下的消息响应函数
{
// TODO: 在此添加消息处理程序代码和/或调用默认值
MessageBox("View Clicked");
CView::OnLButtonDown(nFlags,point);
}
如果我们在框架类里添加同样的消息响应,则编译链接生成的程序得不到想要的效果,这是因为,对于窗口而言,view类是覆盖在框架窗口mainframe之上的。所以虽然框架类有相应鼠标左键的功能函数,但我们点击鼠标的时候,鼠标点击的消息被视类所捕获,框架类捕获不到,就不会响应这个消息。
那么我们通过MFC向导生成的这个消息响应函数,向导在程序中做了什么呢?(三步)
①头文件中:在view类的头文件中添加一个宏,afx_msg表明是消息响应的函数原型
public:
afx_msg voidOnLButtonDown(UINT nFlags, CPointpoint);
②源文件中:在view类的源文件中发现了宏ON_WM_LBUTTONDOWN(),它将WM_LBUTTONDOWN这个鼠标按下的消息和视类里voidCDrawView::OnLButtonDown(UINT nFlags, CPoint point)函数关联起来,这样一旦这个消息产生,捕获到消息就会用消息响应函数去处理这个消息。
BEGIN_MESSAGE_MAP(CDrawApp, CWinAppEx)
ON_WM_LBUTTONDOWN()
END_MESSAGE_MAP()
③源文件中:添加了函数voidCDrawView::OnLButtonDown(UINT nFlags, CPoint point),这样可以在函数体中编写代码,响应消息。
消息循环是操作系统将产生的消息放到消息对列中,应用程序通过getmessage从消息对列中将消息取出来,最后通过dispatch message交给操作系统,操作系统调用过程函数进行处理消息。但在MFC中好像不是这种方式处理消息了,而是按照上面的三步进行的,即消息映射。消息映射是怎么处理的呢?
理论上我们可以有两种处理方式达到要求。
①在基类中对每一个消息做一个虚函数,当子类需要响应这个消息时,子类重写这个消息响应函数。通过多态性原理,子类有的调用子类的消息响应函数,子类没有的调用基类的消息响应函数。这样也可以完成消息的路由,完成消息响应,但这样的话,因为基类的消息有一百多个,那么凡是从基类派生的子类都会有一个虚函数,这样会浪费资源,所以就没有采用这种机制。
②MFC同过消息映射的机制完成消息响应。程序中,MFC在后台维护了一个句柄和C++对象的指针的对照表。例如,CDrawView窗口,窗口的句柄和CDrawView对象的指针有一个对照表,当收到消息的时候,消息结构体中第一个参数就表明了和哪个窗口相关的句柄。通过句柄找到对应的C++类对象的指针,通过指针传递给基类,基类调用windowproc函数(wincore.cpp源文件中),这个函数如下。
LRESULT CWnd::WindowProc(UINT message, WPARAM wParam, LPARAM lParam)
{
// OnWndMsg does mostof the work, except for DefWindowProc call
LRESULT lResult = 0;
if (!OnWndMsg(message,wParam, lParam, &lResult))
lResult =DefWindowProc(message, wParam, lParam);
return lResult;
}
windowproc是一个虚函数,在这个函数中,又调用了OnWndMsg函数,真正的消息路由是由OnWndMsg这个函数完成的。消息过来后,通过句柄找到指针,通过指针传给基类,这样它调用的就是子类的函数(view是基类,里面的CDrawView就已经是子类的)。OnWndMsg函数它响应消息前会判断有没有我们写的消息响应函数,它会在上面头文件中的DECLARE_MESSAGE_MAP()前查看是否有消息声明,源文件中在查看BEGIN_MESSAGE_MAP(CDrawApp, CWinAppEx)和END_MESSAGE_MAP()间有没有消息宏,如果有,就调用这个子类的消息响应函数。如果子类没有这个消息响应函数,就调用基类的默认消息响应函数。
MFC的消息映射方法和第一种方法基本思路是一样的,都是通过一个虚函数去调用消息响应函数,但第一种方法是每一种消息做一个虚函数,然后在子类中进行重载,这样同一种消息在不同的应用程序中就可以做出不同响应。而MFC发消息映射方法,相当于是对所有的消息都用同一个虚函数windowproc,然后这个函数调用OnWndMsg这个函数,这个函数通过查看头文件和源文件特定位置确定有没有消息宏来响应这个特定的消息。
【MFC映射机制】实现方法总结:在每个能接收和处理消息的类中,定义了一个消息和消息函数静态对照表,即消息映射表。在消息映射表中,消息与对应的消息处理函数指针是成对出现的。当有消息需要处理时,程序只要搜索该消息静态表,查看表中是否有该消息,就知道能否处理该消息了。消息响应函数在程序中有三处相关信息。头文件一处实现函数的声明,源文件两处分别用来函数的实现和关联消息和消息响应函数。
2. 简单直线绘制
直线的绘制需要得到起始点和终止点,可以用鼠标左键按下(起点)和鼠标左键松开(终点),如果能捕获到WM_LBUTTONDOWN和WM_LBUTTONUP消息,并对这两个消息进行响应,就能绘制直线。
在CDrawView类定义一个CPoint结构体的点m_ptOrigin,用来保存鼠标起点,新增鼠标左键按下和弹起的消息处理函数。
private:
CPoint m_ptOrigin;
voidCDrawView::OnLButtonDown(UINT nFlags, CPoint point) //消息响应函数
{
// TODO: 在此添加消息处理程序代码和/或调用默认值
m_ptOrigin= point;
CView::OnLButtonDown(nFlags,point);
}
voidCDrawView::OnLButtonUp(UINT nFlags, CPoint point)
{
// TODO: 在此添加消息处理程序代码和/或调用默认值
//使用CDC类实现绘制线条(实则是使用类里封装的函数)(客户区绘制)
CDC *pDC = GetDC();
pDC->MoveTo(m_ptOrigin);
pDC->LineTo(point);
ReleaseDC(pDC);
CView::OnLButtonUp(nFlags, point);
}