消息反射

来源:互联网 发布:淘宝联盟卖家中心在哪 编辑:程序博客网 时间:2024/05/23 11:29

1、问题的提出:
  在Windows3.x中,不存在WM_NOTIFY消息,控制子窗口的通知消息同菜单的命令消息及加速键消息一样,均使用WM_COMMAND来发送,此时,根据WM_COMMAND消息传递的做法,可知道含有如下的传递结构:

wParam high-order : Notify Code :(如TTN_NEEDTEXT)
wParam low-order   ControlID
lParam ControlHandle


  使用上述结构进行消息的传递时,如果一个通知消息有一些附加的消息需要发送时(如传递LVN_COLUMNCLICK时可能需要附加传送如点击的是那一列等信息),因为WM_COMMAND仅能传递如上的参数,故无法做到。

2、解决的方案

  --Windows 3.x中的解决方案,为这些有附加消息传送要求的通知消息,为它们各自定义了许多特殊的消息。

  以WM_DRAWITEM为例:
  lParam参数成为指向DRAWITEMSTRUCT结构的指针,DRAWITEMSTRUCT结构如下:

typedef struct tagDRAWITEMSTRUCT {
UINT CtlType; 
UINT CtlID; 
UINT itemID; 
UINT itemAction; 
UINT itemState; 
HWND hwndItem; 
HDC hDC; 
RECT rcItem; 
ULONG_PTR itemData; 
} DRAWITEMSTRUCT;


  这样,就实现了附加参数的传递,类似的做法在Windows3.x中随处可见。

  --Windows32中的解决方案,Windows3.x的解决方案各自为政,没有统一性,为了解决这个问题,Window32中引入了一个新的消息,即WM_NOTIFY消息来解决这个问题,它将所有的这些消息采用一个统一的架构进行处理,为了与以前的处理方式兼容,上述那些特殊的消息仍然存在,它们在实质上也是相同的。

 控件通知消息,是指这样一种消息,一个窗口内的子控件发生了一些事情,需要通知父窗口。通知消息只适用于标准的窗口控件如按钮、

列表框、组合框、编辑框,以及Windows公共控件如树状视图、列表视图等。例如,单击或双击一个控件、在控件中选择部分文本、操作控件的

滚动条都会产生通知消息。 她类似于命令消息,当用户与控件窗口交互时,那么控件通知消息就会从控件窗口发送到它的主窗口。但是这种消

息的存在并不是为了处理用户命令,而是为了让主窗口能够改变控件,例如加载、显示数据。例如按下一个按钮,他向父窗口发送的消息也可

以看作是一个控件通知消息;单击鼠标所产生的消息可以由主窗口直接处理,然后交给控件窗口处理。      控件通知消息主要由窗口类即直接或间接由CWND类派生类处理。

     控件通知格式 

     控件通知经历了一个演变过程,因而SendMessage( )的变量Message、wParam和lParam有三种格式。

      第一控件通知格式      第一控件通知格式只是窗口消息的子集。它的特征格式如下:WM_XXXX。它主要来自下面的3种消息类型:

      (1)表示一个控件窗口要么已经被创建或销毁,要么已经被鼠标单击的消息:WM_PARENTNOTIFY;

      (2)发送到父窗口,用来绘制自身窗口的消息,例如: WM_CTLCOLOR、WM_DRAWITEM、WM_MEASUREITEM、WM_DELETEITEM、WM_CHARTOITEM、WM_VKTOITEM、WM_COMMAND和WM_COMPAREITEM

      (3)有滚动调控件发送,通知父窗口滚动窗口的消息:WM_VSCROLL和WM_HSCROLL 

     第二控件通知格式      第二控件通知格式与命令消息共享,它的特征格式如下:WM_COMMAND。      在WM_COMMAND中,lParam用来区分是命令消息还是控件通知消息:如果lParam为NULL,则这是个命令消息,否则lParam里面放的必然就是控件的句柄,是一个控件通知消息。对于wParam则是低位放的是控件ID,高位放的是相应的消息事件。

      第三控件通知格式      这个才真正涉及到我们要讲的内容,同时他也是最为灵活的一种格式。它的特征格式如下:WM_NOTIFY。      在WM_NOTIFY中,lParam中放的是一个称为NMHDR结构的指针。在wParam中放的则是控件的ID。

     NMHDR结构的由来 

     NMHDR结构是很值得一提的,该结构包括有关制作该通知的控件的任何内容,而不受空间和类型的限制,他的来历也是很有意思的。      在最初的windows3.x中,根本就不存在什么WM_NOTIFY,控件通知它们父窗口,如鼠标点击,控件背景绘制事件,通过发送一个消息到父窗

口。简单的通知仅发送一个WM_COMMAND消息,包含一个通知码和一个在wParam中的控件ID及一个在lPraram中的控件句柄。这样一来,wParam和

lParam就都被填充了,没有额外的空间来传递一些其它的消息,例如鼠标按下的位置和时间。

      为了克服这个困难,windows3.x就提出了一个比较低级的解决策略,那就是给一些消息添加一些附加消息,最为明显的就是控件自画用到

的DRAWITEMSTRUCT。不知道大家对这个结构熟悉不,不过,如果你是老手,你应该非常清楚这个结构,这个结构包含了9个内容,几乎你需要控

制的信息都给你提供了。为什么说它比较低级呢?因为不同的消息附加的内容不同,结果就是一盘散沙,非常混乱。

      在win32中,MS又提出了一个更好的解决方案:引进NMHDR结构。这个结构的引进就是消息统一起来,利用它可以传递复杂的信息。这个结

构的布局如下:

      NMHDR      {

         HWnd hWndFrom ; 相当于原WM_COMMAND传递方式的lParam

         UINT idFrom ;    相当于原WM_COMMAND传递方式的wParam(low-order)

         UINT code ;      相当于原WM_COMMAND传递方式的Notify Code(wParam"s high-order) 

     };      对于这个结构的应用于WM_NOTIFY信息结构,结果WM_NOTIFY就变成了:

      A、无附加信息。结构变得很简单,就是一个NMHDR结构。

      B、有附加信息。定义一个大的结构,它的第一个元素就是NMHDR结构,它的后面放置附加信息。

           WM_NOTIFY结构的好处      除了上面我们所说的好处外,WN_NOTIFY还有自己的独特的好处:

      由于在大结构中,第一个成员为NMHDR,这样一来,我们就可以利用指向NMHDR的指针来传递结构地址,根据指针的特性,无论消息有没有附

加信息,这个指针都适用,也能够很方便的进行强制转换。

     分析ON_NOTIFY

     类向导可以创建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* pLVKey= (LV_KEYDOWN*)pNMHDR; 

     *pResult = 0; 

     } 

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

     ON_NOTIFY_RANGE 

  有时我们可能需要为一组控件处理相同的WM_NOTIFY消息。这时需要使用ON_NOTIFY_RANGE而不是ON_NOTIFY。不过,很不幸的是,VC6的

ClassWizard并不支持这个消息,所以我们必须手工添加。方法和一般的手工添加的消息一样,不过需要注意的是:

      (1)当你使用 ON_NOTIFY_RANGE时,你需要指定控件的ID范围.其消息映射入口及函数原型如下:

       ON_NOTIFY_RANGE( wNotifyCode, id, idLast, memberFxn )

   其中:wNotifyCode:消息通知码.比如:LVN_KEYDOWN。

           id: 第一控件的标识ID。  

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

        memberFxn: 消息处理函数。

  (2)成员函数必须有如下原型申明:afx_msg void memberFxn( UINT id, NMHDR * pNotifyStruct, LRESULT * result );


在前面我们分析了控件通知消息WM_NOTIFY,和WM_NOTIFY紧密联系的还有一个MFC新特性:消息反射。 
   什么是消息反射? 
   在windows里面,子控件经常向父控件发送消息,例如很多子控件要绘制自己的背景,就可能向父窗口发送消息WM_CTLCOLOR。对于从子控件发来的消息,父控件有可能在处理之前,把消息返还给子控件处理,这样消息看起来就想是从父窗口反射回来一样,故此得名:消息反射。 

   消息反射的由来 
   在windows和MFC4.0版本一下,父窗口(通常是一个对话框)会对这些消息进行处理,换句话说,自控件的这些消息处理必须在父窗口类体内,每当我们添加子控件的时候,就要在父窗口类中复制这些代码,我们可以想象这是多么的复杂,代码是多么的臃肿! 
   我们可以想象,如果这些消息都让父窗口类去做,父窗口就成了一个万能的神,一个臃肿不堪的代码机,无论如何消息的处理都集中在父窗口类中,会使父窗口繁重无比,但是子控件却无事可做,并且代码也无法重用,这对于一个程序员来讲是多么痛苦的一件事?! 
   在老版本的MFC中,设计者也意识到了这个问题,他们对一些消息采用了虚拟机制,例如:WM_DRAWITEM,这样子控件就有机会控制自己的动作,代码的可重用性有了一定的提高,但是这还没有达到大部分人的要求,所以在高版本的MFC中,提出了一种更方便的机制:消息反射。 
   通过消息反射机制,子控件窗口便能够自行处理与自身相关的一些消息,增强了封装性,同时也提高了子控件窗口类的可重用性。不过需要注意的是:消息反射是MFC实现的,不是windows实现的;要让你的消息反射机制工作,你得类必须从CWnd类派生。 

   Message-Map中的处理 
   如果想要处理消息反射,必须了解相应的Message-Map宏和函数原型。一般来讲,Message-Map是有一定的规律的,通常她在消息的前面加上一个ON_ ,然后再消息的最后加上 _REFLECT。例如我们前面提到的WM_CTLCOLOR 经过处理后变成了ON_WM_CTLCOLOR_REFLECT;WM_MEASUREITEM则变成了ON_WM_MEASUREITEM_REFLECT。 
   凡事总会有例外,这里也是这样,这里面有3个例外: 
   (1) WM_COMMAND 转换成 ON_CONTROL_REFLECT; 
   (2) WM_NOTIFY 转换成 ON_NOTIFY_REFLECT; 
   (3) ON_UPDATE_COMMAND_UI 转换成 ON_UPDATE_COMMAND_UI_REFLECT; 
   对于函数原型,也必须是以 afx_msg 开头。 

   利用ClassWizard添加消息反射 
   (1)在ClassWizard中,打开选择项Message Maps; 
   (2)在下拉列表Class name中选择你要控制的类; 
   (3)在Object IDs中,选中相应的类名; 
   (4)在Messages一栏中找到前面带有=标记的消息,那就是反射消息; 
   (5)双击鼠标或者单击添加按钮,然后OK! 

   消息处理的过程 
  (1)子窗口向父窗口发送通知消息,激发父窗口去调用它的虚函数CWnd::OnNotify。大致的结构如下 
    BOOL CWnd::OnNotify(WPARAM wParam, LPARAM lParam, LRESULT* pResult) 
    { 
      if (ReflectLastMsg(hWndCtrl, pResult)) file://hWndCtrl,为发送窗口 
                   return TRUE; file://如果子窗口已处理了此消息,返回 
      AFX_NOTIFY notify; 
      notify.pResult = pResult; 
      notify.pNMHDR = pNMHDR; 
      return OnCmdMsg(nID, MAKELONG(nCode, WM_NOTIFY)? notify:NULL); 
    } 
  (2)ReflectLastMsg声明如下:static BOOL PASCAL ReflectLastMsg(HWND hWndChild, LRESULT* pResult = NULL); 
   它的主要任务就是调用发送窗口的SendChildNotifyLastMsg。 
  (3)SendChildNotifyLastMsg声明如下:BOOL SendChildNotifyLastMsg(LRESULT* pResult = NULL); 
   调用发送窗口的虚函数OnChildNotify函数,进行处理。 如果发送窗口没有进行重载处理,则调用ReflectChildNotify(...)函数进行标准的反射消息的消息映射处理。 

   使用的一个例子 
   这里面我们举一个简单的例子,希望大家能够更清晰的掌握消息反射机制。 
   (1)创建一个基于对话框的工程。 
   (2)利用向导创建一个新的类:CMyEdit,基类是CEdit。 
   (3)在CMyEdit头文件中加入3个成员变量: 
     COLORREF m_clrText ; 
COLORREF m_clrBkgnd ; 
CBrush m_brBkgnd; 
   (4)利用向导在其中加入WM_CTLCOLOR(看到了么,前面是不是有一个=?),并且将它的函数体改为: 
     HBRUSH CMyEdit::CtlColor(CDC* pDC, UINT nCtlColor) 
     { 
pDC->SetTextColor( m_clrText );   // text 
pDC->SetBkColor( m_clrBkgnd );   // text bkgnd 
return m_brBkgnd;         // ctl bkgnd 
     } 
     同时我们在.cpp文件中会看到ON_WM_CTLCOLOR_REFLECT(),这就是我们所说的经过处理的宏,是不是很符合规则? 
   (5)在对话框中加入一个Edit,增加一个关联的变量,选择Control属性,类别为CMyEdit。 
   (6)在对话框.cpp文件中加入#include "MyEdit.h",运行,看到了什么?

原创粉丝点击