让MFC消息反射机制原型毕露-------(Message Reflection)

来源:互联网 发布:美工抠图总结 编辑:程序博客网 时间:2024/05/01 05:34

        绪言

         我想现在我可以写一遍和别人不一样的关于探讨MFC消息反射机制的文章了。

       其实,在网上有很多关探讨MFC消息反射机制的文章,但是大都是把MSDN中的TN062那篇文章翻译一下或者把<<Defining a Message Handler for a Reflected Message>>这篇文章的操作重复一篇。对于那些想刨根问底的人来说,这显然是不够的。而这篇文章将会为那些渴望知道MFC消息反射机制底层实现细节的人带来福音。

     消息反射机制要解决什么问题呢?

        消息反射机制主要是为了控件而实现的。每当控件需要某些资讯(比如,绘制自身背景的画刷,显示字体的颜色等等)时,都会频繁地向其父窗口发送通告消息(notification message),比如最常见的WM_CTLCOLOR通告消息,让父窗口为它提供各种服务。当子控件很多时,父窗口很会变得臃肿不堪,而子控件却没事可做。另一方面,假如我们想继续创建一个相同风格的控件时,必须在父类中重复相同的代码。前者让我们程序不够健壮,父窗口可能累死;后者不利于代码的重用,很可能把程序员给累死了。显然,这不是面向对象程序设计的思维。如果我们在子控件对应的类中处理自身的通告消息,父窗口就不需要为子控件而操心了,子控件处理自己的事情。既解决前面两个问题,两个模块的耦合度也小了。

哪些消息可以被反射?

        能够被反射都是控件发送给父窗口的通告消息notification message, 从MFC的源代码中我们可以得知,能够被反射的消息总共有12中,根据MFC对它们不同的包装方式,我将它们分为四类。 

反射消息表第一类WM_COMMAND第二类

WM_NOTIFY

第三类

WM_CTLCOLOR

第四

WM_VSCROLL,WM_HSCROLL, WM_DRAWITEM, WM_MEASUREITEM,WM_COMPAREITEM,WM_DELETEITEM, WM_CHARTOITEM,WM_VKEYTOITEM,WM_PARENTNOTIFY

消息反射机制具体是如何实现的呢?

         事实上,对于上面的每一种消息,子控件的父窗口对应的类中都已为其添加了消息处理。每当控件向发父窗口发送一个通告消息时,都会首先由父窗口中对应的消息处理函数接手,请注意,父窗口中的消息处理函数只是接管这个消息,并没有实际处理它。父窗口中的消息处理函数首先将其“反射”给子控件,看子控件是否处理了这个反射消息。如果子控件处理了该反射消息并且返回TRUE,则消息终结,否则,交由父窗口处理。但是,对于上面的四种反射消息,MFC对它们实现反射的细节还是不一样的。具体的请看下面的讲解,我会尽量贴出源代码,因为源代码才是最有说服力和最有感觉的。

        因为控件通常要发很多通告notification给父窗口,这些通告notification分别携带了不同数据量的additional information额外数据,为了便于处理,windows根据额外数据量additional information的区别将其包装成了上面的12种。

第一类:WM_COMMAND的反射消息

       有一类通告,它们只需要携带通告代码(notification code),控件标识符(identifier)和控件句柄hwnd,它们分别保存在wParam的高字节,wParam低字节,lParam中,Windows将其打包成WM_COMMAND消息。 

      当鼠标点击子控件时,子控件向父窗口发送WM_COMMAND消息,父窗口首先调用一个虚函数OnCommand处理,MFC源代码如下:

BOOL CWnd::OnWndMsg(UINT message, WPARAM wParam, LPARAM lParam, LRESULT* pResult)
{
      ......

 if (message == WM_COMMAND)
 {
  if (OnCommand(wParam, lParam))
  {
   lResult = 1;
   goto LReturnTrue;
  }
      ......

 }

 OnCommand首先将其反射给控件自身,如果ReflectLastMsg返回false,子控件没有处理该反射消息,就调用OnCmdMsg,让父窗口处理。mfc源代码如下:

BOOL CWnd::OnCommand(WPARAM wParam, LPARAM lParam)
{            ......

 if (ReflectLastMsg(hWndCtrl))//首先给控件一个机会处理它
   return TRUE;   

              ......

 return OnCmdMsg(nID, nCode, NULL, NULL);//若控件没有处理则由父窗口处理

}

由于代码太多,我并没有具体列出调用的每个函数,而只是将其展开,贴出关键的代码,请下载附带的源程序demo_msgreflection,里面的代码断点都设置好了,调试可以看到具体的细节。

BOOL PASCAL CWnd::ReflectLastMsg(HWND hWndChild, LRESULT* pResult)
{

             ...................

 if (CWnd::OnCmdMsg(0, MAKELONG(nCode,WM_REFLECT_BASE+WM_COMMAND), NULL, NULL))//消息反射原型

{
    if (pResult != NULL)
     *pResult = 1;
    return TRUE;
   }

          ....................

} 

第二类:WM_NOTIFY的反射消息

           事实上,MFC对WM_CONOTIFY 和WM_COMMAND消息的处理非常类似,这里我就不详解,只贴出代码,大家可参照上面。

BOOL CWnd::OnWndMsg(UINT message, WPARAM wParam, LPARAM lParam, LRESULT* pResult)
{
 LRESULT lResult = 0;

      ...............

  if (message == WM_NOTIFY)
 {
  NMHDR* pNMHDR = (NMHDR*)lParam;
  if (pNMHDR->hwndFrom != NULL && OnNotify(wParam, lParam, &lResult))//OnNotify专门处理WM_NOTIFY消息
   goto LReturnTrue;
  return FALSE;

     ................
 }

BOOL CWnd::OnNotify(WPARAM, LPARAM lParam, LRESULT* pResult)
{
     ...................

 if (ReflectLastMsg(hWndCtrl, pResult))//反射给控件,让控件首先有机会处理它
  return TRUE;     

     ....................

  return OnCmdMsg(nID, MAKELONG(nCode, WM_NOTIFY), &notify, NULL);//控件没有处理,有父窗口处理
}

 

BOOL PASCAL CWnd::ReflectLastMsg(HWND hWndChild, LRESULT* pResult)
{

           ................

 return CWnd::OnCmdMsg(0, MAKELONG(nCode,WM_REFLECT_BASE+WM_NOTIFY), &notify, NULL);//消息反射原型

         .....................

}

第三类:WM_CTLCOLOR的反射消息

        事实上,32位系统里面的WM_CTLCOLOR消息是被MFC包装出来的,只是为了16位的系统兼容。在32位系统中,当控件需要画刷绘制背景时,向父窗口发送的消息是WM_CTLCOLORSTATIC,   WM_CTLCOLOREDIT,    WM_CTLCOLORBTN,  WM_CTLCOLORLISTBOX, WM_CTLCOLORDLG, WM_COLORMSGBOX, WM_CTLCOLORSCROLLBAR.  我在上面说过,mfc在父窗口中会首先接管这些通告消息,请看下面的message map,当静态文本框向父窗口发送WM_CTLCOLORSTATIC后,首先是被父窗口中OnNtCtlColor函数处理,我们跟着这个函数,揭开WM_CTLCOLOR这个消息的神秘面纱。

BEGIN_MESSAGE_MAP(CWnd, CCmdTarget)
 ON_MESSAGE(WM_CTLCOLORSTATIC, OnNTCtlColor)
 ON_MESSAGE(WM_CTLCOLOREDIT, OnNTCtlColor)
 ON_MESSAGE(WM_CTLCOLORBTN, OnNTCtlColor)
 ON_MESSAGE(WM_CTLCOLORLISTBOX, OnNTCtlColor)
 ON_MESSAGE(WM_CTLCOLORDLG, OnNTCtlColor)
 ON_MESSAGE(WM_CTLCOLORMSGBOX, OnNTCtlColor)
 ON_MESSAGE(WM_CTLCOLORSCROLLBAR, OnNTCtlColor)

 

OnNtCtlColor只是接管这个消息,他将WM_CTLCOLORSTATIC消息变换为WM_CTLCOLOR传给了WindowProc,但还是传给了父窗口

LRESULT CWnd::OnNTCtlColor(WPARAM wParam, LPARAM lParam)
{
    ....................

 return WindowProc(WM_CTLCOLOR, 0, (LPARAM)&ctl);        //在此处WM_CTLCOLORSTATIC被包装成了WM_CTLCOLOR传递给父窗口
}

父窗口调用CDialog::OnCtlColor函数处理, 该函数又调用OnGrayCtlColor

HBRUSH CDialog::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
{
  return OnGrayCtlColor(pDC, pWnd, nCtlColor);
}

OnGrayCtlColor将消息反射给控件处理,如果控件处理了,消息终结,否则传回父窗口,默认处理

HBRUSH CWnd::OnGrayCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
{
 LRESULT lResult;
 if (pWnd->SendChildNotifyLastMsg(&lResult))//将消息反射给子控件,让子控件首先有机会处理它
  return (HBRUSH)lResult;   

 _AFX_WIN_STATE* pWinState = _afxWinState;

//如果控件没有处理反射消息,有父窗口处理
 if (!GrayCtlColor(pDC->m_hDC, pWnd->GetSafeHwnd(), nCtlColor,pWinState->m_hDlgBkBrush, pWinState->m_crDlgTextClr))
                              return (HBRUSH)Default();
 return pWinState->m_hDlgBkBrush;

}

SendChildNotifyLastMsg调用了OnChildNotify

BOOL CWnd::SendChildNotifyLastMsg(LRESULT* pResult)
{
 _AFX_THREAD_STATE* pThreadState = _afxThreadState.GetData();
 return OnChildNotify(pThreadState->m_lastSentMsg.message,pThreadState->m_lastSentMsg.wParam, pThreadState->m_lastSentMsg.lParam, pResult);
}

OnChildNotify调用ReflectChildNotify

BOOL CWnd::OnChildNotify(UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT* pResult)
{

    ................
  return ReflectChildNotify(uMsg, wParam, lParam, pResult);
}

 

ReflectChildNotify消息反射消息给控件

BOOL CWnd::ReflectChildNotify(UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT* pResult)
{

 if (uMsg >= WM_CTLCOLORMSGBOX && uMsg <= WM_CTLCOLORSTATIC)
  {
    AFX_CTLCOLOR ctl;
   ctl.hDC = (HDC)wParam;
   ctl.nCtlType = uMsg - WM_CTLCOLORMSGBOX;
   ASSERT(ctl.nCtlType >= CTLCOLOR_MSGBOX);
   ASSERT(ctl.nCtlType <= CTLCOLOR_STATIC);

   // reflect the message through the message map as OCM_CTLCOLOR
   
BOOL bResult = CWnd::OnWndMsg(WM_REFLECT_BASE+WM_CTLCOLOR, 0, (LPARAM)&ctl, pResult);
   if ((HBRUSH)*pResult == NULL)
    bResult = FALSE;
   return bResult;
  }

}

第四类(以WM_DRAWITEM为例)

        这类消息很的反射很简单,没有什么特别,只有WM_DRAWITE另辟蹊径,贴出关键的代码,大家都能懂了。这里有示范例子,点击下载:Demo1

        以WM_DRAWITEM,当父窗口收到WM_DRAWITEM消息后,首先调用父类为其添加的消息处理函数OnDrawItem,OnDrawItem接管这个消息调用子控件的ReflectLastMsg

void CWnd::OnDrawItem(int /*nIDCtl*/, LPDRAWITEMSTRUCT lpDrawItemStruct)
{

    ..............


   if (ReflectLastMsg(lpDrawItemStruct->hwndItem))//将消息反射给控件,让控件首先有机会处理它

    return; 

       ..............


      Default();            //如果控件没有处理它,就父窗口处理它
}

 下面的代码会揭示出我们熟知的DrawItem这个虚函数的内幕

ReflectLastMsg调用了SendChildNotifyLastMsg

BOOL PASCAL CWnd::ReflectLastMsg(HWND hWndChild, LRESULT* pResult)
{

  。。。。。。
  return pWnd->SendChildNotifyLastMsg(pResult);//中转站而已
}

 

SendChildNotifyLastMsg调用了CButton::OnChildNotify

BOOL CWnd::SendChildNotifyLastMsg(LRESULT* pResult)
{
 _AFX_THREAD_STATE* pThreadState = _afxThreadState.GetData();
 return OnChildNotify(pThreadState->m_lastSentMsg.message,pThreadState->m_lastSentMsg.wParam, pThreadState->m_lastSentMsg.lParam, pResult);
}

 

OnChildNotify对这九个消息进行分流,WM_DRAWITEM 流向DrawItem, 而其他的消息则流向 CWnd::OnChildNotify

BOOL CButton::OnChildNotify(UINT message, WPARAM wParam, LPARAM lParam,
 LRESULT* pResult)
{
 if (message != WM_DRAWITEM)
  return CWnd::OnChildNotify(message, wParam, lParam, pResult);

 ASSERT(pResult == NULL);       // no return value expected
 UNUSED(pResult); // unused in release builds
 DrawItem((LPDRAWITEMSTRUCT)lParam);
 return TRUE;
}

 

 CWnd::OnChildNotify调用了ReflectChildNotify

BOOL CWnd::OnChildNotify(UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT* pResult)
{
#ifndef _AFX_NO_OCC_SUPPORT
 if (m_pCtrlSite != NULL)
 {
    LRESULT lResult = SendMessage(OCM__BASE+uMsg, wParam, lParam);
  if (uMsg >= WM_CTLCOLORMSGBOX && uMsg <= WM_CTLCOLORSTATIC &&
   (HBRUSH)lResult == NULL)
  {
     return FALSE;
  }
  if (pResult != NULL)
   *pResult = lResult;
  return TRUE;
 }

 return ReflectChildNotify(uMsg, wParam, lParam, pResult);
}

 

 ReflectChildNotify则将其反射给子控件

BOOL CWnd::ReflectChildNotify(UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT* pResult)
{
  switch (uMsg)
 {
 case WM_HSCROLL:
 case WM_VSCROLL:
 case WM_PARENTNOTIFY:
 case WM_DRAWITEM:
 case WM_MEASUREITEM:
 case WM_DELETEITEM:
 case WM_VKEYTOITEM:
 case WM_CHARTOITEM:
 case WM_COMPAREITEM:
  // reflect the message through the message map as WM_REFLECT_BASE+uMsg
  return CWnd::OnWndMsg(WM_REFLECT_BASE+uMsg, wParam, lParam, pResult);//消息反射原型

}

        

              我已经进了我最大的努力去写这篇文章了,如果你对上面的代码感到陌生,感到害怕,我想你肯定还不清楚出MFC消息的传递路线,要真是这样的话请你看这篇文章《浅析Windows消息在MFC中的传递路线------Command Rounting》。对于WM_CTLCOLOR这个消息,如果你在父窗口提供了消息处理,并且返回非空画刷,子视窗中的处理就不会被调用。但是,对于WM_NOTIFY消息,不管你在父窗口是否为其提供消息处理函数,它总是先调用子视窗中对应的处理。关于这些细节,上面并没有提到,不过,如果你遇到了这方面的问题,请看msdn中TN062这边文章。

原创粉丝点击