二.Duilib开发之消息系统

一.Duilib消息系统

Windows是靠消息来驱动的,所有的动作/事件都被映射成了一个消息,熟悉Duilib整个消息处理和流向,掌握消息系统的来龙去脉将有助于我们更好的追踪和定位问题!

二.Win32常识

1. 窗体

Windows窗体其实就是屏幕上的一个矩形区域,可以响应用户的输入和操作,以文本或者图形的格式来展示!

2. 句柄

句柄其实是Windows上面用来标识一个实体对象的一个ID,比如:文件句柄(标识打开的文件对象),窗体句柄(标识一个窗体对象),内核对象句柄(信号量,互斥量,事件等)

3. 消息

Windows是靠消息来驱动的,所有的动作/事件都被映射成一个消息,通过消息我们可以知晓当前发生的动作或者事件!

4. Windows消息模型

在这里插入图片描述

5. 消息队列

	1. 系统队列:操作系统维护的消息队列!
	2. 应用队列:GUI应用程序的消息队列!
	3. 消息泵:获取消息-加工消息-消息分发!

6. 消息类型

同步消息:不放置队列,直接交付窗口过程处理等待处理!PostMessage
异步消息:放置队列中,不等待异步处理!SendMessage

7. 消息处理

窗口过程:其实就是消息处理回调函数,将来会把消息交付给对应窗体的窗口过程!

三. Duilib消息系统

1. Duilib 窗体类(CWindowWnd)

//CWindowWnd 其实就是对Win32窗体的封装
//注册-创建-显示等窗口相关操作,用来标识一个Win32窗体的类
HWND CWindowWnd::Create(HWND hwndParent, LPCTSTR pstrName, DWORD dwStyle, DWORD dwExStyle, int x, int y, int cx, int cy, HMENU hMenu)
{
	//关注1:RegisterWindowClass注册窗体
    if( GetSuperClassName() != NULL && !RegisterSuperclass() ) return NULL;
    if( GetSuperClassName() == NULL && !RegisterWindowClass() ) return NULL;
    //关注2:CreateWindowEx	 创建窗体
    m_hWnd = ::CreateWindowEx(dwExStyle, GetWindowClassName(), pstrName, dwStyle, x, y, cx, cy, hwndParent, hMenu, CPaintManagerUI::GetInstance(), this/*注意看这里,它把当前窗体对象塞了进去*/);
    ASSERT(m_hWnd!=NULL);
    return m_hWnd;
}
//注册窗体
bool CWindowWnd::RegisterWindowClass()
{
    WNDCLASS wc = { 0 };
    wc.style = GetClassStyle();
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hIcon = NULL;
    wc.lpfnWndProc = CWindowWnd::__WndProc;//关注3:指定窗口过程:消息处理函数!
    wc.hInstance = CPaintManagerUI::GetInstance();
    wc.hCursor = ::LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = NULL;
    wc.lpszMenuName  = NULL;
    wc.lpszClassName = GetWindowClassName();
    ATOM ret = ::RegisterClass(&wc);
    ASSERT(ret!=NULL || ::GetLastError()==ERROR_CLASS_ALREADY_EXISTS);
    return ret != NULL || ::GetLastError() == ERROR_CLASS_ALREADY_EXISTS;
}
//窗口过程
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);
        //这里取出创建窗体时,塞进去的窗体对象this
        pThis = static_cast<CWindowWnd*>(lpcs->lpCreateParams);
        //将句柄设置到窗体类对象里面
        pThis->m_hWnd = hWnd;
        //将窗体类CWindowWnd对象塞进了对应句柄GWLP_USERDATA     
        ::SetWindowLongPtr(hWnd, GWLP_USERDATA, reinterpret_cast<LPARAM>(pThis));
		//我们在创建窗体时塞进去的窗体类对象this就和对应的窗体句柄绑定了起来
    } 
    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
    	//这里消息就从创建窗体的窗口过程流向了CWindowWnd HandleMessage
        return pThis->HandleMessage(uMsg, wParam, lParam);
    } 
    else {
        return ::DefWindowProc(hWnd, uMsg, wParam, lParam);
    }
}
总结:创建窗体主要干了以下几件事!
1.注册窗体->指定窗口过程CWindowWnd::__WndProc
2.创建窗体->将当前窗体类对象this塞了进去!
3.窗口过程:
	取出this->将this塞进句柄的GWLP_USERDATA(将窗体类对象CWindowWnd和对应窗体句柄绑定起来)
	消息流入窗口过程后->流向CWindowWnd pThis->HandleMessage(uMsg, wParam, lParam);

2.Duilib消息循环

//主程序Duilib消息泵:
CPaintManagerUI::MessageLoop();
int CPaintManagerUI::MessageLoop()
{
    MSG msg = { 0 };
    while( ::GetMessage(&msg, NULL, 0, 0) ) {
        if( !CPaintManagerUI::TranslateMessage(&msg) ) {
            ::TranslateMessage(&msg);
			::DispatchMessage(&msg);
    }
    return msg.wParam;
}
总结:消息循环,Duilib预处理拦截!
Duilib的消息循环其实就是Win32消息循环,只不过多了分发前也就是消息流向窗口过程前,流向了Duilib
CPaintManagerUI::TranslateMessage(&msg) 这里蕴含了我们后续对消息流入窗口过程,流入CWindowWnd 
HandleMessage前,进行拦截的方式(继承IMessageFilterUI,重写MessageHandler,将当前窗体对象加入
提前拦截数组m_aPreMessageFilters中,消息分发前,会先交付给Duilib一次预处理逻辑中)

3. Duilib消息处理

//消息正常DispatchMessage分发->消息也就自然流向了CWindowWnd::__WndProc->交付给对应句柄的窗体类对象this的HandleMessage去处理!

//我们重写CWindowWnd HandleMessage
LRESULT CBaseDuilibWnd::HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	//Windows原生消息,我们是否需要处理,如果需要就处理!
	LRESULT lRes = 0;
	BOOL bHandled = TRUE;
	switch (uMsg)
	{
	case WM_CREATE:
		lRes = OnCreate(uMsg, wParam, lParam, bHandled);
		break;
	case WM_DESTROY:
		PostQuitMessage(0);
		break;
	default:
		bHandled = FALSE;
	}


	if (bHandled)
	{
		return lRes;
	}
	//我们不处理原生消息,让消息流向Duilib去处理!
	if (m_pUI.MessageHandler(uMsg, wParam, lParam, lRes))
		return lRes;
	//都不处理,就流向默认的windows默认处理过程!
	return CWindowWnd::HandleMessage(uMsg, wParam, lParam);
}
//Duilib消息处理:这里面Duilib对很多消息都进行了处理并且代码很多!
//我们重点关注:
//case WM_PAINT:定位,渲染都在这里面,用了递归机制!
//case WM_LBUTTONUP:这个举个例子来看原生消息流向Duilib后,Duilib进行自身逻辑处理后如何传递逻辑控件的点击事件!
m_pUI.MessageHandler(uMsg, wParam, lParam, lRes)
/*
{
    case WM_LBUTTONUP:
        {
            POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
            m_ptLastMousePos = pt;
            if( m_pEventClick == NULL ) break;
            ReleaseCapture();
            TEventUI event = { 0 };
            event.Type = UIEVENT_BUTTONUP;
            event.pSender = m_pEventClick;
            event.wParam = wParam;
            event.lParam = lParam;
            event.ptMouse = pt;
            event.wKeyState = (WORD)wParam;
            event.dwTimestamp = ::GetTickCount();
			// By daviyang35 at 2015-6-5 16:10:13
			// 在Click事件中弹出了模态对话框,退出阶段窗口实例可能已经删除
			// this成员属性赋值将会导致heap错误
			// this成员函数调用将会导致野指针异常
			// 使用栈上的成员来调用响应,提前清空成员
			// 当阻塞的模态窗口返回时,回栈阶段不访问任何类实例方法或属性
			// 将不会触发异常
			CControlUI* pClick = m_pEventClick;
			m_pEventClick = NULL;
            pClick->Event(event);
            	if( OnEvent(&event) ) DoEvent(event);
            		DoEvent->Activate()
 						m_pManager->SendNotify(this, DUI_MSGTYPE_CLICK);
 		}
        break;		
}

4. Duilib事件

/*通过上面Duilib对windows消息处理,事件被传输到CPaintManagerUI SendNotify*/ 						
void CPaintManagerUI::SendNotify(CControlUI* pControl, LPCTSTR pstrMessage, WPARAM wParam /*= 0*/, LPARAM lParam /*= 0*/, bool bAsync /*= false*/, bool bEnableRepeat /*= true*/)
{
	//Duilib事件封装
    TNotifyUI Msg;
    Msg.pSender = pControl;
    Msg.sType = pstrMessage;
    Msg.wParam = wParam;
    Msg.lParam = lParam;
    SendNotify(Msg, bAsync, bEnableRepeat);    
}
void CPaintManagerUI::SendNotify(TNotifyUI& Msg, bool bAsync /*= false*/, bool bEnableRepeat /*= true*/)
{
    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 ) {
            if( Msg.pSender->OnNotify ) Msg.pSender->OnNotify(&Msg);
        }
        //重点看这个:其实Duilib的消息是通过Notify发送给上层
        //所以如果想处理Duilib消息只需要m_aNotifiers添加接受者,重写Notify
        //这也就是我们之前提过的:继承INotifyUI接口,重写Notify方法就可以收到Duilib消息
        for( int i = 0; i < m_aNotifiers.GetSize(); i++ ) {
            static_cast<INotifyUI*>(m_aNotifiers[i])->Notify(Msg);
        }
    }
    else {
		if( !bEnableRepeat ) {
			for( int i = 0; i < m_aAsyncNotify.GetSize(); i++ ) {
				TNotifyUI* pMsg = static_cast<TNotifyUI*>(m_aAsyncNotify[i]);
				if( pMsg->pSender == Msg.pSender && pMsg->sType == Msg.sType) {
                    if (m_bUsedVirtualWnd) pMsg->sVirtualWnd = Msg.sVirtualWnd;
					pMsg->wParam = Msg.wParam;
					pMsg->lParam = Msg.lParam;
					pMsg->ptMouse = Msg.ptMouse;
					pMsg->dwTimestamp = Msg.dwTimestamp;
					return;
				}
			}
		}

		TNotifyUI *pMsg = new TNotifyUI;
        if (m_bUsedVirtualWnd) pMsg->sVirtualWnd = Msg.sVirtualWnd;
		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);//加入到异步消息Notify

		PostAsyncNotify();
    }
}
总结:
Duilib的事件最终传递给static_cast<INotifyUI*>(m_aNotifiers[i])->Notify(Msg);
所以,我们只需要继承INotifyUI,重写Notify,加入到这个Duilib事件监听数组中,创建窗体时m_pUI.AddNotifier(this);即可!

Duilib消息系统概述:
1. 获取消息

2. Duilib消息分发预处理
2.1 具体细节:CPaintManagerUI::TranslateMessage(&msg)
2.2 实现拦截:继承IMessageFilterUI,重写MessageHandler,创建窗体时加入到消息预处理数组中:m_pUI.AddPreMessageFilter(this);

3. 加工消息

4. 分发消息
4.1 消息分发!
4.2 创建窗体时指定的窗体过程CWindowWnd __WndProc
4.3 Duilib 窗体类处理函数CWindowWnd HandleMessage
4.4 自己处理,Duilib处理(接收Duilib事件:继承INotifyUI,重写Notify,创建窗体时加入到Duilib事件接收数组中:m_pUI.AddNotifier(this);),系统默认处理!

以上就是Duilib消息系统的一个大致流程,更多细节需要调试跟踪具体代码!

作者: 祁莫问.

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值