DUILIB库笔记:消息的路由
2013-07-30 12:16
253 查看
本文若有不对之处,敬请指出。
我们知道win32窗口程序的基本流程:
注册窗口类 -> 创建窗口 —> 显示窗口 -> 消息循环 -> 消息流转
基于这个路线,我们来探索下duilib库的实现。
毋庸置疑,先看一例测试程序中的WinMain函数:
可以确定注册窗口类和创建窗口都在CFrameWindowWnd::Create中进行。其中CFrameWindowWnd的一个基类是CWindowWnd,进入到该类Create方法中:
其中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);
}
至此,消息路由大体就说完了,有兴趣的可以画个图,一目了然。
我们知道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);
}
至此,消息路由大体就说完了,有兴趣的可以画个图,一目了然。
相关文章推荐
- 分布式异步消息框架构建笔记3 —— 自动消息路由
- rabbitMQ消息服务器学习笔记(java)4消息路由routing
- 解析duilib 消息处理函数-笔记1
- 分布式异步消息框架构建笔记4——分布式消息路由
- DUILib学习笔记---消息处理
- Spring学习笔记3之消息队列(rabbitmq)发送邮件功能
- MFC学习笔记之二----------MFC框架程序及消息映射
- MFC消息路由
- 笔记:vc6.0添加自定义用户消息的基本过程
- Android应用开发学习笔记之多线程与Handler消息处理机制
- 消息队列开发记录笔记-ActiveMQ
- 路由类型笔记整理
- cisco三层与华为三层路由设置笔记
- [WPF] Felix 的线程学习笔记(一)——从Win32的消息循环说起
- windows编程(消息机制、消息队列、消息映射、线程同步)笔记windows编程(消息机制、消息队列、消息映射、线程同步)笔记
- ThinkPHP实战03——《ThinkPHP3.2.3 实战个人博客》笔记——路由形式
- 小五思科技术学习笔记之路由再发布
- 【Visual C++】游戏开发笔记十二 游戏输入消息处理(一) 键盘消息处理
- Duilib扩展《01》— 双击、右键消息扩展
- freeDiameter源码阅读之消息路由