Duilib消息路由
来源:互联网 发布:禁止在淘宝网上发布 编辑:程序博客网 时间:2024/06/08 10:40
本文若有不对之处,敬请指出。
我们知道win32窗口程序的基本流程:
注册窗口类 -> 创建窗口 —> 显示窗口 -> 消息循环 -> 消息流转
基于这个路线,我们来探索下duilib库的实现。
毋庸置疑,先看一例测试程序中的WinMain函数:CFrameWindowWnd* pFrame = new CFrameWindowWnd(); if( pFrame == NULL ) return 0; pFrame->Create(NULL, _T("测试"), UI_WNDSTYLE_FRAME, WS_EX_WINDOWEDGE); pFrame->ShowWindow(true); CPaintManagerUI::MessageLoop();
可以确定注册窗口类和创建窗口都在CFrameWindowWnd::Create中进行。其中CFrameWindowWnd的一个基类是CWindowWnd,进入到该类Create方法中:
HWND CWindowWnd::Create(HWND hwndParent, LPCTSTR pstrName, DWORD dwStyle, DWORD dwExStyle, int x, int y, int cx, int cy, HMENU hMenu)
{
if( GetSuperClassName() != NULL && !RegisterSuperclass() ) return NULL;
if( GetSuperClassName() == NULL && !RegisterWindowClass() ) return NULL;
m_hWnd = ::CreateWindowEx(dwExStyle, GetWindowClassName(), pstrName, dwStyle, x, y, cx, cy, hwndParent, hMenu, CPaintManagerUI::GetInstance(), this);
ASSERT(m_hWnd!=NULL);
return m_hWnd;
}
其中GetSuperClassName为虚函数,用于指定要子类化的窗口类。关于窗口子类化的处理此处不予关注。
如果不需要子类化,则调用RegisterWindowClass进行窗口类注册。进入该方法内,可以发现注册窗的口过程是CWindowWnd::__WndProc;
注册之后,调用API CreateWindowEx创建窗口。
创建窗口后,就该最关键的消息循环和消息路由了,here we go!
首先看看消息循环的处理CPaintManagerUI::MessageLoop:
[cpp] view plain copy
01.void CPaintManagerUI::MessageLoop()
02.{
03. MSG msg = { 0 };
04. while( ::GetMessage(&msg, NULL, 0, 0) ) {
05. if( !CPaintManagerUI::TranslateMessage(&msg) ) {
06. ::TranslateMessage(&msg);
07. ::DispatchMessage(&msg);
08. }
09. }
10.}
可以看出,任何消息都会经过CPaintManagerUI::TranslateMessage的过滤,如果该方法没有处理消息,才将消息路由给窗口过程。相当于MFC中的PreTranslateMessage。
然后我们看下CPaintManagerUI::TranslateMessage的实现:
[cpp] view plain copy
01.bool CPaintManagerUI::TranslateMessage(const LPMSG pMsg)
02.{
03. // Pretranslate Message takes care of system-wide messages, such as
04. // tabbing and shortcut key-combos. We'll look for all messages for
05. // each window and any child control attached.
06. HWND hwndParent = ::GetParent(pMsg->hwnd);
07. UINT uStyle = GetWindowStyle(pMsg->hwnd);
08. LRESULT lRes = 0;
09. for( int i = 0; i < m_aPreMessages.GetSize(); i++ ) {
10. CPaintManagerUI* pT = static_cast<CPaintManagerUI*>(m_aPreMessages[i]);
11. if( pMsg->hwnd == pT->GetPaintWindow()
12. || (hwndParent == pT->GetPaintWindow() && ((uStyle & WS_CHILD) != 0)) )
13. {
14. if( pT->PreMessageHandler(pMsg->message, pMsg->wParam, pMsg->lParam, lRes) ) return true;
15. }
16. }
17. return false;
18.}
m_aPreMessages 的定义为:static CStdPtrArray m_aPreMessages; 当创建一个主窗口时,m_aPreMessages一般会有插入动作,因此,此数组是所有相关窗口attach的CPaintManagerUI对象地址的集合,处理的是整个程序的全局的消息。当msg消息路由至此处时,如果该窗口是子窗口或者消息窗口本身,就调用PreMessageHandler处理,如果仍未得到处理,则返回false,传递给窗口注册的窗口消息处理。
接下来看看CPaintManagerUI::PreMessageHandler的实现:
[cpp] view plain copy
01.bool CPaintManagerUI::PreMessageHandler(UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT& /*lRes*/)
02.{
03. for( int i = 0; i < m_aPreMessageFilters.GetSize(); i++ )
04. {
05. bool bHandled = false;
06. LRESULT lResult = static_cast<IMessageFilterUI*>(m_aPreMessageFilters[i])->MessageHandler(uMsg, wParam, lParam, bHandled);
07. if( bHandled ) {
08. return true;
09. }
10. }
11. switch( uMsg ) {
12. case WM_KEYDOWN:
13. {
14. // Tabbing between controls
15. if( wParam == VK_TAB ) {
16. if( m_pFocus && m_pFocus->IsVisible() && m_pFocus->IsEnabled() && _tcsstr(m_pFocus->GetClass(), _T("RichEditUI")) != NULL ) {
17. if( static_cast<CRichEditUI*>(m_pFocus)->IsWantTab() ) return false;
18. }
19. SetNextTabControl(::GetKeyState(VK_SHIFT) >= 0);
20. return true;
21. }
22. }
23. break;
24. case WM_SYSCHAR:
25. {
26. // Handle ALT-shortcut key-combinations
27. FINDSHORTCUT fs = { 0 };
28. fs.ch = toupper((int)wParam);
29. CControlUI* pControl = m_pRoot->FindControl(__FindControlFromShortcut, &fs, UIFIND_ENABLED | UIFIND_ME_FIRST | UIFIND_TOP_FIRST);
30. if( pControl != NULL ) {
31. pControl->SetFocus();
32. pControl->Activate();
33. return true;
34. }
35. }
36. break;
37. case WM_SYSKEYDOWN:
38. {
39. if( m_pFocus != NULL ) {
40. TEventUI event = { 0 };
41. event.Type = UIEVENT_SYSKEY;
42. event.chKey = (TCHAR)wParam;
43. event.ptMouse = m_ptLastMousePos;
44. event.wKeyState = MapKeyState();
45. event.dwTimestamp = ::GetTickCount();
46. m_pFocus->Event(event);
47. }
48. }
49. break;
50. }
51. return false;
52.}
这里又新增了一个对象属性:m_aPreMessageFilters,是一个窗口类(派生CWindowWnd)对象的集合。同时该对象的类必须是IMessageFilterUI和CWindowWnd的子类。通过这个集合,app将消息推送到每一个需要预处理所关心的消息的窗口,即调用MessageHandler,此方法是我们要重载的重要方法之一,用于对消息传递给窗口过程之前的预处理。接下来的是duilib提供的预处理,switch块处理的是全局级消息,对其进行预处理,包括tab处理,alt组合键和系统按键消息。
必须要注意,CPaintManagerUI::MessageHandler为我们提供了多数消息的默认实现。
至此,消息的预处理阶段完成,回到CPaintManagerUI::MessageLoop,未处理的消息将传递给窗口过程:
[cpp] view plain copy
01.LRESULT CALLBACK CWindowWnd::__WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
02.{
03. CWindowWnd* pThis = NULL;
04. if( uMsg == WM_NCCREATE ) {
05. LPCREATESTRUCT lpcs = reinterpret_cast<LPCREATESTRUCT>(lParam);
06. pThis = static_cast<CWindowWnd*>(lpcs->lpCreateParams);
07. pThis->m_hWnd = hWnd;
08. ::SetWindowLongPtr(hWnd, GWLP_USERDATA, reinterpret_cast<LPARAM>(pThis));
09. }
10. else {
11. pThis = reinterpret_cast<CWindowWnd*>(::GetWindowLongPtr(hWnd, GWLP_USERDATA));
12. if( uMsg == WM_NCDESTROY && pThis != NULL ) {
13. LRESULT lRes = ::CallWindowProc(pThis->m_OldWndProc, hWnd, uMsg, wParam, lParam);
14. ::SetWindowLongPtr(pThis->m_hWnd, GWLP_USERDATA, 0L);
15. if( pThis->m_bSubclassed ) pThis->Unsubclass();
16. pThis->m_hWnd = NULL;
17. pThis->OnFinalMessage(hWnd);
18. return lRes;
19. }
20. }
21. if( pThis != NULL ) {
22. return pThis->HandleMessage(uMsg, wParam, lParam);
23. }
24. else {
25. return ::DefWindowProc(hWnd, uMsg, wParam, lParam);
26. }
27.}
此过程很简单,对窗口创建过程中的第一个消息WM_NCCREATE进行处理,对其窗口attach窗口对象地址。HandleMessage是一个虚函数,需要我们进行重载,此接口很重要,相当于MFC中的具体的一个窗口类的消息映射表,win32中的具体的窗口过程。
下面的代码展示了HandleMessage的典型实现:
[cpp] view plain copy
01.LRESULT HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam)
02. {
03. if( uMsg == WM_CREATE ) {
04. m_pm.Init(m_hWnd);
05. CControlUI *pButton = new CButtonUI;
06. pButton->SetName(_T("closebtn"));
07. pButton->SetBkColor(0xFFFF0000);
08. m_pm.AttachDialog(pButton);
09. m_pm.AddNotifier(this);
10. return 0;
11. }
12. else if( uMsg == WM_DESTROY ) {
13. ::PostQuitMessage(0);
14. }
15. else if( uMsg == WM_NCACTIVATE ) {
16. if( !::IsIconic(m_hWnd) ) {
17. return (wParam == 0) ? TRUE : FALSE;
18. }
19. }
20. else if( uMsg == WM_NCCALCSIZE ) {
21. return 0;
22. }
23. else if( uMsg == WM_NCPAINT ) {
24. return 0;
25. }
26.
27. LRESULT lRes = 0;
28. if( m_pm.MessageHandler(uMsg, wParam, lParam, lRes) ) return lRes;
29. return CWindowWnd::HandleMessage(uMsg, wParam, lParam);
30. }
至此,消息路由大体就说完了,有兴趣的可以画个图,一目了然。
本文若有不对之处,敬请指出。
我们知道win32窗口程序的基本流程:
注册窗口类 -> 创建窗口 —> 显示窗口 -> 消息循环 -> 消息流转
基于这个路线,我们来探索下duilib库的实现。
毋庸置疑,先看一例测试程序中的WinMain函数:
- CFrameWindowWnd* pFrame = new CFrameWindowWnd();
- if( pFrame == NULL ) return 0;
- pFrame->Create(NULL, _T("测试"), UI_WNDSTYLE_FRAME, WS_EX_WINDOWEDGE);
- pFrame->ShowWindow(true);
- CPaintManagerUI::MessageLoop();
可以确定注册窗口类和创建窗口都在CFrameWindowWnd::Create中进行。其中CFrameWindowWnd的一个基类是CWindowWnd,进入到该类Create方法中:
- HWND CWindowWnd::Create(HWND hwndParent, LPCTSTR pstrName, DWORD dwStyle, DWORD dwExStyle, int x, int y, int cx, int cy, HMENU hMenu)
- {
- if( GetSuperClassName() != NULL && !RegisterSuperclass() ) return NULL;
- if( GetSuperClassName() == NULL && !RegisterWindowClass() ) return NULL;
- m_hWnd = ::CreateWindowEx(dwExStyle, GetWindowClassName(), pstrName, dwStyle, x, y, cx, cy, hwndParent, hMenu, CPaintManagerUI::GetInstance(), this);
- ASSERT(m_hWnd!=NULL);
- return m_hWnd;
- }
其中GetSuperClassName为虚函数,用于指定要子类化的窗口类。关于窗口子类化的处理此处不予关注。
如果不需要子类化,则调用RegisterWindowClass进行窗口类注册。进入该方法内,可以发现注册窗的口过程是CWindowWnd::__WndProc;
注册之后,调用API CreateWindowEx创建窗口。
创建窗口后,就该最关键的消息循环和消息路由了,here we go!
首先看看消息循环的处理CPaintManagerUI::MessageLoop:
- void CPaintManagerUI::MessageLoop()
- {
- MSG msg = { 0 };
- while( ::GetMessage(&msg, NULL, 0, 0) ) {
- if( !CPaintManagerUI::TranslateMessage(&msg) ) {
- ::TranslateMessage(&msg);
- ::DispatchMessage(&msg);
- }
- }
- }
可以看出,任何消息都会经过CPaintManagerUI::TranslateMessage的过滤,如果该方法没有处理消息,才将消息路由给窗口过程。相当于MFC中的PreTranslateMessage。
然后我们看下CPaintManagerUI::TranslateMessage的实现:
- bool CPaintManagerUI::TranslateMessage(const LPMSG pMsg)
- {
- // Pretranslate Message takes care of system-wide messages, such as
- // tabbing and shortcut key-combos. We'll look for all messages for
- // each window and any child control attached.
- HWND hwndParent = ::GetParent(pMsg->hwnd);
- UINT uStyle = GetWindowStyle(pMsg->hwnd);
- LRESULT lRes = 0;
- for( int i = 0; i < m_aPreMessages.GetSize(); i++ ) {
- CPaintManagerUI* pT = static_cast<CPaintManagerUI*>(m_aPreMessages[i]);
- if( pMsg->hwnd == pT->GetPaintWindow()
- || (hwndParent == pT->GetPaintWindow() && ((uStyle & WS_CHILD) != 0)) )
- {
- if( pT->PreMessageHandler(pMsg->message, pMsg->wParam, pMsg->lParam, lRes) ) return true;
- }
- }
- return false;
- }
m_aPreMessages 的定义为:static CStdPtrArray m_aPreMessages; 当创建一个主窗口时,m_aPreMessages一般会有插入动作,因此,此数组是所有相关窗口attach的CPaintManagerUI对象地址的集合,处理的是整个程序的全局的消息。当msg消息路由至此处时,如果该窗口是子窗口或者消息窗口本身,就调用PreMessageHandler处理,如果仍未得到处理,则返回false,传递给窗口注册的窗口消息处理。
接下来看看CPaintManagerUI::PreMessageHandler的实现:
- bool CPaintManagerUI::PreMessageHandler(UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT& /*lRes*/)
- {
- for( int i = 0; i < m_aPreMessageFilters.GetSize(); i++ )
- {
- bool bHandled = false;
- LRESULT lResult = static_cast<IMessageFilterUI*>(m_aPreMessageFilters[i])->MessageHandler(uMsg, wParam, lParam, bHandled);
- if( bHandled ) {
- return true;
- }
- }
- switch( uMsg ) {
- case WM_KEYDOWN:
- {
- // Tabbing between controls
- if( wParam == VK_TAB ) {
- if( m_pFocus && m_pFocus->IsVisible() && m_pFocus->IsEnabled() && _tcsstr(m_pFocus->GetClass(), _T("RichEditUI")) != NULL ) {
- if( static_cast<CRichEditUI*>(m_pFocus)->IsWantTab() ) return false;
- }
- SetNextTabControl(::GetKeyState(VK_SHIFT) >= 0);
- return true;
- }
- }
- break;
- case WM_SYSCHAR:
- {
- // Handle ALT-shortcut key-combinations
- FINDSHORTCUT fs = { 0 };
- fs.ch = toupper((int)wParam);
- CControlUI* pControl = m_pRoot->FindControl(__FindControlFromShortcut, &fs, UIFIND_ENABLED | UIFIND_ME_FIRST | UIFIND_TOP_FIRST);
- if( pControl != NULL ) {
- pControl->SetFocus();
- pControl->Activate();
- return true;
- }
- }
- break;
- case WM_SYSKEYDOWN:
- {
- if( m_pFocus != NULL ) {
- TEventUI event = { 0 };
- event.Type = UIEVENT_SYSKEY;
- event.chKey = (TCHAR)wParam;
- event.ptMouse = m_ptLastMousePos;
- event.wKeyState = MapKeyState();
- event.dwTimestamp = ::GetTickCount();
- m_pFocus->Event(event);
- }
- }
- break;
- }
- return false;
- }
这里又新增了一个对象属性:m_aPreMessageFilters,是一个窗口类(派生CWindowWnd)对象的集合。同时该对象的类必须是IMessageFilterUI和CWindowWnd的子类。通过这个集合,app将消息推送到每一个需要预处理所关心的消息的窗口,即调用MessageHandler,此方法是我们要重载的重要方法之一,用于对消息传递给窗口过程之前的预处理。接下来的是duilib提供的预处理,switch块处理的是全局级消息,对其进行预处理,包括tab处理,alt组合键和系统按键消息。
必须要注意,CPaintManagerUI::MessageHandler为我们提供了多数消息的默认实现。
至此,消息的预处理阶段完成,回到CPaintManagerUI::MessageLoop,未处理的消息将传递给窗口过程:
- 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);
- }
- }
此过程很简单,对窗口创建过程中的第一个消息WM_NCCREATE进行处理,对其窗口attach窗口对象地址。HandleMessage是一个虚函数,需要我们进行重载,此接口很重要,相当于MFC中的具体的一个窗口类的消息映射表,win32中的具体的窗口过程。
下面的代码展示了HandleMessage的典型实现:
- LRESULT HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam)
- {
- if( uMsg == WM_CREATE ) {
- m_pm.Init(m_hWnd);
- CControlUI *pButton = new CButtonUI;
- pButton->SetName(_T("closebtn"));
- pButton->SetBkColor(0xFFFF0000);
- m_pm.AttachDialog(pButton);
- m_pm.AddNotifier(this);
- return 0;
- }
- else if( uMsg == WM_DESTROY ) {
- ::PostQuitMessage(0);
- }
- else if( uMsg == WM_NCACTIVATE ) {
- if( !::IsIconic(m_hWnd) ) {
- return (wParam == 0) ? TRUE : FALSE;
- }
- }
- else if( uMsg == WM_NCCALCSIZE ) {
- return 0;
- }
- else if( uMsg == WM_NCPAINT ) {
- return 0;
- }
- LRESULT lRes = 0;
- if( m_pm.MessageHandler(uMsg, wParam, lParam, lRes) ) return lRes;
- return CWindowWnd::HandleMessage(uMsg, wParam, lParam);
- }
至此,消息路由大体就说完了,有兴趣的可以画个图,一目了然。
- Duilib消息路由
- DUILIB库笔记:消息的路由
- duilib消息
- Duilib消息类型
- Duilib中的控件消息
- duilib 消息流程
- duilib : 消息处理 tips
- DuiLib 消息机制剖析
- duilib创建自定义消息
- duilib消息处理
- DuiLib消息处理剖析
- DuiLib 消息机制剖析
- DuiLib 消息机制剖析
- duilib 快捷键发送消息
- DuiLib 消息机制剖析
- DuiLib消息处理剖析
- DuiLib消息处理剖析
- DuiLib消息处理剖析
- redis 队列
- JavaScript实现二级联动下拉框
- cocos2dx 3.x关于网络http的curl编译报错_imp_curl_esay_XX解决
- [编程题] 膨胀的牛牛
- 第13期《织梦之路》9月刊
- Duilib消息路由
- ubuntu下sublime 3安装Package Control的超简单方法
- Linux一些软件的安装
- 1307:矩阵找值
- Oracle姿势图谱
- Python网络爬虫与信息提取-Day9-信息标记与提取方法
- hihocoder#1338 : A Game(dfs+DP)
- JVM的内存区域划分
- JavaWeb之反射技术