在父窗口收到子控件的WM_NOTIFY通知消息后的处理流程,MFC源代码如下:
BOOL CWnd::OnNotify(WPARAM, LPARAM lParam, LRESULT* pResult)
{
ASSERT(pResult != NULL);
NMHDR* pNMHDR = (NMHDR*)lParam;
HWND hWndCtrl = pNMHDR->hwndFrom;
// get the child ID from the window itself
UINT_PTR nID = _AfxGetDlgCtrlID(hWndCtrl);
int nCode = pNMHDR->code;
ASSERT(hWndCtrl != NULL);
ASSERT(::IsWindow(hWndCtrl));
if (_afxThreadState->m_hLockoutNotifyWindow == m_hWnd)
return TRUE; // locked out - ignore control notification
// reflect notification to child window control
<span style="background-color: rgb(255, 102, 102);">if (ReflectLastMsg(hWndCtrl, pResult))</span>
return TRUE; // eaten by child
AFX_NOTIFY notify;
notify.pResult = pResult;
notify.pNMHDR = pNMHDR;
return <span style="background-color: rgb(255, 102, 102);">OnCmdMsg((UINT)nID, MAKELONG(nCode, WM_NOTIFY), ¬ify, NULL);</span>
}
从上述代码可以看出WM_NOTIFY的默认的流动方向是
先反射给产生这个WM_NOTIFY消息的子控件。如果子控件窗口不处理此消息,然后才是本窗口(父窗口)处理。
下面再看看
<span style="background-color: rgb(255, 102, 102);">ReflectLastMsg(hWndCtrl, pResult)</span>
的源代码:
BOOL PASCAL CWnd::ReflectLastMsg(HWND hWndChild, LRESULT* pResult)
{
// get the map, and if no map, then this message does not need reflection
CHandleMap* pMap = afxMapHWND();
if (pMap == NULL)
return FALSE;
// check if in permanent map, if it is reflect it (could be OLE control)
<span style="background-color: rgb(255, 102, 102);">CWnd* pWnd = (CWnd*)pMap->LookupPermanent(hWndChild);</span>
ASSERT(pWnd == NULL || pWnd->m_hWnd == hWndChild);
if (pWnd == NULL)
{
#ifndef _AFX_NO_OCC_SUPPORT
// check if the window is an OLE control
CWnd* pWndParent = (CWnd*)pMap->LookupPermanent(::GetParent(hWndChild));
if (pWndParent != NULL && pWndParent->m_pCtrlCont != NULL)
{
// If a matching control site exists, it's an OLE control
COleControlSite* pSite = (COleControlSite*)pWndParent->
m_pCtrlCont->m_siteMap.GetValueAt(hWndChild);
if (pSite != NULL)
{
CWnd wndTemp(hWndChild);
wndTemp.m_pCtrlSite = pSite;
LRESULT lResult = wndTemp.SendChildNotifyLastMsg(pResult);
wndTemp.m_hWnd = NULL;
return lResult != 0;
}
}
#endif //!_AFX_NO_OCC_SUPPORT
return FALSE;
}
// only OLE controls and permanent windows will get reflected msgs
ASSERT(pWnd != NULL);
return <span style="background-color: rgb(255, 102, 102);">pWnd->SendChildNotifyLastMsg(pResult);</span>
}
从上述源代码可以看出来,先是取得产生WM_NOTIFY消息的子控件的对象指针。然后再调用子控件的函数SendChildNotifyLastMsg()。
下面再看看SendChildNotifyLastMsg()函数的源代码:
BOOL CWnd::SendChildNotifyLastMsg(LRESULT* pResult)
{
_AFX_THREAD_STATE* pThreadState = _afxThreadState.GetData();
return <span style="background-color: rgb(255, 102, 102);">OnChildNotify(pThreadState->m_lastSentMsg.message,
pThreadState->m_lastSentMsg.wParam, pThreadState->m_lastSentMsg.lParam, pResult);</span>
}
从上述代码可以看出子控件调用了自己的OnChildNotify()这个虚函数。这个函数可以被各个控件覆盖(override),这样就可以自己定义对这个反射回来的WM_NOTIFY消息做什么处理了。
如CButton标准控件的OnChildNotify()函数实现如下:
BOOL CButton::OnChildNotify(UINT message, WPARAM wParam, LPARAM lParam,
LRESULT* pResult)
{
<span style="background-color: rgb(255, 102, 102);">if (message != WM_DRAWITEM)
return CWnd::OnChildNotify(message, wParam, lParam, pResult);</span>
ASSERT(pResult == NULL); // no return value expected
UNUSED(pResult); // unused in release builds
<span style="background-color: rgb(255, 102, 102);">DrawItem((LPDRAWITEMSTRUCT)lParam);</span>
return TRUE;
}
从上述代码可以看出父窗口把WM_DRAWITEM消息也给反射回子控件了。CButton标准控件对反射回来的WM_DRAWITEM消息做了特殊的自定义的处理,调用了DrawItem()虚函数,来进行对CButton的控件自身重画。这就是为什么子控件重画可以不需要在父窗口的OnDrawItem()消息函数中处理了,可以直接就在自己的虚函数里来处理了。这样子的话控件就更具有独立性了。我们要是自己做的控件也可以学习上面CButton对某一个反射消息的特殊处理方式。
再看看DrawItem()虚函数的源代码:
// Derived class is responsible for implementing all of these handlers
// for owner/self draw controls
void CButton::DrawItem(LPDRAWITEMSTRUCT)
{
<span style="background-color: rgb(255, 102, 102);">ASSERT(FALSE);</span>
}
上面的代码说明了,如果子控件设置了自画属性的话,要么1.就在父窗口的OnDrawItem()函数里画完,返回TRUE,就不要再调用CWnd::OnDrawItem()把WM_DRAWITEM消息再反射回子控件了。要么2.不再父窗口里的OnDrawItem()函数里自画,而是覆盖(override)DrawItem()虚函数在自己的DrawItem()函数里自画。否则的话,不覆盖(override)DrawItem()函数,但是又让WM_DRAWITEM消息反射回子控件的话,就会报错(在Debug模式下)。
如果对反射的消息不做特殊的处理,源代码如下:
BOOL CWnd::OnChildNotify(UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT* pResult)
{
#ifndef _AFX_NO_OCC_SUPPORT
if (m_pCtrlSite != NULL)
{
// first forward raw OCM_ messages to OLE control sources
LRESULT lResult = SendMessage(OCM__BASE+uMsg, wParam, lParam);
if (uMsg >= WM_CTLCOLORMSGBOX && uMsg <= WM_CTLCOLORSTATIC &&
(HBRUSH)lResult == NULL)
{
// for WM_CTLCOLOR msgs, returning NULL implies continue routing
return FALSE;
}
if (pResult != NULL)
*pResult = lResult;
return TRUE;
}
#endif
return <span style="background-color: rgb(255, 102, 102);">ReflectChildNotify(uMsg, wParam, lParam, pResult);</span>
}
ReflectChildNotify()源代码如下:
BOOL CWnd::ReflectChildNotify(UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT* pResult)
{
<span style="background-color: rgb(255, 102, 102);">// Note: reflected messages are send directly to CWnd::OnWndMsg
// and CWnd::OnCmdMsg for speed and because these messages are not
// routed by normal OnCmdMsg routing (they are only dispatched)</span>
switch (uMsg)
{
// normal messages (just wParam, lParam through OnWndMsg)
case WM_HSCROLL:
case WM_VSCROLL:
case WM_PARENTNOTIFY:
case <span style="background-color: rgb(255, 102, 102);">WM_DRAWITEM:</span>
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 <span style="background-color: rgb(255, 102, 102);">CWnd::OnWndMsg(WM_REFLECT_BASE+uMsg, wParam, lParam, pResult);</span>
// special case for WM_COMMAND
case WM_COMMAND:
{
// reflect the message through the message map as OCM_COMMAND
int nCode = HIWORD(wParam);
if (CWnd::OnCmdMsg(0, MAKELONG(nCode, WM_REFLECT_BASE+WM_COMMAND), NULL, NULL))
{
if (pResult != NULL)
*pResult = 1;
return TRUE;
}
}
break;
// special case for WM_NOTIFY
case <span style="background-color: rgb(255, 102, 102);">WM_NOTIFY:</span>
{
// reflect the message through the message map as OCM_NOTIFY
NMHDR* pNMHDR = (NMHDR*)lParam;
int nCode = pNMHDR->code;
AFX_NOTIFY notify;
notify.pResult = pResult;
notify.pNMHDR = pNMHDR;
return <span style="background-color: rgb(255, 102, 102);">CWnd::OnCmdMsg(0, MAKELONG(nCode, WM_REFLECT_BASE+WM_NOTIFY), ¬ify, NULL);</span>
}
// other special cases (WM_CTLCOLOR family)
default:
if (uMsg >= WM_CTLCOLORMSGBOX && uMsg <= WM_CTLCOLORSTATIC)
{
// fill in special struct for compatiblity with 16-bit WM_CTLCOLOR
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;
}
break;
}
return FALSE; // let the parent handle it
}
从上述代码可以看出来,默认的反射消息最后是直接交给了CWnd::OnWndMsg()和CWnd::OnCmdMsg()。这样是为了提高消息处理速度,同时也是不需要再对反射消息路由了。因为反射消息的终点就是子控件本身。如果子控件要处理反射消息的话,就要在子控件的消息映射表里添加ON_NOTIFY_REFLECT(wNotifyCode, memberFxn)宏来定义反射消息的消息处理函数(针对WM_NOTIFY+WM_REFLECT_BASE反射消息)。
如果子控件不处理反射消息,那么WM_NOTIFY就在父窗口处理了,(反正是给了子控件机会处理的)。