MFC Windows 程序设计->消息反射

来源:互联网 发布:app简约下载网页源码 编辑:程序博客网 时间:2024/04/18 14:45

 

1、消息反射解释:

  父窗口将控制子窗口发给它的通知消息,首先反射回子窗口进行处理(即给控制子窗口一个机会,让控制子窗口处理此消息),这样通知消息就有机会能被子窗口自身进行处理。

2、MFC中引入消息反射的原因:

  在Windows的消息处理中,控制子窗口的发给其父窗口的通知消息只能由其父窗口进行处理,这使得控制子窗口的自身能动性大大降低(你想,它连改变自己的背景色,处理一个自身滚动问题都要其父窗口来完成),为了解决这个问题,在MFC中引入了反射消息“Reflect Message”的概念,进行消息反射,可以使得控制子窗口能够自行处理与自身相关的一些消息,增强了封装性,从而提高了控制子窗口的可重用性。

3、消息反射的处理流程(不考虑OLE控制)

  一、消息反射处理流程图:

  1、父窗口收到控制子窗口发来的通知消息后,调用它的虚函数CWnd::OnNotify.

  CWnd::OnNotify()主体部分:

  {

  if (ReflectLastMsg(hWndCtrl, pResult)) //此时,hWndCtrl,为发送窗口,即子窗口的句柄

  return TRUE; // 子窗口已处理了此消息

  AFX_NOTIFY notify;

  notify.pResult = pResult;

  notify.pNMHDR = pNMHDR;

  return OnCmdMsg(nID, MAKELONG(nCode, WM_NOTIFY), ¬ify, NULL);

  }

  分析:首先,调用ReflectLastMsg(hCtrlChildWnd,...)给子窗口一个自身处理的机会,将消息反射给子窗口处理,函数返回TRUE,表明子窗口处理了此消息。反之,表示子窗口未处理此消息,此时,调用OnCmdMsg(...)由父窗口进行通常的处理。

  2、ReflectLastMsg中:

  主要是调用发送窗口的SendChildNotifyLastMsg(...)。

  3、SendChildNotifyLastMsg 中:

  调用发送窗口的虚函数OnChildNotify函数,进行处理。 如果没有处理,则调用ReflectChildNotify(...)函数进行标准的反射消息的消息映射处理。

  二、消息处理

  方式1:

  由上述处理流程可以看出来,子窗口要想自身处理此消息,重载子控件窗口的OnChildNotify虚拟函数应该是很容易想到的方式。

  注意:MFC中对各个子控件窗口一般都已经重载了OnChildNotify函数,它对应调用类的虚函数进行处理,所以,你重载对应的虚函数即可,如下例:

  BOOL CStatusBarCtrl::OnChildNotify(UINT message, WPARAM wParam, LPARAM lParam,LRESULT* pResult)

  {

  if (message != WM_DRAWITEM) //对应不同的控制,会有不同的有特殊处理要求的消息。

  return CWnd::OnChildNotify(message, wParam, lParam, pResult);

  ...

  ...

  DrawItem((LPDRAWITEMSTRUCT)lParam);

  return TRUE;

  }

  virtual void DrawItem( LPDRAWITEMSTRUCT lpDrawItemStruct );

  void CStatusBarCtrl::DrawItem(LPDRAWITEMSTRUCT)

  {

  ASSERT(FALSE); // must override for self draw status bars

  }

  你重载CStatusBarCtrl类的DrawItem虚拟函数,即可实现对反射消息WM_DRAWITEM的处理。

  方式2:

  从方式1可以看出,如果你不在被重载的OnChildNotify中对消息进行处理,函数会调用CWnd::OnChildNotify,它调用ReflectChildNotify函数进行标准的处理。

  1、增加反射消息的映射入口。

  2、增加对应的消息处理函数。

  注意:可以使用MFC的ClassWizard作上述动作,在ClassWizard中,可处理的反射消息以一个"="号以示区别。返回值为TRUE,表示控件窗口已处理此反射消息,为FALSE,表示控件子窗口未处理此反射消息。

  结语:

  消息反射不是很难的概念。它仅出现在MFC中;它的用意是方便控制子窗口的重用;对某些通知消息你可以重载对应的虚函数(WM_DRAWITEM...)进行处理;对其它你可以使用标准的消息反射映射进行处理。限于篇幅,一些细节问题,请阅读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",运行。

 

 

ON_WM_CTLCOLOR_REFLECT is one of several message-map macros introduced in MFC 4.0 that permit notification messages to be reflected back to the controls that sent them. Message reflection is a powerful tool for building reusable control classes because it empowers derived control classes to implement their own behavior independent of their parents. Previous versions of MFC reflected certain messages back to the controls that sent them using a virtual CWnd function named OnChildNotify. Modern versions of MFC make the concept of message reflection generic so that a derived control class can map any message sent to its parent to a class member function. You saw an example of message reflection at work in the previous section when we derived a new class from CStatic and allowed it to handle its own WM_CTLCOLOR messages.

The following table contains a list of the message reflection macros MFC provides and short descriptions of what they do.

MFC Message Reflection Macros

MacroDescriptionON_CONTROL_REFLECTReflects notifications relayed through WM_COMMAND messagesON_NOTIFY_REFLECTReflects notifications relayed through WM_NOTIFY messagesON_UPDATE_COMMAND_UI_REFLECTReflects update notifications to toolbars, status bars, and other user interface objectsON_WM_CTLCOLOR_REFLECTReflects WM_CTLCOLOR messagesON_WM_DRAWITEM_REFLECTReflects WM_DRAWITEM messages sent by owner-draw controlsON_WM_MEASUREITEM_REFLECTReflects WM_MEASUREITEM messages sent by owner-draw controlsON_WM_COMPAREITEM_REFLECTReflects WM_COMPAREITEM messages sent by owner-draw controlsON_WM_DELETEITEM_REFLECTReflects WM_DELETEITEM messages sent by owner-draw controlsON_WM_CHARTOITEM_REFLECTReflects WM_CHARTOITEM messages sent by list boxesON_WM_VKEYTOITEM_REFLECTReflects WM_VKEYTOITEM messages sent by list boxesON_WM_HSCROLL_REFLECTReflects WM_HSCROLL messages sent by scroll barsON_WM_VSCROLL_REFLECTReflects WM_VSCROLL messages sent by scroll barsON_WM_PARENTNOTIFY_REFLECTReflects WM_PARENTNOTIFY messages

Suppose you want to write a list box class that responds to its own LBN_DBLCLK notifications by displaying a message box containing the text of the item that was double-clicked. In an SDK-style application, the list box's parent would have to process the notification message and pop up the message box. In an MFC application, the list box can handle the notification and display the message box itself. Here's a derived list box class that does just that:

 

The ON_CONTROL_REFLECT entry in the derived class's message map tells MFC to call CMyListBox::OnDoubleClick anytime the list box sends an LBN_DBLCLK notification to its parent. It's worth noting that the notification is reflected only if the parent doesn't process the notification itself—that is, if the parent's message map doesn't include an ON_LBN_DBLCLK entry for this list box. The parent receives precedence, which is consistent with the fact that Windows expects the parent to process any notifications in which it is interested.

 

原创粉丝点击