duidemo梳理
项目:duidemo
依赖库:duilib
源代码:
#头文件
ControlEx.h : 一些扩展的UI控件。圆圈加载条"CircleProgress"、win窗口"wnd"、新Combo控件"MyCombo"、图表类
MainWnd.h : 主页面,又是页面功能逻辑和窗口逻辑分开
MsgWnd.h : 新消息提示框,美化了MessageBox,只是个提示框
PopWnd.h : 弹出窗口,这是个窗口,功能可以比较多
resource.h : 自动生成,和rc资源相关
SkinFrame.h : 换肤窗口,主要是为了实现项目中的皮肤切换,内部有SkinManager类;
SkinManager.h : 皮肤管理,为了换肤,也引用了UIlib.h
SplashWnd.h : 闪屏飞溅窗口,用于在软件启动时显示一些信息,顺便加载,一般比较小
StdAfx.h : 加载duilib库和头文件
#资源文件
Resources
xmls
.ico
.rc
.zip
#cpp
ControlEx.h
DuiDemo.cpp
MainWnd.cpp
MsgWnd.cpp
PopWnd.cpp
SkinManager.cpp
SplashWnd.cpp
StdAfx.cpp
疑问1:
CChartViewUI类没有实现GetClass()接口,默认的是CControlUI,那后续怎么识别这个控件的呢?
(pT->TranslateAccelerator(pMsg))是什么
控件工厂
一个新类 :控件加工厂 CControlFactory,所有的控件及其创建函数都在这个单例中
关于控件类的创建函数,头文件中用DECLARE_DUICONTROL
声明,cpp中用IMPLEMENT_DUICONTROL
进行定义
疑问:内部注册和外部注册的区别。REGIST_DUICONTROL和INNER_REGISTER_DUICONTROL
//ControlFactory.h
#pragma once
#include <map>
namespace DuiLib
{
// 声明 一个指针函数CreateClass 返回类型为CControlUI*
typedef CControlUI* (*CreateClass)();
// 声明 map容器管理加工厂的控件类和其对应的实例化函数
// 类名 :函数指针
typedef std::map<CDuiString, CreateClass> MAP_DUI_CTRATECLASS;
// 需要导出到dll中的函数
class UILIB_API CControlFactory
{
public:
// 创建控件 实例化
CControlUI* CreateControl(CDuiString strClassName);
// 注册各个控件的(实例化)函数
void RegistControl(CDuiString strClassName, CreateClass pFunc);
// 获取工厂(单例)
static CControlFactory* GetInstance();
// 关闭工厂
void Release();
private:
// 构造函数里面 把所有的库已有的控件进行注册
CControlFactory();
virtual ~CControlFactory();
private:
// map容器 工厂私有
MAP_DUI_CTRATECLASS m_mapControl;
};
//这里只是声明创建控件的静态函数 声明这个函数接口
#define DECLARE_DUICONTROL(class_name)\
public:\
static CControlUI* CreateControl();
//控件的实施 静态函数的定义
#define IMPLEMENT_DUICONTROL(class_name)\
CControlUI* class_name::CreateControl()\
{\
return new class_name;\
}
//注册 用控件名和对应控件的静态函数 宏的好处啊 替换
#define REGIST_DUICONTROL(class_name)\
CControlFactory::GetInstance()->RegistControl(_T(#class_name), (CreateClass)class_name::CreateControl);
//duilib内部的控件 直接注册 外部的通过单例工厂实例注册
#define INNER_REGISTER_DUICONTROL(class_name)\
RegistControl(_T(#class_name), (CreateClass)class_name::CreateControl);
}
//ControlFactory.cpp
#include "StdAfx.h"
#include "ControlFactory.h"
namespace DuiLib
{
CControlFactory::CControlFactory()
{
INNER_REGISTER_DUICONTROL(CControlUI);
INNER_REGISTER_DUICONTROL(CContainerUI);
INNER_REGISTER_DUICONTROL(CButtonUI);
INNER_REGISTER_DUICONTROL(CComboUI);
INNER_REGISTER_DUICONTROL(CComboBoxUI);
INNER_REGISTER_DUICONTROL(CDateTimeUI);
INNER_REGISTER_DUICONTROL(CEditUI);
INNER_REGISTER_DUICONTROL(CActiveXUI);
INNER_REGISTER_DUICONTROL(CFlashUI);
INNER_REGISTER_DUICONTROL(CGifAnimUI);
#ifdef USE_XIMAGE_EFFECT
INNER_REGISTER_DUICONTROL(CGifAnimExUI);
#endif
INNER_REGISTER_DUICONTROL(CGroupBoxUI);
INNER_REGISTER_DUICONTROL(CIPAddressUI);
INNER_REGISTER_DUICONTROL(CIPAddressExUI);
INNER_REGISTER_DUICONTROL(CLabelUI);
INNER_REGISTER_DUICONTROL(CListUI);
INNER_REGISTER_DUICONTROL(CListHeaderUI);
INNER_REGISTER_DUICONTROL(CListHeaderItemUI);
INNER_REGISTER_DUICONTROL(CListLabelElementUI);
INNER_REGISTER_DUICONTROL(CListTextElementUI);
INNER_REGISTER_DUICONTROL(CListContainerElementUI);
INNER_REGISTER_DUICONTROL(CMenuUI);
INNER_REGISTER_DUICONTROL(CMenuElementUI);
INNER_REGISTER_DUICONTROL(COptionUI);
INNER_REGISTER_DUICONTROL(CCheckBoxUI);
INNER_REGISTER_DUICONTROL(CProgressUI);
INNER_REGISTER_DUICONTROL(CRichEditUI);
INNER_REGISTER_DUICONTROL(CScrollBarUI);
INNER_REGISTER_DUICONTROL(CSliderUI);
INNER_REGISTER_DUICONTROL(CTextUI);
INNER_REGISTER_DUICONTROL(CTreeNodeUI);
INNER_REGISTER_DUICONTROL(CTreeViewUI);
INNER_REGISTER_DUICONTROL(CWebBrowserUI);
INNER_REGISTER_DUICONTROL(CAnimationTabLayoutUI);
INNER_REGISTER_DUICONTROL(CChildLayoutUI);
INNER_REGISTER_DUICONTROL(CHorizontalLayoutUI);
INNER_REGISTER_DUICONTROL(CTabLayoutUI);
INNER_REGISTER_DUICONTROL(CTileLayoutUI);
INNER_REGISTER_DUICONTROL(CVerticalLayoutUI);
INNER_REGISTER_DUICONTROL(CRollTextUI);
INNER_REGISTER_DUICONTROL(CColorPaletteUI);
INNER_REGISTER_DUICONTROL(CListExUI);
INNER_REGISTER_DUICONTROL(CListContainerHeaderItemUI);
INNER_REGISTER_DUICONTROL(CListTextExtElementUI);
INNER_REGISTER_DUICONTROL(CHotKeyUI);
INNER_REGISTER_DUICONTROL(CFadeButtonUI);
INNER_REGISTER_DUICONTROL(CRingUI);
}
CControlFactory::~CControlFactory()
{
}
CControlUI* CControlFactory::CreateControl(CDuiString strClassName)
{
strClassName.MakeLower();
MAP_DUI_CTRATECLASS::iterator iter = m_mapControl.find(strClassName);
if ( iter == m_mapControl.end()) {
return NULL;
}
else {
return (CControlUI*) (iter->second());
}
}
void CControlFactory::RegistControl(CDuiString strClassName, CreateClass pFunc)
{
//将字符串转化为小写字母
strClassName.MakeLower();
m_mapControl.insert(MAP_DUI_CTRATECLASS::value_type(strClassName, pFunc));
}
CControlFactory* CControlFactory::GetInstance()
{
static CControlFactory* pInstance = new CControlFactory;
return pInstance;
}
void CControlFactory::Release()
{
delete this;
}
}
消息机制
- CPaintManager::MessageLoop();
- CPaintManager::ShowModal();
以CPaintManager::MessageLoop();为例
1.消息预过滤 —— IMessageFilterUI接口
void CPaintManagerUI::MessageLoop()
{
MSG msg = { 0 };
while( ::GetMessage(&msg, NULL, 0, 0) ) {
// 这里过滤消息
if( !CPaintManagerUI::TranslateMessage(&msg) ) {
::TranslateMessage(&msg);
try{
::DispatchMessage(&msg);
} catch(...) {
DUITRACE(_T("EXCEPTION: %s(%d)\n"), __FILET__, __LINE__);
#ifdef _DEBUG
throw "CPaintManagerUI::MessageLoop";
#endif
}
}
}
}
这里在获取到消息后,在翻译分发前,在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.
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;
pT->PreMessageHandler(pMsg->message, pMsg->wParam, pMsg->lParam, lRes);
}
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;
}
这里通过静态指针数组m_aPreMessages管理了所有的窗口的CPaintManagerUI实例,不同的实例间互不影响,各自过滤。但是在初始化时得将各窗口对应的绘制管理器加进来,像这样:
void CPaintManagerUI::Init(HWND hWnd, LPCTSTR pstrName)
{
//需要绘制的窗口,把自己加到这数组中,可设定绘制管理员的名字
//清空CPaintManagerUI的状态 注册接受drop信息 注册自己作为窗口的预处理对象
// ...
if( m_hWndPaint != hWnd ) {
m_hWndPaint = hWnd;
m_hDcPaint = ::GetDC(hWnd);
// 添加当前的示例到静态数组中
m_aPreMessages.Add(this);
}
}
接着上一步,CPaintManagerUI::TranslateMessage中逐个调用了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;
}
}
...
}
这里正式通过m_aPreMessageFilters过滤器指针数组(这个不是静态的,而是CPaintManagerUI的一个普通成员变量)调用了继承自IMessageFilterUI的回调接口MessageHandler,一样,要预先添加到数组中;
bool CPaintManagerUI::AddPreMessageFilter(IMessageFilterUI* pFilter)
{
ASSERT(m_aPreMessageFilters.Find(pFilter)<0);
return m_aPreMessageFilters.Add(pFilter);
}
这个在OnCreate中调用了
LRESULT WindowImplBase::OnCreate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
LONG styleValue = ::GetWindowLong(*this, GWL_STYLE);
styleValue &= ~WS_CAPTION;
::SetWindowLong(*this, GWL_STYLE, styleValue | WS_CLIPSIBLINGS | WS_CLIPCHILDREN);
// CPaintManagerUI::Init 关联UI管理器 添加到静态指针数组 m_aPreMessages.Add(this);
m_pm.Init(m_hWnd, GetManagerName());
// 注册PreMessage回调
m_pm.AddPreMessageFilter(this);
...
}
至此,第一次消息过滤完成。
我们只需要实现好继承自IMessageFilterUI的回调接口MessageHandler即可。
其中,WindowImplBase的CPaintManagerUI m_pm在WindowImplBase::OnCreate中被初始化,这个m_pm被添加到静态指针数组m_aPreMessages中;
同样的,在WindowImplBase::OnCreate中IMessageFilterUI的回调接口MessageHandler也被注册了。
2.消息在窗口过程处的处理 —— HandleMessage重载
实际的窗口类继承自CWindowWnd,创建窗口时,CWindowWnd::Create内部会注册窗口,注册时,会设置CWindowWnd::__WndProc为窗口过程函数;
bool CWindowWnd::RegisterWindowClass()
{
WNDCLASS wc = { 0 };
// 1.获取样式信息
wc.style = GetClassStyle();
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hIcon = NULL;
// 2.设置窗口过程函数
wc.lpfnWndProc = CWindowWnd::__WndProc;
// 3.调用绘制管理器获取示例句柄
wc.hInstance = CPaintManagerUI::GetInstance();
wc.hCursor = ::LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = NULL;
wc.lpszMenuName = NULL;
// 4.获取要注册的类名
wc.lpszClassName = GetWindowClassName();
ATOM ret = ::RegisterClass(&wc);
ASSERT(ret!=NULL || ::GetLastError()==ERROR_CLASS_ALREADY_EXISTS);
return ret != NULL || ::GetLastError() == ERROR_CLASS_ALREADY_EXISTS;
}
创建窗口:
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;
// 创建
if(m_bUnicode) {
#ifndef UNICODE
LPWSTR lpClassName = a2w((char*)GetWindowClassName());
LPWSTR lpName = a2w((char*)pstrName);
#else
LPWSTR lpClassName = (LPWSTR)GetWindowClassName();
LPWSTR lpName = (LPWSTR)pstrName;
#endif
m_hWnd = ::CreateWindowExW(dwExStyle, lpClassName, lpName, dwStyle, x, y, cx, cy, hwndParent, hMenu, CPaintManagerUI::GetInstance(), this);
#ifndef UNICODE
delete []lpClassName;
delete []lpName;
#endif
}
else {
m_hWnd = ::CreateWindowEx(dwExStyle, GetWindowClassName(), pstrName, dwStyle, x, y, cx, cy, hwndParent, hMenu, CPaintManagerUI::GetInstance(), this);
}
ASSERT(m_hWnd!=NULL);
return m_hWnd;
}
现在来看窗口过程函数:
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 ) {
// 窗口自定义的消息处理 子类可以通过虚函数的特征来修改这个窗口消息函数。目前CWindowWnd::HandleMessage内部默认呼叫的窗口过程函数
return pThis->HandleMessage(uMsg, wParam, lParam);
}
else {
// 和win32一样 默认处理函数
return ::DefWindowProc(hWnd, uMsg, wParam, lParam);
}
}
所以,我们可以自己重载继承自CWindowWnd的虚函数接口HandleMessage,窗口过程函数的处理都在这个接口中。
至此,我们又知道了在窗口处理过程中,去处理消息。
3.控件消息的调用1 —— 看WindowImplBase如何利用的上两种消息处理
刚刚知道了一些消息接口,现在以WindowImplBase为例,观察其实现:
- 消息预过滤,实现继承自IMessageFilterUI的回调接口MessageHandler,实现分发前的处理;
LRESULT WindowImplBase::MessageHandler(UINT uMsg, WPARAM wParam, LPARAM /*lParam*/, bool& /*bHandled*/)
{
if (uMsg == WM_KEYDOWN)
{
switch (wParam)
{
case VK_RETURN:
case VK_ESCAPE:
// 这个也是自己去实现的 这里是空的
return ResponseDefaultKeyEvent(wParam);
default:
break;
}
}
return FALSE;
}
LRESULT WindowImplBase::ResponseDefaultKeyEvent(WPARAM wParam)
{
if (wParam == VK_RETURN)
{
return FALSE;
}
else if (wParam == VK_ESCAPE)
{
return TRUE;
}
return FALSE;
}
- 窗口过程中的处理,重载继承自CWindowWnd的虚函数接口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;
#endif
case 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;
}
// 这里我们可以看到 bHandled 为true时 会阻止消息的进一步传递
if (bHandled) return lRes;
// 也可以去实现 和本窗口过程函数相关的 这里是空的
lRes = HandleCustomMessage(uMsg, wParam, lParam, bHandled);
if (bHandled) return lRes;
// CPaintManager::MessageHandler 非静态 里面处理了大量常用事件 Notify事件也在这个里面
// 本窗口的绘制管理器管理的 和其他的绘制管理器无关
if (m_pm.MessageHandler(uMsg, wParam, lParam, lRes))
return lRes;
// 再调用父类的HandleMessage 内部直接调用了窗口过程::CallWindowProc(m_OldWndProc, m_hWnd, uMsg, wParam, lParam);
return CWindowWnd::HandleMessage(uMsg, wParam, lParam);
}
HandleCustomMessage我们后续也可以自己重载,其他的不看,我们直接来看m_pm.MessageHandler;
// 处理与窗口相关的使用频率非常高的消息 它接受到了这些消息,然后通过内部的事件机制,转发给相应的子控件。
bool CPaintManagerUI::MessageHandler(UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT& lRes)
{
if( m_hWndPaint == NULL ) return false;
// Cycle through listeners
for( int i = 0; i < m_aMessageFilters.GetSize(); i++ )
{
bool bHandled = false;
// 事件过滤器 需要过滤的子控件需要先实现接口AddMessageFilter
// 这里调用的是子控件的MessageHandler接口,子控件在DoInit初始化时,需调用AddMessageFilter把自己添加到m_aMessageFilters指针数组中
// 在控件释放时,需要调用RemoveMessageFilter进行释放
LRESULT lResult = static_cast<IMessageFilterUI*>(m_aMessageFilters[i])->MessageHandler(uMsg, wParam, lParam, bHandled);
if( bHandled ) {
// 处理完之后还接着处理
// ...
}
}
// 是否需要分层 带有WS_EX_LAYERED风格的窗口也就是我们说的分层窗口,主要是为了了实现异形窗口和窗口整体透明。
if( m_bLayered ) {
// ...
}
// Custom handling of events
switch( uMsg ) {
// 这里处理了很多duilib接收系统消息后,自己内部需要用的东西,大多数控件的默认事件等都在这
// CPaintManagerUI把DUILIB内部的事件都是用TEventUI结构的形式调用CControlUI类的Event函数来投递的.
}
}
这里,我们又发现一个控件的接口MessageHandler,其实也是继承自消息过滤器IMessageFilterUI。
之前的MessageHandler是在窗口中调用的,控件其实也可以是窗口,不过这里这样写可以区分消息等级,是窗口消息还是控件消息。
这样其实也方便管理和继承,比如我们后续要自己封装一个控件,内部消息较多,这样的话,更有条理。
如:class UILIB_API CRichEditUI : public CContainerUI, public IMessageFilterUI,再看其MessageHandler的实现。内部处理了一些东西。
void CRichEditUI::DoInit(){ ... // m_aMessageFilters m_pManager->AddMessageFilter(this); ... }
对比:
LRESULT WindowImplBase::OnCreate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled){ ... // 注册PreMessage回调 m_pm.AddPreMessageFilter(this); ... }
4.控件消息的调用2,3 —— 委托和INotifyUI接口
接着上面的说,
bool CPaintManagerUI::MessageHandler(UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT& lRes){
//...
//
switch( uMsg ) {
// 这里处理的都是异步消息 只是实现的方式有两种,一种委托,一种是INotifyUI接口.
case WM_APP + 1:
{
// 异步通知的处理
// 控件延迟销毁对象
for( int i = 0; i < m_aDelayedCleanup.GetSize(); i++ )
delete static_cast<CControlUI*>(m_aDelayedCleanup[i]);
m_aDelayedCleanup.Empty();
m_bAsyncNotifyPosted = false;
// 调用Notify接口
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);
}
// 每个控件都有一个的,注册就可以
// 这里处理剩余的异步消息,没有委托回调的时候,在这里还有机会进行一次处理,
// 但是这里会遍历所有的Notify接口,pMsg会传递到所有接口中,所以我们需要对控件名,消息类型等进行区分。
for( int j = 0; j < m_aNotifiers.GetSize(); j++ ) {
// 第二种 : 自己继承并实现的接口 Notify
static_cast<INotifyUI*>(m_aNotifiers[j])->Notify(*pMsg);
}
delete pMsg;
}
}
// ...
}
}
方式一:
CEventSource OnNotify; 属于CControlUI类,重载了一些操作符,如operator();
要让控件响应手动发送(SendNotify)的消息,就要给控件的OnNotify添加消息代理,诸如:
CButtonUI* pButton = static_cast<CButtonUI*>(pItem->FindSubControl(_T("music_item_delete"))); pButton->OnNotify += MakeDelegate(this, &RFMainWindow::OnMusicItemDelete);
将类的成员函数作为回调函数,加入到OnNotify中,然后调用
if( pMsg->pSender->OnNotify ) pMsg->pSender->OnNotify(pMsg);
循环调用所有的类函数,实现通知的效果。
当程序某个地方调用了CPaintManager::SendNotify,并且Msg.pSender正好是注册的this,我们的类成员函数将被调用。
这个消息可以手动来分发,也分为同步调用(立即调用,类似于SendMessage)和异步调用(丢到队列后续再处理,类似于PostMessage)
void CPaintManagerUI::SendNotify(TNotifyUI& Msg, bool bAsync /*= false*/) { Msg.ptMouse = m_ptLastMousePos; Msg.dwTimestamp = ::GetTickCount(); if( m_bUsedVirtualWnd ) { Msg.sVirtualWnd = Msg.pSender->GetVirtualWnd(); } if( !bAsync ) { // Send to all listeners if( Msg.pSender != NULL ) { // 同步调用 先直接执行响应函数,随后再和框架一起产生msg消息 响应Notify 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 { // 异步 添加到队列中 先添加 等待下一次处理 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); PostAsyncNotify(); } }
方式二:
这里又有两个指针数组,异步通知m_aAsyncNotify、通知m_aNotifiers。
其中,控件在创建的时候,需要调用m_pm.AddNotifier(this);来将自己添加到绘制管理器的m_aNotifiers数组中去,以便后续消息的实现;
WindowImplBase::OnCreate中就调用了m_pm.AddNotifier(this);
然后实现INotifyUI的Notify接口。
5.控件消息的调用4——虚拟窗口/消息泵/消息映射表
这种和Notify平级,属于一个地方,但是实现方式不一样。这个是仿造的MFC的消息映射机制,提供了一些消息宏。
和MFC原理一样,声明一些静态变量存储类的信息,插入一些成员函数作为回调,最后生成一张静态表,当WindowImplBase::Notify有消息时,遍历表格进行通知
void WindowImplBase::Notify(TNotifyUI& msg)
{
return CNotifyPump::NotifyPump(msg);
}
// 实际上 我们自己继承于WindowImplBase的的类,是这样的
void MyWnd::Notify(TNotifyUI& msg)
{
// 对一些控件的响应判定,自己实现的Notify功能
// 再调用父类的,这里实际是消息泵,模仿mfc实现的静态消息表,找到对应的回调函数
WindowImplBase::Notify(msg);
}
void CNotifyPump::NotifyPump(TNotifyUI& msg)
{
// 查找本身虚拟窗口列表
// 发送消息的是虚拟窗口
if( !msg.sVirtualWnd.IsEmpty() ){
// 遍历虚拟窗口
// CNotifyPump维护的m_VirtualWndMap 调用AddVirtualWnd进行添加
for( int i = 0; i< m_VirtualWndMap.GetSize(); i++ ) {
// 虚拟窗口名
if( LPCTSTR key = m_VirtualWndMap.GetAt(i) ) {
// 消息中的窗口名是否一致 发消息的那个虚拟窗口 在m_VirtualWndMap中找到发消息的这个虚拟窗口
if( _tcsicmp(key, msg.sVirtualWnd.GetData()) == 0 ){
// 找到继承了CNotifyPump的那个子类对象的指针
CNotifyPump* pObject = static_cast<CNotifyPump*>(m_VirtualWndMap.Find(key, false));
// 调用对应的消息表进行查找
if( pObject && pObject->LoopDispatch(msg) )
return;
}
}
}
}
///
//遍历主窗口 消息表
LoopDispatch( msg );
}
它实现的功能是查找本身虚拟窗口列表,查找对应虚拟窗口对象的LoopDispatch函数,如果存在,则调用对应的LoopDispatch函数,否则调用本身的LoopDispatch函数。如下所示:
bool CNotifyPump::LoopDispatch(TNotifyUI& msg)
{
// 消息列表
const DUI_MSGMAP_ENTRY* lpEntry = NULL;
const DUI_MSGMAP* pMessageMap = NULL;
// 先获取本身的响应函数列表,接着再循环父类的响应函数列表
#ifndef UILIB_STATIC
for(pMessageMap = GetMessageMap(); pMessageMap!=NULL; pMessageMap = (*pMessageMap->pfnGetBaseMap)())
#else
for(pMessageMap = GetMessageMap(); pMessageMap!=NULL; pMessageMap = pMessageMap->pBaseMap)
#endif
{
#ifndef UILIB_STATIC
ASSERT(pMessageMap != (*pMessageMap->pfnGetBaseMap)()); // 二选一
#else
ASSERT(pMessageMap != pMessageMap->pBaseMap); // 二选一
#endif
if ((lpEntry = DuiFindMessageEntry(pMessageMap->lpEntries,msg)) != NULL)//查表 找到了对应项
{
goto LDispatch;
}
}
return false;
// 找到对应的消息,则采用对应的函数调用
LDispatch:
// mmf是一个枚举 所以mmf.pfn、mmf.pfn_Notify_lwl、mmf.pfn_Notify_vn指向了同一个函数!!!
union DuiMessageMapFunctions mmf;
// 对应项中的函数指针
mmf.pfn = lpEntry->pfn;
bool bRet = false;
int nSig;
// 对应项中的消息类别
nSig = lpEntry->nSig;
switch (nSig)
{
default:
ASSERT(FALSE);
break;
case DuiSig_lwl:
// 跟普通函数指针不同,不能使用成员函数指针直接调用成员函数,
// 而是要通过.*和->*运算符将成员函数指针和类对象或指向对象的指针绑定起来
(this->*mmf.pfn_Notify_lwl)(msg.wParam,msg.lParam);
bRet = true;
break;
case DuiSig_vn:
// 调用对应的响应函数
(this->*mmf.pfn_Notify_vn)(msg);
bRet = true;
break;
}
return bRet;
}
消息表宏的展开示例:
//eg:
//uibase.cpp中:
DUI_BASE_BEGIN_MESSAGE_MAP(CNotifyPump)
DUI_END_MESSAGE_MAP()
//winimplbase.cpp中:
DUI_BEGIN_MESSAGE_MAP(WindowImplBase, CNotifyPump)
DUI_ON_MSGTYPE(DUI_MSGTYPE_CLICK,OnClick)
DUI_END_MESSAGE_MAP()
//展开:1.函数 2.表项 3.表尾
const DUI_MSGMAP* PASCAL CNotifyPump::_GetBaseMessageMap()
{ return NULL; }
const DUI_MSGMAP* CNotifyPump::GetMessageMap() const
{ return &CNotifyPump::messageMap; }
UILIB_COMDAT const DUI_MSGMAP CNotifyPump::messageMap =
{ &CNotifyPump::_GetBaseMessageMap, &CNotifyPump::_messageEntries[0] };
UILIB_COMDAT const DUI_MSGMAP_ENTRY CNotifyPump::_messageEntries[] =
{
//-------------------------------------------------------------------------------------
{ _T(""), _T(""), 0, (DUI_PMSG)0 }
};
//-------------------------------------------------------------------------------------------------------------------------------------
const DUI_MSGMAP* PASCAL WindowImplBase::_GetBaseMessageMap() {
return &CNotifyPump::messageMap;
}
const DUI_MSGMAP* WindowImplBase::GetMessageMap() const {
return &WindowImplBase::messageMap;
}
UILIB_COMDAT const DUI_MSGMAP WindowImplBase::messageMap = { &WindowImplBase::_GetBaseMessageMap, &WindowImplBase::_messageEntries[0] };
UILIB_COMDAT const DUI_MSGMAP_ENTRY WindowImplBase::_messageEntries[] =
{
//-------------------------------------------------------------------------------------
{ "click", _T(""), 2, (DUI_PMSG)&OnClick}, //表项
//-------------------------------------------------------------------------------------
{ _T(""), _T(""), 0, (DUI_PMSG)0 } //表尾
};
消息传递过程图解
CWindWnd类
WindowImplBase类
WindowImplBase类是对CWindWnd的进一步封装,我们也可以对其再进行封装;
各绘制管理器由静态函数和静态数组联合起来,各窗口控件又由各个绘制管理器集中起来。
部分机制内部实现研究
MessageHandler : 我们可以先只读前半部分,重点在消息本身上,也就是进行消息的过滤(其实过滤也是一种处理)
HandleMessage : 我们可以先只读前半部分,重点在处理过程中,也就是对应窗口过程进行消息的处理
1. IMessageFilterUI预过滤接口MessageHandler
//注册 this是继承了IMessageFilterUI接口的子类
m_pm.Init(m_hWnd, GetManagerName());
m_pm.AddPreMessageFilter(this);
//实现
//继承自IMessageFilterUI的回调接口MessageHandler
2. 窗口过程处理函数HandleMessage及其重载
2.1 Onxxx形式的虚函数
//重载内部定义的一些On开头的虚函数
virtual LRESULT OnCreate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
...
virtual LRESULT OnLButtonDown(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled);
...
2.2 虚函数HandleCustomMessage
重载实现
2.3 IMessageFilterUI过滤接口MessageHandler
//this 是继承了IMessageFilterUI接口的子类
m_pManager->AddMessageFilter(this);
//实现
//继承自IMessageFilterUI的回调接口MessageHandler
2.4 绘制管理器的处理CPaintManager::MessageHandler
2.4.1 委托机制OnNotify的实现
委托的原理
/* 委托的原理 : 这里把回调函数的信息存起来了
*
* CDelegateBase : 一个对象指针,一个对象的成员函数地址 纯虚函数invoke、Copy;
*
* CDelegateStatic : 实际回调调用类1(内部定义了一个通用的函数指针)使用类的全局静态函数,无对象参数,直接给入参数调用
* CDelegate : 实际回调调用模板类2(在1的基础上加了对象指针类型和类名作为模板参数)使用实例对象和其成员函数地址;
*
* MakeDelegate : 再一次抽象,对上述CDelegateStatic、CDelegate进行实例化创建;
没调用copy的时候,相关信息存储在栈中,返回的局部变量??? 进行暂时回调???
*/
/* 事件源对象 : CEventSource
* 功能 : 内部有一个指针数组,对所有的委托进行管理,所有的委托都在这里面;
* 添加方式1 : 遍历数组,若已存在当前委托事件,不再添加;否则以委托为参数构建副本进行添加;
* 添加方式2 : 对应静态函数,先构造一个CDelegateStatic,再调用添加方式1
* 删减方式1,2 : 和添加方式对应
* (param) : 调用,遍历数组,成功调用回调并遇到false返回,阻断遍历,返回false;
* 成功调用回调并遇到true返回,不阻断遍历,继续遍历;
* 否则返回true;
* 清空操作 :
*/
#ifndef __UIDELEGATE_H__
#define __UIDELEGATE_H__
#pragma once
namespace DuiLib {
class UILIB_API CDelegateBase
{
public:
CDelegateBase(void* pObject, void* pFn); //构造方式1:对象指针和对应的成员函数地址
CDelegateBase(const CDelegateBase& rhs); //构造方式2:从现有的委托中复制
virtual ~CDelegateBase(); //啥都不干
bool Equals(const CDelegateBase& rhs) const;//判断两个委托是否一样
bool operator() (void* param); //用于调用Invoke
// 构建委托在内存中的副本
virtual CDelegateBase* Copy() const = 0; // add const for gcc
protected:
//返回两个私有成员
void* GetFn();
void* GetObject();
// 用于调用委托的实现
virtual bool Invoke(void* param) = 0;
private:
void* m_pObject;
void* m_pFn;
};
// 实现方式1 全局静态函数
class CDelegateStatic: public CDelegateBase
{
//定义Fn 一个指针函数返回bool 以通用指针为参数
typedef bool (*Fn)(void*);
public:
CDelegateStatic(Fn pFn) : CDelegateBase(NULL, pFn) { }
CDelegateStatic(const CDelegateStatic& rhs) : CDelegateBase(rhs) { }
virtual CDelegateBase* Copy() const { return new CDelegateStatic(*this); }
protected:
virtual bool Invoke(void* param)
{
Fn pFn = (Fn)GetFn();
return (*pFn)(param);
}
};
// 方式2 对象内部函数 对象::函数
template <class O, class T>
class CDelegate : public CDelegateBase
{
typedef bool (T::* Fn)(void*);
public:
CDelegate(O* pObj, Fn pFn) : CDelegateBase(pObj, &pFn), m_pFn(pFn) { }
CDelegate(const CDelegate& rhs) : CDelegateBase(rhs) { m_pFn = rhs.m_pFn; }
virtual CDelegateBase* Copy() const { return new CDelegate(*this); }
protected:
virtual bool Invoke(void* param)
{
O* pObject = (O*) GetObject();
return (pObject->*m_pFn)(param);
}
private:
Fn m_pFn;
};
// 两种常用的全局构造
template <class O, class T>
CDelegate<O, T> MakeDelegate(O* pObject, bool (T::* pFn)(void*))
{
return CDelegate<O, T>(pObject, pFn);
}
inline CDelegateStatic MakeDelegate(bool (*pFn)(void*))
{
return CDelegateStatic(pFn);
}
// 委托对象真正的使用位置,事件源对象,它的内部就是委托对象的指针数组
// 事件源类在Duilib中使用位置,就在CControlUI这个所有控件的基类里
// 上述5种事件源,特别是OnNotify事件源,参数实现duilib中的自定义消息循环。
class UILIB_API CEventSource
{
typedef bool (*FnType)(void*);
public:
~CEventSource();
operator bool();
void operator+= (const CDelegateBase& d); // add const for gcc
void operator+= (FnType pFn);
void operator-= (const CDelegateBase& d);
void operator-= (FnType pFn);
bool operator() (void* param);
void Clear();
protected:
// 指针数组
CStdPtrArray m_aDelegates;
};
} // namespace DuiLib
#endif // __UIDELEGATE_H__
#include "StdAfx.h"
namespace DuiLib {
CDelegateBase::CDelegateBase(void* pObject, void* pFn)
{
m_pObject = pObject;
m_pFn = pFn;
}
CDelegateBase::CDelegateBase(const CDelegateBase& rhs)
{
m_pObject = rhs.m_pObject;
m_pFn = rhs.m_pFn;
}
CDelegateBase::~CDelegateBase()
{
}
bool CDelegateBase::Equals(const CDelegateBase& rhs) const
{
return m_pObject == rhs.m_pObject && m_pFn == rhs.m_pFn;
}
bool CDelegateBase::operator() (void* param)
{
return Invoke(param);
}
void* CDelegateBase::GetFn()
{
return m_pFn;
}
void* CDelegateBase::GetObject()
{
return m_pObject;
}
CEventSource::~CEventSource()
{
for( int i = 0; i < m_aDelegates.GetSize(); i++ ) {
CDelegateBase* pObject = static_cast<CDelegateBase*>(m_aDelegates[i]);
if( pObject) delete pObject;
}
}
CEventSource::operator bool()
{
return m_aDelegates.GetSize() > 0;
}
void CEventSource::operator+= (const CDelegateBase& d)
{
for( int i = 0; i < m_aDelegates.GetSize(); i++ ) {
CDelegateBase* pObject = static_cast<CDelegateBase*>(m_aDelegates[i]);
if( pObject && pObject->Equals(d) ) return;
}
m_aDelegates.Add(d.Copy());
}
void CEventSource::operator+= (FnType pFn)
{
(*this) += MakeDelegate(pFn);
}
void CEventSource::operator-= (const CDelegateBase& d)
{
for( int i = 0; i < m_aDelegates.GetSize(); i++ ) {
CDelegateBase* pObject = static_cast<CDelegateBase*>(m_aDelegates[i]);
if( pObject && pObject->Equals(d) ) {
delete pObject;
m_aDelegates.Remove(i);
return;
}
}
}
void CEventSource::operator-= (FnType pFn)
{
(*this) -= MakeDelegate(pFn);
}
bool CEventSource::operator() (void* param)
{
for( int i = 0; i < m_aDelegates.GetSize(); i++ ) {
CDelegateBase* pObject = static_cast<CDelegateBase*>(m_aDelegates[i]);
if( pObject && !(*pObject)(param) ) return false;
}
return true;
}
void CEventSource::Clear()
{
for( int i = 0; i < m_aDelegates.GetSize(); i++ ) {
CDelegateBase* pObject = static_cast<CDelegateBase*>(m_aDelegates[i]);
if( pObject) delete pObject;
}
m_aDelegates.Empty();
}
} // namespace DuiLib
委托函数返回false,可以阻断遍历? 是
事件源委托数组在duilib中的实现
控件基类 CControlUI 中维护了一个事件源对象,所以所有后续duilib子类对象都可以用这个
每一个控件都可以有一个事件源对象存储委托回调相关信息
class UILIB_API CControlUI{
...
CEventSource OnNotify;
...
}
所以添加委托事件的时候,我们直接用这个成员:
每个控件可以委托多个回调函数到一个事件源对象中去,重复的只保留一个,然后各个控件的事件源对象互不干扰.
所以:
bool CEventSource::operator() (void* param) { for( int i = 0; i < m_aDelegates.GetSize(); i++ ) { CDelegateBase* pObject = static_cast<CDelegateBase*>(m_aDelegates[i]); if( pObject && !(*pObject)(param) ) return false; } return true; }
我们可以通过参数param类型来区分回调函数是哪一个,不是要求的类型直接在回调函数中返回true即可,不会阻断遍历.
遇到正确的函数,执行完,我们可以返回false,表示完成响应.
不过一般只用一个就可以了,然后参数是TNotifyUI* 的pMsg.
pOption->OnNotify += MakeDelegate(this, &CMainWnd::OnNotify1);
pOption->OnNotify += MakeDelegate(this, &CMainWnd::OnNotify2);
需要调用的时候 这个在CPaintManagerUI::MessageHandler的case WM_APP + 1:下有调用到.
// OnNotify(pMsg)就会调用重载的CEventSource(param)对委托数组各元素底层的invoke进行遍历调用
// 注意,此时是针对某一个控件而言的,这里只是负责了绑定,至于是该控件的什么动作触发的,我们不知道,可以在回调函数内部进行区分
pMsg->pSender->OnNotify(pMsg);
委托事件的触发
注意,同步异步只是区分立即处理还是丢到消息队列中再等待处理,和具体的处理方式(委托还是Notify接口无关).
void CPaintManagerUI::SendNotify(TNotifyUI& Msg, bool bAsync /*= false*/)
{
Msg.ptMouse = m_ptLastMousePos;
Msg.dwTimestamp = ::GetTickCount();
// 虚窗口的获取,为消息泵做准备
if( m_bUsedVirtualWnd )
{
Msg.sVirtualWnd = Msg.pSender->GetVirtualWnd();
}
if( !bAsync ) {
// 同步处理
// Send to all listeners
// 和 " CPaintManagerUI::MessageHandler的case WM_APP + 1 " 下一样的,两种机制,一种委托,一种INotifyUI接口.
if( Msg.pSender != NULL ) {
if( Msg.pSender->OnNotify ) Msg.pSender->OnNotify(&Msg);
}
for( int i = 0; i < m_aNotifiers.GetSize(); i++ ) {
static_cast<INotifyUI*>(m_aNotifiers[i])->Notify(Msg);
}
}
else {
// 异步处理
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);
// 给窗口发送异步消息 " WM_APP + 1 "
PostAsyncNotify();
}
}
至于这个CPaintManagerUI::SendNotify;
他在很多地方被调用,
1.自动调用 : CPaintManagerUI::Messagehandler中,把win32事件转化成控件事件;
2.手动触发:主动调用这个函数,很多控件里面都有用到.
2.4.2 INotifyUI接口的实现
控件消息处理
注册;
m_pm.AddNotifier(this);
消息的生成/发送/接收,和委托的消息是一样的,见2.4.1;
消息的处理,在继承了INotifyUI的类的Notify接口中,内部注意区分控件名和消息类型,否则会多重响应;
void Notify(TNotifyUI& msg) { if( msg.sType == _T("click") ) { // 按钮消息 OnLClick(msg.pSender); } } //各自的UI消息在各自的窗口中进行处理 比ihealth有条理一点 void OnLClick(CControlUI *pControl) { CDuiString sName = pControl->GetName(); // 区分是哪一个控件的 if(sName.CompareNoCase(_T("skin_image_btn")) == 0) { // ... } else if(sName.CompareNoCase(_T("skin_color_btn")) == 0) { // ... } }
消息泵的实现
注册,添加虚窗口,哪个类要用就添加一下自己;
//窗口名,CNotifyPump子类对象地址 AddVirtualWnd(_T("mainpage"),&m_MainPage);
消息表的初始化等,见消息机制中的第5小节,有个宏展开示例;
消息的生成/发送/接收,和委托的消息是一样的,见2.4.1;
我们可以在本控件的Notify后面调用CNotifyPump::NotifyPump(msg)对消息进行处理.这个也是只和本控件相关;
void CNotifyPump::NotifyPump(TNotifyUI& msg) { // 查找本身虚拟窗口列表 // 发送消息的是虚拟窗口 if( !msg.sVirtualWnd.IsEmpty() ){ // 遍历虚拟窗口 // CNotifyPump维护的m_VirtualWndMap 调用AddVirtualWnd进行添加 for( int i = 0; i< m_VirtualWndMap.GetSize(); i++ ) { // 虚拟窗口名 if( LPCTSTR key = m_VirtualWndMap.GetAt(i) ) { // 消息中的窗口名是否一致 发消息的那个虚拟窗口 在m_VirtualWndMap中找到发消息的这个虚拟窗口 if( _tcsicmp(key, msg.sVirtualWnd.GetData()) == 0 ){ // 找到继承了CNotifyPump的那个子类对象的指针 CNotifyPump* pObject = static_cast<CNotifyPump*>(m_VirtualWndMap.Find(key, false)); // 调用对应的消息表进行查找 if( pObject && pObject->LoopDispatch(msg) ) return; } } } } //子控件没有 那就遍历主窗口 LoopDispatch( msg ); }
LoopDispatch会查表然后指向对应的函数;
WindowImplBase消息泵过程示例
// WindowImplBase.h class xxx : : public CNotifyPump { ... //静态声明宏 DUI_DECLARE_MESSAGE_MAP() virtual void OnXxx(TNotifyUI& msg); ... } // WindowImplBase.cpp namespace DuiLib { // // 定义 DUI_BEGIN_MESSAGE_MAP(WindowImplBase, CNotifyPump) // 这 展开就是数组的初始化 把“click” 和 “OnXxx”联系起来放到表中 DUI_ON_MSGTYPE(DUI_MSGTYPE_CLICK,OnXxx) DUI_END_MESSAGE_MAP() void WindowImplBase::OnXxx(TNotifyUI& msg) { CDuiString sCtrlName = msg.pSender->GetName(); if( sCtrlName == _T("closebtn") ) { Close(); return; } ... return; } ... }
2.4.3 点击事件的实现过程
bool CPaintManagerUI::MessageHandler(UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT& lRes)
{
...
// 鼠标左键消息
case WM_LBUTTONDOWN:
{
// We alway set focus back to our app (this helps
// when Win32 child windows are placed on the dialog
// and we need to remove them on focus change).
if (!m_bNoActivate) ::SetFocus(m_hWndPaint);
if( m_pRoot == NULL ) break;
// 查找控件
POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
m_ptLastMousePos = pt;
CControlUI* pControl = FindControl(pt);
if( pControl == NULL ) break;
if( pControl->GetManager() != this ) break;
// 击中控件的绘制
if(m_bDragDrop && pControl->IsDragEnabled()) {
m_bDragMode = true;
if( m_hDragBitmap != NULL ) {
::DeleteObject(m_hDragBitmap);
m_hDragBitmap = NULL;
}
m_hDragBitmap = CRenderEngine::GenerateBitmap(this, pControl, pControl->GetPos());
}
// 开启捕获
SetCapture();
// 事件处理
m_pEventClick = pControl;
pControl->SetFocus();
// 消息转事件
TEventUI event = { 0 };
// 鼠标点击事件
event.Type = UIEVENT_BUTTONDOWN;
event.pSender = pControl;
event.wParam = wParam;
event.lParam = lParam;
event.ptMouse = pt;
event.wKeyState = (WORD)wParam;
event.dwTimestamp = ::GetTickCount();
// 调用事件处理函数 和委托有关 DoEvent
pControl->Event(event);
}
break;
...
}
Event、DoEvent均为虚函数,我们的子控件可以重载,再实现对应的功能
基础的的CControlUI::DoEvent 这里面没有对UIEVENT_BUTTONDOWN进行处理
virtual void Event(TEventUI& event);
virtual void DoEvent(TEventUI& event);
---------------------------------------------------------------------------------------------------------------------------------------
void CControlUI::Event(TEventUI& event)
{
if( OnEvent(&event) ) DoEvent(event);
}
void CControlUI::DoEvent(TEventUI& event)
{
if( event.Type == UIEVENT_SETCURSOR ) {
if( GetCursor() ) {
::SetCursor(::LoadCursor(NULL, MAKEINTRESOURCE(GetCursor())));
}
else {
::SetCursor(::LoadCursor(NULL, MAKEINTRESOURCE(IDC_ARROW)));
}
return;
}
if( event.Type == UIEVENT_SETFOCUS )
{
m_bFocused = true;
Invalidate();
return;
}
if( event.Type == UIEVENT_KILLFOCUS )
{
m_bFocused = false;
Invalidate();
return;
}
if( event.Type == UIEVENT_TIMER )
{
m_pManager->SendNotify(this, DUI_MSGTYPE_TIMER, event.wParam, event.lParam);
return;
}
if( event.Type == UIEVENT_CONTEXTMENU )
{
if( IsContextMenuUsed() ) {
m_pManager->SendNotify(this, DUI_MSGTYPE_MENU, event.wParam, event.lParam);
return;
}
}
if( m_pParent != NULL ) m_pParent->DoEvent(event);
}
现在来看按钮的DoEvent如何实现的:
void CButtonUI::DoEvent(TEventUI& event)
{
if( !IsMouseEnabled() && event.Type > UIEVENT__MOUSEBEGIN && event.Type < UIEVENT__MOUSEEND ) {
if( m_pParent != NULL ) m_pParent->DoEvent(event);
else CLabelUI::DoEvent(event);
return;
}
if( event.Type == UIEVENT_SETFOCUS )
{
Invalidate();
}
if( event.Type == UIEVENT_KILLFOCUS )
{
Invalidate();
}
if( event.Type == UIEVENT_KEYDOWN )
{
if (IsKeyboardEnabled()) {
if( event.chKey == VK_SPACE || event.chKey == VK_RETURN ) {
Activate();
return;
}
}
}
// 鼠标单击双击事件
if( event.Type == UIEVENT_BUTTONDOWN || event.Type == UIEVENT_DBLCLICK)
{
if( ::PtInRect(&m_rcItem, event.ptMouse) && IsEnabled() ) {
m_uButtonState |= UISTATE_PUSHED | UISTATE_CAPTURED;
Invalidate();
// 一样的 又回到了一样的事件触发方式SendNotify
if(IsRichEvent()) m_pManager->SendNotify(this, DUI_MSGTYPE_BUTTONDOWN);
}
return;
}
if( event.Type == UIEVENT_MOUSEMOVE )
{
if( (m_uButtonState & UISTATE_CAPTURED) != 0 ) {
if( ::PtInRect(&m_rcItem, event.ptMouse) ) m_uButtonState |= UISTATE_PUSHED;
else m_uButtonState &= ~UISTATE_PUSHED;
Invalidate();
}
return;
}
if( event.Type == UIEVENT_BUTTONUP )
{
if( (m_uButtonState & UISTATE_CAPTURED) != 0 ) {
m_uButtonState &= ~(UISTATE_PUSHED | UISTATE_CAPTURED);
Invalidate();
if( ::PtInRect(&m_rcItem, event.ptMouse) ) Activate();
}
return;
}
if( event.Type == UIEVENT_CONTEXTMENU )
{
if( IsContextMenuUsed() ) {
m_pManager->SendNotify(this, DUI_MSGTYPE_MENU, event.wParam, event.lParam);
}
return;
}
if( event.Type == UIEVENT_MOUSEENTER )
{
if( IsEnabled() ) {
m_uButtonState |= UISTATE_HOT;
Invalidate();
if(IsRichEvent()) m_pManager->SendNotify(this, DUI_MSGTYPE_MOUSEENTER);
}
}
if( event.Type == UIEVENT_MOUSELEAVE )
{
if( IsEnabled() ) {
m_uButtonState &= ~UISTATE_HOT;
Invalidate();
if(IsRichEvent()) m_pManager->SendNotify(this, DUI_MSGTYPE_MOUSELEAVE);
}
}
// 兼容父类的事件响应
CLabelUI::DoEvent(event);
}
一样,调用 CPaintManagerUI::SendNotify进行消息的发送。