首页 > C/C++开发工具专区 > VC技术 > 谈谈MFC中的消息映射
2006
10-08

谈谈MFC中的消息映射

引言:


  众所周知,windows是基于消息驱动的,作好消息处理是WINDOWS编程的关键任务之一,用VC制作WINDOWS程式同样离不开消息的处理。虽然VC++6的类向导可以完成绝大部分工作,但不幸的是,它并不能完成所有的工作。这就要求我们对 VC中消息的处理有一个比较清淅的认识。只有这样才可能在必要的时候亲自动手完成一些复杂的消息映射处理。


  在MFC中消息是通过一种所谓的消息映射机制来处理的。其实质是一张消息及其处理函数的一一对应表以及分析处理这张表的应用框架内部的一些程序代码.这样的好处是可以避免像早期的SDK编程一样需要罗列一大堆的CASE语句来处理各种消息.由于不同种类的消息其处理方法是不同的,所以我们有必要先弄清楚 WINDOWS消息的种类。


  背景:


  WINDOWS 消息的种类


  WINDOWS中消息主要有以下三种类型:


  1、标准的WINDOWS消息:这类消息是以WM_为前缀,不过WM_COMMAND例外。 例如: WM_MOVE、WM_QUIT等.


  2、命令消息:命令消息以WM_COMMAND为消息名.在消息中含有命令的标志符ID,以区分具体的命令.由菜单,工具栏等命令接口对象产生.


  3、控件通知消息:控件通知消息也是以WM_COMMAND为消息名.由编辑框,列表框,子窗口发送给父窗口的通知消息.在消息中包含控件通知码.以区分具体控件的通知消息.


  其中标准的WINDOWS消息及控件通知消息主要由窗口类即直接或间接由CWND类派生类处理.相对标准WINDOWS消息及控件通知消息而言,命令消息的处理对象范围就广得多.它不仅可以由窗口类处理,还可以由文档类,文档模板类及应用类所处理。
 
  方法:


  不同种类消息的映射方法。


  在以上三种消息中,标准的WINDOWS消息映射是相当简单的。可直接通过类向导完成不同消息的映射处理,所以不在本文讨论之列。


  凡是从CcmdTarget类派生的类都可以有消息映射.消息映射包括如下两方面的内容:


  在类的定义文件中(.H)中加上一条宏调用:


  DECLARE_MESSAGE_MAP()


  通常这条语句中类定义的最后.


  在类的实现文件(.CPP)中加上消息映射表:


  BEGIN_MESSAGE_MAP(类名,父类名)


    ………..


   消息映射入口项.


   ……….


   END_MESSAGE_MAP( )


  幸运的是除了某些类(如没有基类的类或直接从CobjectO类派生的类)外.其它许多类均可由类向导生成.尽管生成的类只是一个框架,需要我们补充内容.但消息映射表已经为我们加好了.只是入口项有待我们加入.


  命令消息映射入口项是一个ON_COMMAND的宏.比如文件菜单下的”打开…”菜单(ID值为ID_FILE_OPEN)对应的消息映射入口项为:


  ON_COMMAND(ID_FILE_NEW,OnFileOpen)


  加入消息映射入口项之后需要完成消息处理函数.在类中消息处理函数都是类的成员函数,要响应一个消息,就必须定义一个该消息的处理函数.定义一个消息处理函数包括以下三方面的内容.


  1.在类定义中加入消息处理函数的函数原型(函数声明)


  2.在类的消息映射表中加入相应的消息映射入口项.


  3.在类的实现中加入消息处理函数的函数体.


  需要说明的是消息处理函数的原型一定要以afx_msg打头.比如:


    afx_msg OnFileOpen();// 函数原型


    作为约定.消息处理函数一般以On打头


  但有时我们可能想用一个消息处理函数来处理一批消息。这时类向导就无能为力了。我们必须手工加入消息映射来完成这种工作。可用如下方法实现:


  首先在处理该消息所在类的实现文件(亦即.CPP)中加入的消息映射入口:


   …


  BEGIN_MESSAGE_MAP(CMyApp, CWinApp)


   file://{{AFX_MSG_MAP(CMyApp)


   …


   file://}}AFX_MSG_MAP


   ON_COMMAND_RANGE(ID_MYCMD_ONE, ID_MYCMD_TEN, OnDoSomething)


  END_MESSAGE_MAP( )


  …


粗体标志的语句是我们加入的语句(以后约定我们加入的语句均用粗体标志).其中我们使用了宏ON_COMMAND_RANGE来实现从命令消息ID_MYCMD_ONE到 ID_MYCMD_TEN都由OnDoSomthing一个消息函数处理.注意.ID_MYCMD_ONE到 ID_MYCMD_TEN的ID值一定要连续.且ID_MYCMD_ONE值一般较小.


  完成上述工作之后我们还需要在该类的头文件(亦即.H)中加入消息处理函数的申明:


  // Generated message-map functions


  protected:


  file://{{AFX_MSG(CMyApp)


  …


  file://}}AFX_MSG


  afx_msg void OnDoSomething( UINT nID );


  DECLARE_MESSAGE_MAP()


  由于这不是VC类向导加入的函数申明,所以放在了//}}AFX_MSG之外.


  注意这个消息处理函数有一个UINT类型参数.而处理单一命令的消息处理函数一般是没有参数(除更新用户接口对象状态命令消息处理函数).这个参数的主要作用是提供用户选择的命令的ID值.


  最后要做的工作就是在该类的实现文件中实现该消息处理函数. 同样,有时我们也想使用一个消息处理函数处理一批更新用户接口对象状态命令消息.方法同上:


  首先在.CPP文件中加入语句如下:


   …


   BEGIN_MESSAGE_MAP(CMyApp, CWinApp)


    file://{{AFX_MSG_MAP(CMyApp)


     …


    file://}}AFX_MSG_MAP


    ON_UPDATE_COMMAND_UI_RANGE (ID_MYCMD_ONE, ID_MYCMD_TEN, OnUpdateSomething)


   END_MESSAGE_MAP( )


    …


  在该类的头文件(亦即.H)中加入消息处理函数的申明:


   // Generated message-map functions


   protected:


   file://{{AFX_MSG(CMyApp)


   …


   file://}}AFX_MSG


   afx_msg void OnUpdateSomething( CcmdUI * pcmdui );


   DECLARE_MESSAGE_MAP()


请各位注意了,仔细的读者已经注意到这里的消息处理函数并未像命令消息处理函数需要一个额外的UINT类型的参数.原因在于pcmdui中已包含了此信息.所以不再需要这个参数了.最后不要忘了完成函数体!


  关于命令消息就讨论到这个地方.接下来讨论控件通知消息.


  控件通知消息相对而言就复杂一点了.限于篇幅不能一一涉及.这里我们仅讨论 WM_NOTIFY消息的处理.


  WM_NOTFY产生的原因如下。


  在WINDOWS3.X中控件通知它们父窗口,如鼠标点击,控件背景绘制事件,通过发送一个消息到父窗口.简单的通知仅发送一个WM_COMMAND消息.包含一个通知码(比如BN_CLICKED)和一个在wParam中的控件ID及一个在lPraram中的控件句柄.因为wParam 和lParam均被使用.就没有方法传送其它的附加信息了.比如在BN_CLICKED 通知消息中.就没有办法发送关于当鼠标点击时光标的位置信息.在这种情况下就只能使用一些特殊的消息.包括:WM_CTLCOLOR,WM_VSCROLL, WM_HSCROLL等等.值得一提的是这些消息能被反射回发送它们的控件.就是所谓的消息反射.有兴趣的读者请参阅有关专著.


  在WIN32中同样可以使用那些在WINDOWS3.1中使用的通知消息.不过不像过去通过增加特殊目的的消息来为新的通知发送附加的数据.而是使用一个叫 WM_NOTIFY的消息,它能以一个标准的风格传送大量的附加数据.
WM_NOTIFY消息包含一个存在wParam中的发送消息控件的ID和一个存在 lParam中的指向一个结构体的指针.这个结构可能是NMHDR结构体.也可能是第一个成员是NMHDR的更大的结构.因为NMHDR是第一个成员,所以指向这个结构的指针也可以指向NMHDR.


  在许多情况下,这个指针将指向一个更大的结构,当你使用时必需转换它.只有很少的通知消息.比如通用通知消息(它的名字以NM_打头),工具提示控件的 TTN_SHOW和TTN_POP实际上在使用NMHDR结构.


  NMHDR结构包含了发送消息控件的句柄,ID及通知码(如TTN_SHOW),其格式如下:


   Typedef sturct tagNMHDR{


    HWND hwndFrom;


    UINT idFrom;


    UINT code;


   } NMHDR;


  对TTN_SHOW消息而言,code成员的值将设为TTN_SHOW.


  类向导可以创建ON_NOTIFY消息映射入口并为你提供一个处理函数的框架.来处理 WM_NOTIFY类型的消息.ON_NOTIFY消息映射宏有如下语法.


   ON_NOTIFY(wNotifyCode,id,memberFxn)


  数意义如下:


   wNotifyCode:要处理的通知消息通知码。比如:LVN_KEYDOWN.


   Id:控件标识ID.


   MemberFxn:处理此消息的成员函数.


  此成员函数必需有如下的原形申明:


   afx_msg void memberFxn( NMHDR * pNotifyStruct, LRESULT * result);


  比如:假设你想成员函数OnKeydownList1处理ClistCtrl(标识ID=IDC_LIST1)的 LVN_KEYDOWN消息,你可以使用类向导添加如下的消息映射:


   ON_NOTIFY( LVN_KEYDOWN, IDC_LIST1, OnKeydownList1 )


  在上面的例子中,类向导提供如下函数:


   void CMessageReflectionDlg::OnKeydownList1(NMHDR* pNMHDR, LRESULT* pResult)


    {


     LV_KEYDOWN* pLVKeyDow = (LV_KEYDOWN*)pNMHDR;


     // TODO: Add your control notification handler


     // code here


     *pResult = 0;


     }


   这时类向导提供了一个适当类型的指针.你既可以通过pNMHDR,也可以通过 pLVKeyDow来访问这个通知结构。


  如前所述,有时我们可能需要为一组控件处理相同的WM_NOTIFY消息.这时需要使用ON_NOTIFY_RANGE而不是ON_NOTIFY.当你使用 ON_NOTIFY_RANGE时,你需要指定控件的ID范围.其消息映射入口及函数原型如下:


   ON_NOTIFY_RANGE( wNotifyCode, id, idLast, memberFxn )


    参数说明:


     wNotifyCode:消息通知码.比如:LVN_KEYDOWN,


     id: 第一控件的标识ID。


     idLast:最后一个控件的标识ID。(标识值一定要连续)


     memberFxn: 消息处理函数。


    成员函数必须有如下原型申明:


    afx_msg void memberFxn( UINT id, NMHDR * pNotifyStruct, LRESULT * result );


    其中id的表示发送通知消息的控件标识ID


  结束语:


  于目前介绍MFC消息映射的资料甚少.而这部分内容对编程又相当重要.本文简要地介绍了MFC中的几种重要的消息映射处理.但基于篇幅有限没能作更全面更深入的探讨.


留下一个回复