MFC---消息机制

讲到MFC的消息,不得不首先讲到windows编程中SDK下的消息,MFC的消息机制也是基于SDK编程下的消息机制的,因此,我们在学习MFC下的消息机制的时候,应该首先了解SDK下的消息机制,推荐通过阅读msdn深入学习SDK下的消息机制。

在SDK下,我们在设计和注册窗口,创建窗口,显示和更新窗口之后,会看见如下一个代码:

while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}

这个一个消息循环,GetMessage从消息队列中取出消息,根据消息结构体中的窗口句柄,通过函数DispatchMessage将消息派发到合适的窗口中。TranslateMessage函数主要将虚拟键消息翻译为字符消息。我们知道,我们在设计的窗口类(非MFC类)的有个数据成员就是设置窗口过程的,通过DispatchMessage派发消息的时候,检查msg结构体的窗口句柄,发到到这个窗口的窗口过程中,然后,我们在窗口过程中通过switch/case语句来处理我们要处理的消息,将我们不处理的消息传递给默认窗口过程。

在MFC中在这个结构的基础之上扩展而来,针对MFC的特性,对这个机制做了扩展,但是我们在MFC中一般看不到上面这段运行消息循环的语句,其实是有这段的,只是封装在了CWinapp的Run函数中,但是在我们生成的MFC程序中,也看不到调用CWinapp的Run函数的语句啊,其实是这样的,我在前面的文章中也说过,我们在由MFC程序向导生成的MFC程序框架中,在C***app(CWinapp的继承类)中有一个InitInstance函数,在这个函数中做了一些初始化工作,如添加文档模板,初始化ActiveX支持,初始化快捷键管理器,执行命令创建和显示窗口等操作之后,我们在最后的返回语句中,我们看到了如下一句:

return TRUE;

但是在基于对话框的程序的InitInstance中返回的却是:

return FALSE;

在由MFC向导生成的基于模式对话框的程序中,对话框是模式对话框,也是不需要我们自己运行一个消息循环,因此在InitInstance最后返回FLASE表示不运行消息循环,当返回TRUE的时候,就需要运行消息循环,现在我们就可以知道,当InitInstance返回TRUE的时候,在CWinapp的底层就会调用Run函数运行消息循环了。就可以接收各种窗口消息,并发送到不同的窗口中。

接着就是消息的处理了,在SDK中,我们是通过switch/case语句来选择处理我们需要处理的消息。在MFC中是通过消息映射来实现的,说到消息映射很多学习者都很迷糊。在前面的文章中,我提到过抛弃MFC的单文档框架,想SDK中创建的过程一样,自己设计/注册一个窗口过程,然后创建它。在对窗口类WNDCLASS的数据成员进行幅值的时候,MFC和SDK中基本一致,只是有一个点,就是窗口过程,在SDK中,窗口过程函数是我们自己指定的,在MFC中,我们必须要设置为AfxWndProc。说明在MFC中也是由窗口过程的,但是要利用MFC的消息映射,所以我们必须要使用这个窗口过程,在SDK中我们就通过switch/case语句来选择要处理的消息,但是在MFC我们通过消息映射来处理。只要是直接或是间接继承于CCmdTarget类都会有一个消息映射,消息映射其实就是MFC预定义消息与处理函数之间的对应关系,如下:


这些是MFC预定义头文件中内容,我们看见一个熟悉的消息,WM_PAINT消息,这个消息在映射中的项是ON_WM_PAINT,消息响应函数是OnPaint。这些代码看不明白不要紧,我们主要是理解MFC的这种消息与函数的对应关系,当我们通过类向导或是类属性添加一个消息的响应的时候,代码都是自动添加的我们的程序中,我们只用把注意力集中在消息的响应上。下面我们要知道如何选择性的处理消息,先看下面的代码:

BEGIN_MESSAGE_MAP(CTransparencyDlg, CDialogEx)
ON_WM_PAINT()
ON_WM_QUERYDRAGICON()
ON_WM_CLOSE()
ON_WM_ERASEBKGND()
END_MESSAGE_MAP()

通过上面这个代码就可以选择性的处理我们要处理的消息,功能就和在SDK中的switch/case语句一样,当一个窗口接收的消息,就会检查这个窗口是否处理这个消息,如果在上面这个消息映射表中找到了映射项目,就表示它要处理这个消息,然后根据上面描述的在MFC预定义头文件中声明的ON_WM_PAINT对应的处理函数是OnPaint,接着程序就运行到OnPaint中,执行这个函数中的操作。

上面对比SDK的消息处理过程,描述了MFC的消息处理过程。但是MFC的是基于C++的,我们在MFC中使用窗口类都是继承于别的窗口类,如下:


我们可以看见CMyView类,CView类,CWnd类都是窗口类,都有自己的消息映射,我们也可以在CMyView类上继续继承,再继承的类也有自己的消息映射。因此在MFC的消息机制中相比较于SDK进行了扩展,我在看开始消息映射宏语句BEGIN_MESSAGE_MAP(CTransparencyDlg, CDialogEx)的两个参数,第一个是我们自己的类,第二个是我们自己类的父类,当我们自己的类不对某些消息进行处理的时候,就会把消息传递给父类。因此我们现在就知道了,CMyView的类不处理的消息,就会传递给CView类,如果CView类也不做处理,就传递给CWnd类,当最后一个窗口对象也不做处理的时候,那么这个消息就会传递给默认窗口过程进行处理。在这个传递的过程中,如果某个类处理了这个消息,消息可以不继续往下传,也可以通过传递给父类继续将消息往下传递。

以上基本就将MFC的标准窗口消息的处理机制描述问题,标准窗口消息一般以WM_开头,WM_COMMAND消息除外。下面讲讲WM_COMMAND消息,一般来自按钮,菜单的命令消息和来自控件的通知消息,都是通过这个消息来传递的,但是这个消息的处理和前面提到的窗口消息不同,下面就来描述一下:

对于标准的窗口消息,只能由窗口类能接收和处理,对于命令消息和通知消息,它们通过WM_COMMAND消息来传递命令,接收和处理它们的类不限制于窗口类,对于任何直接或是间接继承于CCmdTarget类的都可以处理命令,当然对于通知消息,一般仍然是由窗口类来处理的。当我们在菜单上点击一个菜单项,产生一个命令的时候,这个命令也可以由app类或是文档类处理。当一个对象接收到命令消息的时候,首先是给这个对象包含的对象机会处理它们,如果它们不处理,在看看自己能否处理,如下图:


这个图截图自msdn,我们看见,当一个文档框架窗口接收到命令,首先是查看当前激活的视图类能否处理,如果不处理,就给框架窗口,如果框架窗口也不处理,就传递给app类。对于命令消息的处理顺序,在msdn中有个标准顺序,大家可以参考msdn获取。命令消息实现这个顺序的传递其实是通过CCmdTarget::OnCmdMsg函数实现的,先看msdn中这个函数的声明:

virtual BOOL OnCmdMsg(
   UINT nID,
   int nCode,
   void* pExtra,
   AFX_CMDHANDLERINFO* pHandlerInfo 
);
第一个参数是命令的ID号,第二个参数是命令通知码(具体信息参考msdn),后面两个参数的意义参考msdn。那么是如何实现命令传递的呢,看下面的代码:

BOOL CMyView::OnCmdMsg(UINT nID, int nCode, void* pExtra, AFX_CMDHANDLERINFO* pHandlerInfo)
{
   
   if ((m_pActiveShape != NULL)
      && m_pActiveShape->OnCmdMsg(nID, nCode, pExtra, pHandlerInfo))
      return TRUE;


   return CView::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo);
}

我们可以看见CMyView的OnCmdMsg函数中调用了父类的OnCmdMsg函数,这样就把命令传递给了父类,这是一个mfc标准的传递,在这里我们看见,我们重写了这个虚函数,对默认的传递顺序做了改变,首先我们把命令传递给了m_pActiveShape对象,让它先看看能否处理这个命令。当有个类处理了这个消息,这个命令就不再往下传递,当然通过修改,也是可以继续的,一般不需要这样。

当我们选择一个菜单项目,程序执行了命令,也许我们需要改变这个菜单项目的状态,如是否禁用这个菜单项,或是改变菜单上的文本等等。那么这时候就要用到ON_UPDATE_COMMAND_UI 事件消息,这个消息的原理就是当我们点击菜单项目上的某个菜单的时候,如“文件”,“文件”菜单下有打开,新建等命令,在我们点击文件,弹出它的菜单项目之前,程序会首先检查执行这个菜单下所有菜单项目是否有ON_UPDATE_COMMAND_UI处理,如果有,则先调用这个处理当中操作,完成之后,再显示菜单。因此,我们就可以在这当中改变菜单项目的状态。我们要添加这个事件处理,可以进入资源编辑器,选择要处理的菜单项目,如我这里是处理“关于”菜单项目:


左边是选择要添加的事件,右边是添加响应处理的类,也就是说,这个命令由哪个类来处理,这里我选择用框架类,前面已经讲过命令的传递,这里就用到了,点击添加之后,在框架类的cpp文件中有了以下响应函数:

void CMainFrame::OnUpdateAppAbout(CCmdUI *pCmdUI)
{
// TODO: 在此添加命令更新用户界面处理程序代码
pCmdUI->Enable(0);
}

当然我在这里只是简单的添加了一个禁用此菜单项目(会变成灰色)的操作,我们在实际的编写中,可以添加判断根据程序的状态来改变菜单项目的状态,这里我运行一次程序,菜单“关于”就被禁用了:


下面是一个示意图,上面是命令的响应过程,下面是菜单项目的更新过程,来自msdn:

Commands in the Framework

VC_Commands Framework

Command Updating in the Framework

Command Updating Framework

到目前基本就将MFC的消息机制描述完了,下面在看看在一个类中添加消息响应,如果一个类要处理一个消息,一个方法可以通过类属性,另一个方法就是类向导。首先看看通过类属性,比如我要对一个类添加消息,一般为窗口类,将鼠标放到这个类上,右键点击属性,就会出现如下图所示:


在这当中,已经标出了类属性的功能,在这里我们就可以添加事件响应,消息响应,添加重写虚函数,下面看看添加消息的方法,点击消息按钮,如下图:


在要添加的消息后面点击就会出现添加某个消息的选项,如这里,我要添加WM_CREATE消息,如果某个消息已经添加,就会直接显示响应函数在相应消息的后面,如这里的WM_CLOSE,表示已经添加了。另外,添加重写虚函数,事件响应也是通过这样的办法,就可以像程序中添加处理了,我们只用关注在响应函数中写我们要执行的代码就可以了。

接着我们用另外一种方法添加消息响应的方法就是使用类向导,不管是在类视图中点击右键打开类向导,还是通过菜单栏上的“项目--类向导”,都可以打开类向导,如下图:


在类名一栏选择要添加消息响应的类的类名,在下面有消息,命令,虚函数,其实和上面讲的类属性里面是相同的,产生的效果也是一样的,在这里我们要响应的消息选中,点击添加,就可以向程序中添加我们要响应的消息。

那么我们点击添加消息响应之后,被添加的类的代码中有什么变化呢,有三处变化,编译器分别在三个地方,添加了代码,以这里的WM_CLOSE代码为例,首先是在头文件中,有消息响应函数的声明,如下:

afx_msg void OnClose();

然后在消息映射宏中有映射项,如下:

BEGIN_MESSAGE_MAP(CTransparencyDlg, CDialogEx)
ON_WM_PAINT()
ON_WM_QUERYDRAGICON()
ON_WM_CLOSE()           ///选择处理WM_CLOSE消息
ON_WM_ERASEBKGND()
END_MESSAGE_MAP()

最后是消息响应函数的实现:

void CTransparencyDlg::OnClose()
{
// TODO: 在此添加消息处理程序代码和/或调用默认值
GdiplusShutdown(gdiplusToken);
CDialogEx::OnClose();
}

现在我们知道了,当我们添加一个消息响应到一个类中的时候,有这三个地方添加了代码。那么反过来,如果我们要删除某个消息响应,也应该分别在这三个地方删除相应的代码。

总结

本文描述了有关在MFC中的消息机制,如果在阅读当中遇到问题,应及时查阅msdn,并且通过msdn做扩展学习,如果还要更加深入的学习windows的消息,参考msdn中有关消息,消息队列,窗口过程函数的文章,当中有更加全面详尽的描述。

本人邮箱:andyhl1987@126.com 欢迎交流学习

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值