duidemo梳理

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

消息机制

  1. CPaintManager::MessageLoop();
  2. 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为例,观察其实现:

  1. 消息预过滤,实现继承自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;
}
  1. 窗口过程中的处理,重载继承自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接口的实现
控件消息处理
  1. 注册;

    m_pm.AddNotifier(this);
    
  2. 消息的生成/发送/接收,和委托的消息是一样的,见2.4.1;

  3. 消息的处理,在继承了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)
        {
            // ...
        }
    }
    
消息泵的实现
  1. 注册,添加虚窗口,哪个类要用就添加一下自己;

    //窗口名,CNotifyPump子类对象地址
    AddVirtualWnd(_T("mainpage"),&m_MainPage);
    
  2. 消息表的初始化等,见消息机制中的第5小节,有个宏展开示例;

  3. 消息的生成/发送/接收,和委托的消息是一样的,见2.4.1;

  4. 我们可以在本控件的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 );
    }
    
  5. LoopDispatch会查表然后指向对应的函数;

  6. 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进行消息的发送。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值