消息循环小探:
在程序中如果想要处理某消息,可以在GetMessage中截获后处理,当然也可以在窗口过程中直接处理。这里要注意的是,有的事件根本没有进入消息循环,而是直接发送到了窗口过程,比如点击窗口关闭时,在GetMessage里就截获不到消息,在窗口过程里才能收到。所以这就是为什么一个窗口类继承于CWindowWnd必须实现HandleMessage函数并且处理相关消息并PostQuitMessage,否则GetMessage不会收到WM_QUIT消息,从而造成程序没有正常退出。
程序消息流程
我们都知道消息一般要发送到窗口的消息队列中(也有直接分派到窗口过程中去的),利用GetMessageMap从中获取消息,然后再分派到窗口过程中去。duilib为我们在消息分派到具体的窗口过程函数之前,也可以进行相应的处理。一般用来先处理键盘按下等消息。在函数CPaintManagerUI::TranslateMessage(&msg)中 处理。其思想如下:
CPaintManagerUI有一个m_aPreMessageFilters容器,只要窗口类继承于IMessageFilterUI,m_aPreMessageFilters就可以添加监听该类,一般在WM_CREATE消息时操作:m_PaintManager.AddPreMessageFilter(this)。该类实现了MessageHandler接口就可以预处理消息,而不经过窗口过程。其余的消息则发往他们的窗口过程。
在程序窗口创建时,注册了窗口的函数过程。该窗口过程如下:
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);}}所以任何消息的处理函数在该类的HandleMessage中。而HandleMessage函数又是这么实现的:LRESULT WindowImplBase::HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam){LRESULT lRes = 0;BOOL bHandled = TRUE;switch (uMsg){case WM_CREATE: lRes = OnCreate(uMsg, wParam, lParam, bHandled); break;case WM_CLOSE: lRes = OnClose(uMsg, wParam, lParam, bHandled); break;case WM_DESTROY: lRes = OnDestroy(uMsg, wParam, lParam, bHandled); break;#if defined(WIN32) && !defined(UNDER_CE)case WM_NCACTIVATE: lRes = OnNcActivate(uMsg, wParam, lParam, bHandled); break;case WM_NCCALCSIZE: lRes = OnNcCalcSize(uMsg, wParam, lParam, bHandled); break;case WM_NCPAINT: lRes = OnNcPaint(uMsg, wParam, lParam, bHandled); break;case WM_NCHITTEST: lRes = OnNcHitTest(uMsg, wParam, lParam, bHandled); break;case WM_GETMINMAXINFO: lRes = OnGetMinMaxInfo(uMsg, wParam, lParam, bHandled); break;case WM_MOUSEWHEEL: lRes = OnMouseWheel(uMsg, wParam, lParam, bHandled); break;#endifcase WM_SIZE: lRes = OnSize(uMsg, wParam, lParam, bHandled); break;case WM_CHAR: lRes = OnChar(uMsg, wParam, lParam, bHandled); break;case WM_SYSCOMMAND: lRes = OnSysCommand(uMsg, wParam, lParam, bHandled); break;case WM_KEYDOWN: lRes = OnKeyDown(uMsg, wParam, lParam, bHandled); break;case WM_KILLFOCUS: lRes = OnKillFocus(uMsg, wParam, lParam, bHandled); break;case WM_SETFOCUS: lRes = OnSetFocus(uMsg, wParam, lParam, bHandled); break;case WM_LBUTTONUP: lRes = OnLButtonUp(uMsg, wParam, lParam, bHandled); break;case WM_LBUTTONDOWN: lRes = OnLButtonDown(uMsg, wParam, lParam, bHandled); break;case WM_MOUSEMOVE: lRes = OnMouseMove(uMsg, wParam, lParam, bHandled); break;case WM_MOUSEHOVER: lRes = OnMouseHover(uMsg, wParam, lParam, bHandled); break;default: bHandled = FALSE; break;}if (bHandled) return lRes;lRes = HandleCustomMessage(uMsg, wParam, lParam, bHandled);if (bHandled) return lRes;if (m_PaintManager.MessageHandler(uMsg, wParam, lParam, lRes))return lRes;return CWindowWnd::HandleMessage(uMsg, wParam, lParam);}所以可以在WindowImplBase的子类的HandleMessage中先截获相关消息。那程序中的控件又是怎么对相关消息产生响应的?或者说点击一个button应该如何去响应的地方实现?这一切都是CPaintManagerUI的功劳。看上面代码:if (m_PaintManager.MessageHandler(uMsg, wParam, lParam, lRes))return lRes;在这里,程序作出了反应,进而推动整个程序。m_PaintManager.MessageHandler是整个框架的关键所在,而且细节也是相当的复杂。下面从两个重要的方面讨论。
①界面的显示
duilib的核心就是美化界面。显示绚丽界面的代码也在该函数中实现。和window SDK程序一样也要通过该消息来显示 WM_PAINT。由于细节比较复杂,咱只讲大体思路。整个程序界面和控件都是一个容器,容器又负责里面的子控件绘制,从而得到整个漂亮的界面。
②控件消息的响应
其实,duilib没有什么真正的控件,每个控件都是‘画’出来的,不像SDK和MFC里的控件都对应一个窗口类,每个类又有其窗口函数,duilib只有主界面一个HWND,所有的诸如点击按钮等消息都是他在接收处理。而控件的消息无外乎鼠标点击,鼠标Hover等等消息。鼠标点击或Hover时通过坐标找到相应的控件,然后模拟控件对事件的响应,这就是duilib处理控件的方法。由于其中涉及到一些控件具体的实现,而我小菜还没得及仔细分析,下次有空再表。