DuiLib介绍及其消息处理剖析

2024-06-23 17:18

本文主要是介绍DuiLib介绍及其消息处理剖析,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

DirectUI技术

DirectUI意为直接在父窗口上绘图(Paint on parent dc directly)。即子窗口不以窗口句柄的形式创建(windowless),只是逻辑上的窗口,绘制在父窗口之上。微软的"DirectUI"技术广 泛的应用于Windows XP,Vista,Windows 7,如浏览器左侧的TaskPanel,控制面板导航界面,Media Player播放器,即时通讯工具MSN Messager等。

DirectUI好处在于可以很方便的构建高效,绚丽的,非常易于扩展的界面。国外如微软,国内如腾讯,百度等公司的客户端产品多采用这种方式来组织界 面,从而很好的将界面和逻辑分离,同时易于实现各种超炫的界面效果如换色,换肤,透明等。 DirectUI 旨在满足客户端界面快速开发的需要,同时融入业界前沿的皮肤技术,为用户创建更加高效,专业的界面。

但微软并没有公开任何Win32 桌面应用开发的DirectUI库,类似的库有一套叫WPF,但此库只能在.NET托管环境下运行。所以这种库,基本上上是各个公司各自开发了一套。

DuiLib 简介

  • 国内首个开源的DirectUI界面库,使用C++开发,遵循bsd协议,可以免费用于商业项目,目前支持Windows 32 、Window CE、Mobile等平台。
  • Duilib 是一款强大的界面开发工具,可以将用户界面和处理逻辑彻底分离,极大地提高用户界面的开发效率。提供所见即所得的开发工具UIDesigner。使用 DirectUI后将使得我们的设计人员彻底解放,不会受到开发的束缚,可以充分地发挥其设计能力来设计软件界面,并参与到用户界面开发过程中。
  • DirectUI界面库 取名自微软的一个窗口类名"DirectUIHWND",意为Paint on parent dc directly。 即子窗口不以窗口句柄的形式创建,只是逻辑上的窗口,绘制在父窗口之上。
  • DirectUI界面库使用XML来描述界面风格,界面布局,可 以很方便的构建高效,绚丽的,非常易于扩展的界面。从而很好的将界面和逻辑分离,同时易于实现各种超炫的界面效果如换色,换肤,透明等。
  • Duilib界面库的出现解决了使用传统MFC界面库开发软件不美观、界面细节处理不好、使用硬编码、开发效率低下、生成程序体积大等问题。而且传统 MFC界面美化库大都使用HOOK等对系统影响比较大的技术,可能会导致系统不稳定或者引发其他错误。而Duilib界面库完全基于GDI在窗口上自绘, 无其他依赖,未使用特殊或危险的系统调用,能够很好的解决传统MFC界面的一系列问题。
  • Duilib界面库完全兼容ActiveX控件(如常见的IE控件和Flash),也可以和MFC等界面库配合使用,用户完全可以不用担心切换到Duilib界面库上面会带来额外的成本或者Duilib界面库会出现功能不够用等情况。
  • Duilib界面库可广泛用于互联网客户端、工具软件客户端、管理系统客户端、多媒体客户端(如KTV、触摸屏)、车载电脑系统、gps系统和手机客户端软件等。
  • Duilib界面库可以广泛运行在windows98、Windows2000、WindowsXP、Windows2003、Windows Vista、Windows7、Windows8、WindowsCE5、WindowsCE6、Windows Mobile6等平台上,目前支持的开发工具包括vc6、vc2003、vc2005、vc2008、vc2010。
  • Duilib界面库基于viksoe的DirectUI项目,并且以BSD协议开源。

DuiLib 一部分技术特点

  • 界面与业务逻辑分离
  • 使用XML配置界面
  • 界面布局方式灵活多样
  • 内置常用的控件
  • 支持自定义控件
  • 强大的控件组合能力,复杂功能可通过简单控件组合完成
  • 强大的事件处理机制
  • 基于GDI和脏矩形的高效绘制技术
  • 支持多种资源方式,支持多种图片格式
  • 支持alpha混合,支持窗口透明
  • 强大的图片绘制描述方式
  • 类html字符串绘制技术
  • 支持动态变换色调
  • 支持动态切换资源位置方式换肤
  • 支持ansi和unicode,支持多国家语言
  • 支持插件系统
  • 内存占用小
  • 无第三方库依赖
  • 提供可见即所得的ui编辑器
  • 丰富易上手的demo例子
  • 可以非常容易和mfc、wtl结合使用

DuiLib 入门

此库是完全开源,而且是国内的人开源的,最后维护时间为2013年底,2014年上半年没有任何动静。基本上没有任何文档说明,全靠自己阅读代码来理解原理。这里提供一些入门资源:

http://www.cnblogs.com/Alberl/tag/duilib入门教程/

看完此教程,基本上就算初级入门了。 此博客里还链接了一些更高一级的教程,有兴趣的可以自己阅读和研究。

此库在Google开源项目中可以找到: https://code.google.com/p/duilib/

SVN:

# Non-members may check out a read-only working copy anonymously over HTTP.

svn checkout http://duilib.googlecode.com/svn/trunk/ duilib-read-only

 

注:DuiLib好像已经从svn迁移到github了。

DuiLib核心的大体结构图如下:

分为几个大部分:

  1. 控件
  2. 容器(本质也是控件)
  3. UI构建解析器(XML解析)
  4. 窗体管理器(消息循环,消息映射,消息处理,窗口管理等)
  5. 渲染引擎

DuiLib 消息循环剖析

DuiLib的消息循环非常灵活,但不熟悉的可能会觉得非常混乱,不知道该如何下手。所以,我总结了下DuiLib的各种消息响应的方式,帮助大家理解DuiLib和加快开发速度。

其消息处理架构较为灵活,基本上在消息能过滤到的地方,都给出了扩展接口。

看了DuiLib入门教程后,对消息机制的处理有些模糊,为了屏蔽Esc按键,都花了大半天的时间。究其原因,是因为对DuiLib消息过滤不了解。
你至少应该看过上面提及的那篇入门教程,看过一些DuiLib的代码,但可能没看懂,那么这篇文章会给你指点迷津。

Win32消息路由如下:

  1. 消息产生。
  2. 系统将消息排列到其应该排放的线程消息队列中。
  3. 线程中的消息循环调用GetMessage(or PeekMessage)获取消息。
  4. 传送消息TranslateMessage and DispatchMessage to 窗口过程(Windows procedure)。
  5. 在窗口过程里进行消息处理

我们看到消息经过几个步骤,DuiLib架构可以让你在某些步骤间进行消息过滤。首先,第1、2和3步骤,DuiLib并不关心。DuiLib对消息处理集中在CPaintManagerUI类中(也就是上面提到的窗体管理器)。DuiLib在发送到窗口过程的前和后都进行了消息过滤。

DuiLib的消息渠,也就是所谓的消息循环在CPaintManagerUI::MessageLoop()或者CWindowWnd::ShowModal()中实现。俩套代码的核心基本一致,以MessageLoop为例:

void CPaintManagerUI::MessageLoop()
{MSG msg = { 0 };while( ::GetMessage(&msg, NULL, 0, 0) ) {// CPaintManagerUI::TranslateMessage进行消息过滤if( !CPaintManagerUI::TranslateMessage(&msg) ) {::TranslateMessage(&msg);try{::DispatchMessage(&msg);} catch(...) {DUITRACE(_T("EXCEPTION: %s(%d)\n"), __FILET__, __LINE__);#ifdef _DEBUGthrow "CPaintManagerUI::MessageLoop";#endif}}}
}

3和4之间,DuiLib调用CPaintManagerUI::TranslateMessage做了过滤,类似MFC的PreTranlateMessage

想象一下,如果不使用这套消息循环代码,我们如何能做到在消息发送到窗口过程前进行常规过滤(Hook等拦截技术除外)?答案肯定是做不到。因为那段循环 代码你是无法控制的。CPaintManagerUI::TranslateMessage将无法被调用,所以,可以看到DuiLib中几乎所有的 demo在创建玩消息后,都调用了这俩个消息循环函数。下面是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.UINT uStyle = GetWindowStyle(pMsg->hwnd);UINT uChildRes = uStyle & WS_CHILD;    LRESULT lRes = 0;if (uChildRes != 0) // 判断子窗口还是父窗口{HWND hWndParent = ::GetParent(pMsg->hwnd);for( int i = 0; i < m_aPreMessages.GetSize(); i++ ) {CPaintManagerUI* pT = static_cast<CPaintManagerUI*>(m_aPreMessages[i]);        HWND hTempParent = hWndParent;while(hTempParent){if(pMsg->hwnd == pT->GetPaintWindow() || hTempParent == pT->GetPaintWindow()){if (pT->TranslateAccelerator(pMsg))return true;// 这里进行消息过滤if( pT->PreMessageHandler(pMsg->message, pMsg->wParam, pMsg->lParam, lRes) ) return true;return false;}hTempParent = GetParent(hTempParent);}}}else{for( int i = 0; i < m_aPreMessages.GetSize(); i++ ) {CPaintManagerUI* pT = static_cast<CPaintManagerUI*>(m_aPreMessages[i]);if(pMsg->hwnd == pT->GetPaintWindow()){if (pT->TranslateAccelerator(pMsg))return true;if( pT->PreMessageHandler(pMsg->message, pMsg->wParam, pMsg->lParam, lRes) ) return true;return false;}}}return false;
}
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); // 这里调用接口 IMessageFilterUI::MessageHandler 来进行消息过滤if( bHandled ) {return true;}
}
…… ……
return false;
}

在发送到窗口过程前,有一个过滤接口:IMessageFilterUI,此接口只有一个成员:MessageHandler,我们的窗口类要提前过滤消息,只要实现这个IMessageFilterUI,调用CPaintManagerUI::AddPreMessageFilter,将我们的窗口类实例指针添加到CPaintManagerUI::m_aPreMessageFilters 数组中。当消息到达窗口过程之前,就会会先调用我们的窗口类的成员函数:MessageHandler。

下面是AddPreMessageFilter代码:

bool CPaintManagerUI::AddPreMessageFilter(IMessageFilterUI* pFilter)
{// 将实现好的接口实例,保存到数组 m_aPreMessageFilters 中。ASSERT(m_aPreMessageFilters.Find(pFilter)<0);return m_aPreMessageFilters.Add(pFilter);
}

我们从函数CPaintManagerUI::TranslateMessage代码中能够看到,这个过滤是在大循环:

for( int i = 0; i < m_aPreMessages.GetSize(); i++ )

中被调用的。如果m_aPreMessages.GetSize()为0,也就不会调用过滤函数。从代码中追溯其定义:

static CStdPtrArray m_aPreMessages;

是个静态变量,MessageLoop,TranslateMessage等也都是静态函数。其值在CPaintManagerUI::Init中被初始化:

void CPaintManagerUI::Init(HWND hWnd)
{ASSERT(::IsWindow(hWnd));// Remember the window context we came fromm_hWndPaint = hWnd;m_hDcPaint = ::GetDC(hWnd);// We'll want to filter messages globally toom_aPreMessages.Add(this);
}

看来,m_aPreMessages存储的类型为CPaintManagerUI* ,也就说,这个静态成员数组里,存储了当前进程中所有的CPaintManagerUI实例指针,所以,如果有多个CPaintManagerUI实例, 也不会存在过滤问题,互不干扰,都能各自过滤。当然m_aPreMessages不止用在消息循环中,也有别的用处。我觉得这个名字起得有点诡异。

然后再说,消息抵达窗口过程后,如何处理。首先,要清楚,窗口过程在哪儿?使用DuiLib开发,我们的窗口类无外呼,继承俩个基类:一个是功能简陋一点 的:CWindowWnd,一个是功能健全一点的:WindowImplBase(继承于CWindowWnd)。然后,我们实例化窗口类,调用这俩个基 类的Create函数,创建窗口,其内部注册了窗口过程:

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);}
}

里面,主要做了一些转换,细节自行研究,最终,他会调用pThis→HandleMessage(uMsg, wParam, lParam);。也即是说,HandleMessage相当于一个窗口过程(虽然它不是,但功能类似)。他是CWindowWnd的虚函数:

virtual LRESULT HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam);

所以,如果我们的窗口类实现了HandleMessage,就相当于再次过滤了窗口过程,HandleMessage代码框架如下:

LRESULT HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam)
{
if( uMsg == WM_XXX ) {… … return 0;}else if( uMsg == WM_XXX) {… … return 1;
}LRESULT lRes = 0;
if( m_pm.MessageHandler(uMsg, wParam, lParam, lRes) ) //CPaintManagerUI::MessageHandler
return lRes;return CWindowWnd::HandleMessage(uMsg, wParam, lParam); // 调用父类HandleMessage
}

在注意:CPaintManagerUI::MessageHandler,名称为MessageHandler,而不是HandleMessage。
没有特殊需求,一定要调用此函数,此函数处理了绝大部分常用的消息响应。而且如果你要响应Notify事件,不调用此函数将无法响应,后面会介绍。

好现在我们已经知道,俩个地方可以截获消息:

  1. 实现IMessageFilterUI接口,调用CPaintManagerUI:: AddPreMessageFilter,进行消息发送到窗口过程前的过滤。
  2. 重载HandleMessage函数,当消息发送到窗口过程中时,最先进行过滤。

下面继续看看void Notify(TNotifyUI& msg)是如何响应的。我们的窗口继承于INotifyUI接口,就必须实现此函数:

class INotifyUI

{

public:

    virtual void Notify(TNotifyUI& msg) = 0;

};

上面我说了,在我们的HandleMessage要调用CPaintManagerUI::MessageHandler来进行后续处理。下面是一个代码片段:

bool CPaintManagerUI::MessageHandler(UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT& lRes)
{… …TNotifyUI* pMsg = NULL;while( pMsg = static_cast<TNotifyUI*>(m_aAsyncNotify.GetAt(0)) ) {m_aAsyncNotify.Remove(0);if( pMsg->pSender != NULL ) {if( pMsg->pSender->OnNotify ) pMsg->pSender->OnNotify(pMsg);}// 先看这里,其它代码先忽略;我们看到一个转换操作static_cast<INotifyUI*>for( int j = 0; j < m_aNotifiers.GetSize(); j++ ) {static_cast<INotifyUI*>(m_aNotifiers[j])->Notify(*pMsg);}delete pMsg;}// Cycle through listenersfor( int i = 0; i < m_aMessageFilters.GetSize(); i++ ) {bool bHandled = false;LRESULT lResult = static_cast<IMessageFilterUI*>(m_aMessageFilters[i])->MessageHandler(uMsg, wParam, lParam, bHandled);if( bHandled ) {lRes = lResult;return true;}
}
… …
}

定义为CStdPtrArray m_aNotifiers;数组,目前还看不出其指向的实际类型。看看,什么时候给该数组添加成员:

bool CPaintManagerUI::AddNotifier(INotifyUI* pNotifier)
{ASSERT(m_aNotifiers.Find(pNotifier)<0);return m_aNotifiers.Add(pNotifier);
}

不错,正是AddNotifier,类型也有了:INotifyUI。所以,入门教程里会在响应WM_CREATE消息的时候,调用 AddNotifier(this),将自身加入数组中,然后在CPaintManagerUI::MessageHandler就能枚举调用。由于 AddNotifer的参数为INotifyUI*,所以,我们要实现此接口。 
所以,当HandleMessage函数被调用后,紧接着会调用我们的Notify函数。如果你没有对消息过滤的特殊需求,实现INotifyUI即可,在Notify函数中处理消息响应。

上面的Notify调用,是响应系统产生的消息。程序本身也能手动产生,其函数为:

void CPaintManagerUI::SendNotify(TNotifyUI& Msg, bool bAsync /*= false*/)

DuiLib将发送的Notify消息分为了同步和异步消息。同步就是立即调用(类似SendMessage),异步就是先放到队列中,下次再处理。(类似PostMessage)。

void CPaintManagerUI::SendNotify(TNotifyUI& Msg, bool bAsync /*= false*/)
{… …if( !bAsync ) {// Send to all listeners// 同步调用OnNotify,注意不是Notifyif( Msg.pSender != NULL ) {if( Msg.pSender->OnNotify ) Msg.pSender->OnNotify(&Msg);}// 还会再次通知所有注册了INotifyUI的窗口。for( int i = 0; i < m_aNotifiers.GetSize(); i++ ) {static_cast<INotifyUI*>(m_aNotifiers[i])->Notify(Msg);}}
else {// 异步调用,添加到m_aAsyncNotify array中TNotifyUI *pMsg = new TNotifyUI;pMsg->pSender = Msg.pSender;pMsg->sType = Msg.sType;pMsg->wParam = Msg.wParam;pMsg->lParam = Msg.lParam;pMsg->ptMouse = Msg.ptMouse;pMsg->dwTimestamp = Msg.dwTimestamp;m_aAsyncNotify.Add(pMsg);}
}

我们CPaintManagerUI::MessageHandler在开始处发现一些代码:

TNotifyUI* pMsg = NULL;

while( pMsg = static_cast<TNotifyUI*>(m_aAsyncNotify.GetAt(0)) ) {

m_aAsyncNotify.Remove(0);

if( pMsg->pSender != NULL ) {

if( pMsg->pSender->OnNotify ) pMsg->pSender->OnNotify(pMsg);

}

可以看到MessageHandler首先从异步队列中一个消息并调用OnNotify。OnNotify和上面的Notify不一样哦。

OnNotify是响应消息的另外一种方式。它的定义为:

CEventSource OnNotify;

属于CControlUI类。重载了一些运算符,如 operator();要让控件响应手动发送(SendNotify)的消息,就要给控件的OnNotify,添加消息代理。在DuiLib的TestApp1中的OnPrepare函数里,有:

CSliderUI* pSilder = static_cast<CSliderUI*>(m_pm.FindControl(_T("alpha_controlor")));

if( pSilder ) pSilder->OnNotify += MakeDelegate(this, &CFrameWindowWnd::OnAlphaChanged);

至于代理的代码实现,我就不展示了,这里简单说明,就是将类成员函数,作为回调函数,加入到OnNotify中,然后调用 pMsg→pSender→OnNotify(pMsg)的时候,循环调用所有的类函数,实现通知的效果。代理代码处理的很巧妙,结合多态和模板,能将任 何类成员函数作为回调函数。 

查阅CSliderUI代码,发现他在自身的DoEvent函数内调用了诸如:

m_pManager->SendNotify(this, DUI_MSGTYPE_VALUECHANGED);

类似的代码,调用它,我们就会得到通知。

现在,又多了两种消息处理的方式:

  1. 实现INotifyUI,调用CPaintManagerUI::AddNotifier,将自身加入Notifier队列。
  2. 添加消息代理(其实就是将成员函数最为回到函数加入),MakeDelegate(this, &CFrameWindowWnd::OnAlphaChanged);,当程序某个地方调用了 CPaintManagerUI::SendNotify,并且Msg.pSender正好是注册的this,我们的类成员回调函数将被调用。

搜寻CPaintManagerUI代码,我们发现还有一些消息过滤再里面:

bool CPaintManagerUI::AddMessageFilter(IMessageFilterUI* pFilter)

{
    ASSERT(m_aMessageFilters.Find(pFilter)<0);
    return m_aMessageFilters.Add(pFilter);

}

m_aMessageFilters也是IMessageFilterUI array,和m_aPreMessageFilters类似。

上面我们介绍的是CPaintManagerUI::AddPreMessageFilter,那这个又是在哪儿做的过滤?

还是CPaintManagerUI::MessageHandler中:

……// Cycle through listenersfor( int i = 0; i < m_aMessageFilters.GetSize(); i++ ) {bool bHandled = false;LRESULT lResult = static_cast<IMessageFilterUI*>(m_aMessageFilters[i])->MessageHandler(uMsg, wParam, lParam, bHandled);if( bHandled ) {lRes = lResult;return true;}
}
… …

这个片段是在,异步OnNotify和Nofity消息响应,被调用后。才被调用的,优先级也就是最低。但它始终会被调用,因为异步OnNotify和 Nofity消息响应没有返回值,不会因为消息已经被处理,而直接退出。DuiLib再次给用户一个处理消息的机会。用户可以选择将bHandled设置 为True,从而终止消息继续传递。我觉得,这个通常是为了弥补OnNotify和Nofity没有返回值的问题,在m_aMessageFilters 做集中处理。

处理完所有的消息响应后,如果消息没有被截断,CPaintManagerUI::MessageHandler继续处理大多数默认的消息,它会处理在其管理范围中的所有控件的大多数消息和事件等。

然后,消息机制还没有完,这只是CPaintManagerUI::MessageHandler中的消息机制,如果继承的是 WindowImplBase, WindowImplBase实现了DuiLib窗口的大部分功能。WindowImplBase继承了CWindowWnd,重载了 HandleMessage,也就是说,消息发送的窗口过程后,第一个调用的是WindowImplBase::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处理一些消息,使用成员函数On***来处理消息,所以,可以重载这些函数达到消息过滤的目的。 然后,我们看到,有一个函数:WindowImplBase::HandleCustomMessage,它是虚函数,我们可以重载此函数,进行消息过滤,由于还没有调用m_PaintManager.MessageHandler,所以在收到Notify消息之前进行的过滤。

有多了两种方式:

  • 重载父类:WindowImplBase的虚函数
  • 重载父类:WindowImplBase::HandleCustomMessage函数

最后,继承于WindowImplBase,还有一种过滤消息的方式,和Notify消息平级,实现方式是仿造的MFC消息映射机制: WindowImplBase实现了INotifyUI接口,并且AddNotify了自身,所以,它会收到Notify消息:

void WindowImplBase::Notify(TNotifyUI& msg)

{

    return CNotifyPump::NotifyPump(msg);

}

NotifyPump 调用 LoopDispatch,代码片段如下:

... ... const DUI_MSGMAP_ENTRY* lpEntry = NULL;const DUI_MSGMAP* pMessageMap = NULL;#ifndef UILIB_STATICfor(pMessageMap = GetMessageMap(); pMessageMap!=NULL; pMessageMap = (*pMessageMap->pfnGetBaseMap)())
#elsefor(pMessageMap = GetMessageMap(); pMessageMap!=NULL; pMessageMap = pMessageMap->pBaseMap)
#endif{
#ifndef UILIB_STATICASSERT(pMessageMap != (*pMessageMap->pfnGetBaseMap)());
#elseASSERT(pMessageMap != pMessageMap->pBaseMap);
#endifif ((lpEntry = DuiFindMessageEntry(pMessageMap->lpEntries,msg)) != NULL){goto LDispatch;}}
... ...

代码量过多,这里进行原理说明,和MFC一样,提供了一些消息宏

DUI_DECLARE_MESSAGE_MAP()

   

DUI_BEGIN_MESSAGE_MAP(CKeyBoardDlg, CNotifyPump)

    DUI_ON_MSGTYPE(DUI_MSGTYPE_CLICK, OnClick)

    DUI_ON_MSGTYPE(DUI_MSGTYPE_WINDOWINIT, OnInitWindow)

DUI_END_MESSAGE_MAP()

和MFC原理一样,声明一些静态变量,存储类的信息,插入一些成员函数,最为回调,最后生成一张静态表。当WindowImplBase::Notify有消息时,遍历表格,进行消息通知。

总结,DuiLib消息响应方式:

  • 实现IMessageFilterUI接口,调用CPaintManagerUI::AddPreMessageFilter,进行消息发送到窗口过程前的过滤。
  • 重载HandleMessage函数,当消息发送到窗口过程中时,最先进行过滤。
  • 实现INotifyUI,调用CPaintManagerUI::AddNotifier,将自身加入Notifier队列。
  • 添加消息代理(其实就是将成员函数最为回到函数加入),MakeDelegate(this, &CFrameWindowWnd::OnAlphaChanged);,当程序某个地方调用了 CPaintManagerUI::SendNotify,并且Msg.pSender正好是this,我们的类成员回调函数将被调用。
  • 重载父类:WindowImplBase的虚函数
  • 重载父类:WindowImplBase::HandleCustomMessage函数
  • 使用类似MFC的消息映射

这篇关于DuiLib介绍及其消息处理剖析的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1087777

相关文章

四种Flutter子页面向父组件传递数据的方法介绍

《四种Flutter子页面向父组件传递数据的方法介绍》在Flutter中,如果父组件需要调用子组件的方法,可以通过常用的四种方式实现,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录方法 1:使用 GlobalKey 和 State 调用子组件方法方法 2:通过回调函数(Callb

一文详解Python中数据清洗与处理的常用方法

《一文详解Python中数据清洗与处理的常用方法》在数据处理与分析过程中,缺失值、重复值、异常值等问题是常见的挑战,本文总结了多种数据清洗与处理方法,文中的示例代码简洁易懂,有需要的小伙伴可以参考下... 目录缺失值处理重复值处理异常值处理数据类型转换文本清洗数据分组统计数据分箱数据标准化在数据处理与分析过

mysql外键创建不成功/失效如何处理

《mysql外键创建不成功/失效如何处理》文章介绍了在MySQL5.5.40版本中,创建带有外键约束的`stu`和`grade`表时遇到的问题,发现`grade`表的`id`字段没有随着`studen... 当前mysql版本:SELECT VERSION();结果为:5.5.40。在复习mysql外键约

Java中Springboot集成Kafka实现消息发送和接收功能

《Java中Springboot集成Kafka实现消息发送和接收功能》Kafka是一个高吞吐量的分布式发布-订阅消息系统,主要用于处理大规模数据流,它由生产者、消费者、主题、分区和代理等组件构成,Ka... 目录一、Kafka 简介二、Kafka 功能三、POM依赖四、配置文件五、生产者六、消费者一、Kaf

Python进阶之Excel基本操作介绍

《Python进阶之Excel基本操作介绍》在现实中,很多工作都需要与数据打交道,Excel作为常用的数据处理工具,一直备受人们的青睐,本文主要为大家介绍了一些Python中Excel的基本操作,希望... 目录概述写入使用 xlwt使用 XlsxWriter读取修改概述在现实中,很多工作都需要与数据打交

java脚本使用不同版本jdk的说明介绍

《java脚本使用不同版本jdk的说明介绍》本文介绍了在Java中执行JavaScript脚本的几种方式,包括使用ScriptEngine、Nashorn和GraalVM,ScriptEngine适用... 目录Java脚本使用不同版本jdk的说明1.使用ScriptEngine执行javascript2.

Go语言使用Buffer实现高性能处理字节和字符

《Go语言使用Buffer实现高性能处理字节和字符》在Go中,bytes.Buffer是一个非常高效的类型,用于处理字节数据的读写操作,本文将详细介绍一下如何使用Buffer实现高性能处理字节和... 目录1. bytes.Buffer 的基本用法1.1. 创建和初始化 Buffer1.2. 使用 Writ

Python视频处理库VidGear使用小结

《Python视频处理库VidGear使用小结》VidGear是一个高性能的Python视频处理库,本文主要介绍了Python视频处理库VidGear使用小结,文中通过示例代码介绍的非常详细,对大家的... 目录一、VidGear的安装二、VidGear的主要功能三、VidGear的使用示例四、VidGea

Python结合requests和Cheerio处理网页内容的操作步骤

《Python结合requests和Cheerio处理网页内容的操作步骤》Python因其简洁明了的语法和强大的库支持,成为了编写爬虫程序的首选语言之一,requests库是Python中用于发送HT... 目录一、前言二、环境搭建三、requests库的基本使用四、Cheerio库的基本使用五、结合req

使用Python处理CSV和Excel文件的操作方法

《使用Python处理CSV和Excel文件的操作方法》在数据分析、自动化和日常开发中,CSV和Excel文件是非常常见的数据存储格式,ython提供了强大的工具来读取、编辑和保存这两种文件,满足从基... 目录1. CSV 文件概述和处理方法1.1 CSV 文件格式的基本介绍1.2 使用 python 内