再次强调,duilib只不过是一种思想!
在上一节中,我剖析了duilib中窗口类的注册,其中遗留两个小问题没有细说的?
第一个问题:过程函数中__WndProc()中有这么一小段代码:
pThis = reinterpret_cast<CWindowWnd*>(::GetWindowLongPtr(hWnd, GWLP_USERDATA)); if( uMsg == WM_NCDESTROY && pThis != NULL ) { LRESULT lRes = ::CallWindowProc(pThis->m_OldWndProc, hWnd, uMsg, wParam, lParam); ::SetWindowLongPtr(pThis->m_hWnd, GWLP_USERDATA, 0L); if( pThis->m_bSubclassed ) pThis->Unsubclass(); pThis->m_hWnd = NULL; pThis->OnFinalMessage(hWnd); return lRes; }
有这么一段代码,请看我分析一下流程,首先第一句,从USER_DATA中取出this指针,接着判断是否是WM_NCDESTROY消息,这段有什么用呢?众所周知,这个WM_NCDESTROY消息是Windows程序退出时所发送的最后一个消息,那么拦截这个消息就是留给我们一个清场的机会,看到pThis->OnFinalMessage(hWnd)没?这个函数就是个虚函数,我们可以重写它,从而处理程序退出时应该做的后续工作,比如资源释放等.
第二个问题:就是我上一节提到的duilib另外一套处理消息的机制
接着看过程函数__WndProc()的代码:
LRESULT CALLBACK CWindowWnd::__WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { CWindowWnd* pThis = NULL; if( uMsg == WM_NCCREATE ) { LPCREATESTRUCT lpcs = reinterpret_cast<LPCREATESTRUCT>(lParam); pThis = static_cast<CWindowWnd*>(lpcs->lpCreateParams); pThis->m_hWnd = hWnd; ::SetWindowLongPtr(hWnd, GWLP_USERDATA, reinterpret_cast<LPARAM>(pThis)); } else { pThis = reinterpret_cast<CWindowWnd*>(::GetWindowLongPtr(hWnd, GWLP_USERDATA)); if( uMsg == WM_NCDESTROY && pThis != NULL ) { LRESULT lRes = ::CallWindowProc(pThis->m_OldWndProc, hWnd, uMsg, wParam, lParam); ::SetWindowLongPtr(pThis->m_hWnd, GWLP_USERDATA, 0L); if( pThis->m_bSubclassed ) pThis->Unsubclass(); pThis->m_hWnd = NULL; pThis->OnFinalMessage(hWnd); return lRes; } } if( pThis != NULL ) { return pThis->HandleMessage(uMsg, wParam, lParam); } else { return ::DefWindowProc(hWnd, uMsg, wParam, lParam); } }
请看红色标记部分.
我之所以说,duilib采用的消息处理机制,和MFC稍微有些不同,是因为duilib的消息处理是在另一个类当中处理的,CPaintManagerUI 就是这个类!
那么duilib为什么要设计这样一个类来处理消息, 消息处理为什么不放在CWindowWnd类中直接处理,而要放在CPaintManagerUI类中?这些归功于duilib设计者们的良苦用心!为什么?
如果我们在不看duilib源码的基础上,也去设计一个这样的duilib,也许我们会这样设计:我们对消息的处理是在用户构建的类CDuiFrameWnd类(派生自CWindowWnd),为了在控件中可以实时刷新,那么每个控件都必须强制的保存一个变量---m_hWnd(主窗口句柄),而且,我们在每次处理消息时,都必然做一些重复性的工作(代码),那么我们将这些控件都具有的变量与操作都封装起来,这就是CPaintManagerUI类的由来!当然从名字看来,它主要处理的是绘图方面的消息,因为整个应用程序只有一个句柄(主窗口句柄),所以CPaintManagerUI作为管理者,承载了很多.看代码:
class CDuiFrameWnd : public CWindowWnd, public INotifyUI { public: virtual LPCTSTR GetWindowClassName() const { return _T("DUIMainFrame"); } virtual void Notify(TNotifyUI& msg) { if (msg.sType == _T("click")) { if (msg.pSender->GetName() == _T("btnClick")) { ::MessageBox(NULL, _T("我是按钮"), _T("点击了按钮"), NULL); } } } virtual LRESULT HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam) { LRESULT lRes = 0; if( uMsg == WM_CREATE ) { CControlUI *pWnd = new CButtonUI; pWnd->SetName(_T("btnClick")); pWnd->SetText(_T("My First DUI")); // 设置文字 pWnd->SetBkColor(0xFF808080); // 设置背景色 m_PaintManager.Init(m_hWnd); //主窗口句柄 m_PaintManager.AttachDialog(pWnd); m_PaintManager.AddNotifier(this); return lRes; } if( m_PaintManager.MessageHandler(uMsg, wParam, lParam, lRes) ) { return lRes; } return __super::HandleMessage(uMsg, wParam, lParam); } protected: CPaintManagerUI m_PaintManager; };
这就是我们的CDuiFrameWnd类,其中就有类CPaintManagerUI的对象m_PaintManager. 红色标记部分就是m_PaintManager处理绘图消息.如果没有被处理的其他消息,则给基类处理;
不过CPaintManager为我们处理了最重要的两个消息:命令消息 和 通知消息 ,请看代码:
case WM_NOTIFY: { LPNMHDR lpNMHDR = (LPNMHDR) lParam; if( lpNMHDR != NULL ) lRes = ::SendMessage(lpNMHDR->hwndFrom, OCM__BASE + uMsg, wParam, lParam); return true; } break; case WM_COMMAND: { if( lParam == 0 ) break; HWND hWndChild = (HWND) lParam; lRes = ::SendMessage(hWndChild, OCM__BASE + uMsg, wParam, lParam); return true; }
从代码上看,SendMessage()中的参数和MFC源码中是如出一辙,没什么两样,话不多说,上图,正所谓源码面前,了无秘语--j.j.hou
LRESULT CWnd::WindowProc(UINT message, WPARAM wParam, LPARAM lParam) { // OnWndMsg does most of the work, except for DefWindowProc call LRESULT lResult = 0; if (!OnWndMsg(message, wParam, lParam, &lResult)) lResult = DefWindowProc(message, wParam, lParam); return lResult; }
BOOL CWnd::OnWndMsg(UINT message, WPARAM wParam, LPARAM lParam, LRESULT* pResult) { LRESULT lResult = 0; union MessageMapFunctions mmf; mmf.pfn = 0; CInternalGlobalLock winMsgLock; // special case for commands if (message == WM_COMMAND) { if (OnCommand(wParam, lParam)) { lResult = 1; goto LReturnTrue; } return FALSE; } // special case for notifies if (message == WM_NOTIFY) { NMHDR* pNMHDR = (NMHDR*)lParam; if (pNMHDR->hwndFrom != NULL && OnNotify(wParam, lParam, &lResult)) goto LReturnTrue; return FALSE; } ...//代码太多,请自己查看余下的部分,这里只挑出我要讲的部分,代码在wincore.cpp中 }
接着看
BOOL CWnd::OnCommand(WPARAM wParam, LPARAM lParam) // return TRUE if command invocation was attempted { UINT nID = LOWORD(wParam); HWND hWndCtrl = (HWND)lParam; int nCode = HIWORD(wParam); // default routing for command messages (through closure table) if (hWndCtrl == NULL) { // zero IDs for normal commands are not allowed if (nID == 0) return FALSE; // make sure command has not become disabled before routing CTestCmdUI state; state.m_nID = nID; OnCmdMsg(nID, CN_UPDATE_COMMAND_UI, &state, NULL); if (!state.m_bEnabled) { TRACE(traceAppMsg, 0, _T("Warning: not executing disabled command %d "), nID); return TRUE; } // menu or accelerator nCode = CN_COMMAND; } else //请注意这里的控件反射消息 { // control notification ASSERT(nID == 0 || ::IsWindow(hWndCtrl)); if (_afxThreadState->m_hLockoutNotifyWindow == m_hWnd) return TRUE; // locked out - ignore control notification // reflect notification to child window control if (ReflectLastMsg(hWndCtrl)) return TRUE; // eaten by child // zero IDs for normal commands are not allowed if (nID == 0) return FALSE; } #ifdef _DEBUG if (nCode < 0 && nCode != (int)0x8000) TRACE(traceAppMsg, 0, _T("Implementation Warning: control notification = $%X. "), nCode); #endif return OnCmdMsg(nID, nCode, NULL, NULL); }
再看通知消息的处理
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 if (ReflectLastMsg(hWndCtrl, pResult)) return TRUE; // eaten by child AFX_NOTIFY notify; notify.pResult = pResult; notify.pNMHDR = pNMHDR; return OnCmdMsg((UINT)nID, MAKELONG(nCode, WM_NOTIFY), ¬ify, NULL); }
我复制了这么多代码干嘛?我想让大家引起注意的是我用蓝色加粗部分的ReflectLastMsg(hWndCtrl, pResult)函数,别着急,接着往下看:
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) CWnd* pWnd = (CWnd*)pMap->LookupPermanent(hWndChild); 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 pWnd->SendChildNotifyLastMsg(pResult); }
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); }
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 ReflectChildNotify(uMsg, wParam, lParam, pResult); }
BOOL CWnd::ReflectChildNotify(UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT* pResult) { // 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) switch (uMsg) { // normal messages (just wParam, lParam through OnWndMsg) case WM_HSCROLL: case WM_VSCROLL: #ifndef _WIN32_WCE case WM_PARENTNOTIFY: #endif // !_WIN32_WCE 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); // 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 WM_NOTIFY: { // 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 CWnd::OnCmdMsg(0, MAKELONG(nCode, WM_REFLECT_BASE+WM_NOTIFY), ¬ify, NULL); } // 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 }
好了,我们终于到了最核心的部分,看到我用红色标记的部分没,这两个case就是WM_COMMAND和WM_NOTIFY消息的反射处理,我们可以看到MAKELONG(nCode, WM_REFLECT_BASE+WM_NOTIFY)..是不是和duilib的如出一辙?
这些消息最终会流向基类CCmdTarget去查询消息映射表!
反射消息处理路线,暂时先到这吧,东拉西扯了这么多,呵呵!