duilib消息产生以及响应机制

news2024/12/28 5:21:57

1、duilib消息产生处理原理

win32消息产生机制

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

duilib是基于windows的ui框架,其消息产生处理机制和windows基本一样。一个最简单的duilib程序如下:

// 程序入口及Duilib初始化部分
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE /*hPrevInstance*/, LPSTR /*lpCmdLine*/, int nCmdShow)
{
	// 加载资源
	DuilibResourceInit::InitInstance(hInstance, UILIB_ZIP, L"");
	DuilibResourceInit::Load();
	//创建主窗口
	TsetControl* pFrame = new TsetControl();
	pFrame->Create(NULL, L"example", UI_WNDSTYLE_FRAME^WS_THICKFRAME, WS_EX_WINDOWEDGE);
	pFrame->CenterWindow();
	pFrame->ShowWindow(true);
	CPaintManagerUI::MessageLoop();
	return 0;
}

在这里插入图片描述
上图是一个duilib窗体调用过程,和win32消息产生处理机制基本一致。重点看一下**MessageLoop()**中的代码:

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

其中**GetMessage()**方法就是从消息队列中取对应的消息,**TranslateMessage()**将消息进行预处理,**DispatchMessage()**将消息分发至窗体过程函数进行处理。

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

TranslateMessage()中,看一下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;
			}
		}
		......
	}

从这里我们可以知道,只要我们窗体实现了IMessageFilterUI()接口,然后调用CPaintManagerUI::AddPreMessageFilter,将我们的窗口类实例指针添加到CPaintManagerUI::m_aPreMessageFilters 数组中。当消息到达窗口过程之前,就会会先调用我们的窗口类的成员函数:MessageHandler。

	bool CPaintManagerUI::AddPreMessageFilter(IMessageFilterUI* pFilter)
	{
		ASSERT(m_aPreMessageFilters.Find(pFilter)<0);
		return m_aPreMessageFilters.Add(pFilter);
	}

上面AddPreMessageFilter就是往m_aPreMessageFilters数组中添加IMessageFilterUI对象。

回顾函数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, LPCTSTR pstrName)
	{
		if( m_hWndPaint != hWnd ) {
			m_hWndPaint = hWnd;
			m_hDcPaint = ::GetDC(hWnd);
			m_aPreMessages.Add(this);
		}
	}

看来,m_aPreMessages存储的类型为CPaintManagerUI* ,也就说,这个静态成员数组里,存储了当前进程中所有的CPaintManagerUI实例指针,所以,如果有多个CPaintManagerUI实例, 也不会存在过滤问题,互不干扰,都能各自过滤。
不知道大家有个疑问没有,CPaintManagerUI是何时进行初始化的,其实是在加载资源的时候进行实例化的。

然后再说,消息抵达窗口过程后,如何处理。首先,要清楚,窗口过程在哪儿?使用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) {
		// 先做一遍预处理,以便做一些特殊处理(为了通用性)
		if (pThis->PreHandleMessage(hWnd, uMsg, wParam, lParam)){
			return true;
		}
		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函数,当消息发送到窗口过程中时,最先进行过滤。

2、Notify响应原理

下面继续看看**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 listeners
    for( 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。所以,入门教程里会在HandleMessage响应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,注意不是Notify
        if( 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;

class UILIB_API CEventSource
{
    typedef bool (*FnType)(void*);
public:
	CEventSource();
    ~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;
private:
	std::shared_ptr<char> ptr_; //生命周期监控
};

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

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

查阅CSliderUI代码,发现他在自身的DoEvent函数(也是在messageHandler中触发的)内调用了诸如:

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 listeners
    for( 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;
        }
}
… …

这个片段是在,异步OnNotifyNofity消息响应,被调用后。才被调用的,优先级也就是最低。但它始终会被调用,因为异步OnNotifyNofity消息响应没有返回值,不会因为消息已经被处理,而直接退出。DuiLib再次给用户一个处理消息的机会。用户可以选择将bHandled设置 为True,从而终止消息继续传递。我觉得,这个通常是为了弥补OnNotifyNofity没有返回值的问题,在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;
#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;
	}
	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消息之前进行的过滤。
又多了两种方式:
1、重载父类:WindowImplBase的虚函数。
2、重载父类: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_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;
		}
	}
... ...

代码量过多,这里进行原理说明,和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的消息映射。

3、DUILIB的消息处理HandleMessage

duilib还提供了另外一种响应的方法,即消息映射DUI_BEGIN_MESSAGE_MAP,可以将DUI_MSGTYPE_CLICK消息映射到指定的函数(比如OnClick),这和在Notify判断msg.sType是一样的效果,具体请参见duilibRichListDemo

先看看下面几段代码:

DUI_BEGIN_MESSAGE_MAP(CPage1, CNotifyPump)
    DUI_ON_MSGTYPE(DUI_MSGTYPE_CLICK,OnClick)
    DUI_ON_MSGTYPE(DUI_MSGTYPE_SELECTCHANGED,OnSelectChanged)
    DUI_ON_MSGTYPE(DUI_MSGTYPE_ITEMCLICK,OnItemClick)
DUI_END_MESSAGE_MAP()

LRESULT HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    LRESULT lRes = 0;
    BOOL bHandled = TRUE;
    switch (uMsg)
    {
    case WM_KEYDOWN:        lRes = OnKeyDown(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;
    default:                bHandled = FALSE; break;
    }
    if (bHandled) return lRes;
 
    return CWindowWnd::HandleMessage(uMsg, wParam, lParam);
}

void Notify(TNotifyUI& msg)
{
    if( msg.sType == _T("windowinit") )
    {
    }      
    else if( msg.sType == _T("click") )
    {
    }
}  

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/767016.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

JavaWeb课程设计项目实战(01)——项目综述

版权声明 本文原创作者&#xff1a;谷哥的小弟作者博客地址&#xff1a;http://blog.csdn.net/lfdfhl 项目背景 本项目适合JavaWeb新手作为入门学习的练手案例&#xff0c;主要技术包括&#xff1a; TomcatJSPELJSTLMySQL 该项目是一个学生信息管理系统&#xff0c;主要功能…

300-700题红题第四题和第五题

题目链接&#xff1a;332. 重新安排行程 题目链接&#xff1a;335. 路径交叉 332. 重新安排行程 是写过的详细的在回溯。只能说再写一遍还是磕磕绊绊&#xff0c;但好歹理解的比较快了。 主要就是理解一下回溯的backtrack的输出&#xff0c;不仅仅是可以输出路径的&#xff0…

竹云IDaaS | 全面融合AWS Cognito

AWS Cognito组件用户池User Pool国内暂未上线&#xff0c;身份云IDaaS可完全替代Cognito用户池&#xff0c;借助身份云进行用户身份验证并融合Coginto身份池Identity Pool&#xff0c;实现对AWS资源的无缝安全访问。 企业面临的挑战 AWS Cognito中国站暂未提供用户池User&…

Python_pymysql_与mysql交互

目录 基础功能 简单封装 源码等资料获取方法 基础功能 import pymysql from pymysql.cursors import DictCursor # 导入字典类型的游标对象# 连接数据库 db pymysql.connect(host192.168.3.109, # 数据库IP地址port3306, # 数据库端口号userroot, …

Qchart学习

目录 Qchart简介 QChartView 简介 QAbstractAxis 简介 QAbstractSeries 简介 Qchart Public Types Properties属性 Public Functions QAbstractSeries Public Types Properties Public Functions Signals信号 QAbstractAxis Properties Public Functions 主题设…

全面了解ESP-01SWiFi模块

ESP-01S是一款基于ESP8266芯片的WiFi模块&#xff0c;它提供了低成本、低功耗和高度集成的解决方案&#xff0c;适用于物联网和嵌入式应用。本文将介绍ESP-01S模块的功能和特点&#xff0c;并提供一个简单的WiFi控制示例。 目录 ESP-01S模块管脚功能&#xff1a; ESP-01S特点…

字符串列表分类求平均值

给定一字符串列表数据&#xff0c;按颜色分类计算价格平均值并写入列表。 (本笔记适合对python字符串和列表基本烂熟的 coder 翻阅) 【学习的细节是欢悦的历程】 Python 官网&#xff1a;https://www.python.org/ Free&#xff1a;大咖免费“圣经”教程《 python 完全自学教程…

【C++修炼之路】模板初阶

&#x1f451;作者主页&#xff1a;安 度 因 &#x1f3e0;学习社区&#xff1a;StackFrame &#x1f4d6;专栏链接&#xff1a;C修炼之路 文章目录 一、泛型编程二、函数模板1、概念2、格式3、函数模板实例化① 隐式② 显式 4、特性 五、类模板六、模板使内置类型"升级&q…

无人驾驶中识别颜色并跟踪的优化(加入PID算法控制)

我们了解到无人驾驶是如何去识别颜色的&#xff0c;以及无人车能够跟随颜色目标的演示。回到现实中我们发现&#xff0c;无人车的速度控制是很关键的&#xff0c;这个涉及到安全问题&#xff0c;比如等待红绿灯时&#xff0c;该减速或加速超车等这样很常见的情形&#xff0c;在…

论文笔记--OpenPrompt: An Open-source Framework for Prompt-learning

论文笔记--OpenPrompt: An Open-source Framework for Prompt-learning 1. 文章简介2. 文章概括3 文章重点技术4. 文章亮点5. 原文传送门 1. 文章简介 标题&#xff1a;OpenPrompt: An Open-source Framework for Prompt-learning作者&#xff1a;Ning Ding, Shengding Hu, We…

基于Java+SpringBoot+vue前后端分离在线视频教育平台设计实现

博主介绍&#xff1a;✌全网粉丝30W,csdn特邀作者、博客专家、CSDN新星计划导师、Java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专…

Leecode316: 去除重复字母

下面这里使用有序map——TreeMap来实现Map接口&#xff0c;但是相对顺序是不能改变的&#xff01;这样会使得后面的跑到前面去&#xff0c;所以有问题 最简单的思想肯定是暴力思想&#xff0c;就是从前往后寻找&#xff0c;一旦遇到存在的情况就. 重点在于明确两点&#xff1a…

HarmonyOS学习路之方舟开发框架—学习ArkTS语言(基本语法 五)

Styles装饰器&#xff1a;定义组件重用样式 如果每个组件的样式都需要单独设置&#xff0c;在开发过程中会出现大量代码在进行重复样式设置&#xff0c;虽然可以复制粘贴&#xff0c;但为了代码简洁性和后续方便维护&#xff0c;我们推出了可以提炼公共样式进行复用的装饰器St…

【LLM】self-instruct 构建指令微调数据集

文章目录 一、self-instruct流程二、具体过程1. 指令生成2. 分类任务识别3. 实例生成4. 过滤和后处理 三、其他部分1. 验证数据质量2. GPT3SELF-INSTRUCT生成数据的词性分析3. Rouge-L指标 Reference 一、self-instruct流程 四部曲&#xff1a;指令生成&#xff1b;分类任务识别…

Oracle 的视图

Oracle 的视图 源数据&#xff1a; -- Create table create table STU_INFO (id NUMBER not null,name VARCHAR2(8),score NUMBER(4,1),class VARCHAR2(2) ) tablespace STUDENTpctfree 10initrans 1maxtrans 255storage(initial 64Knext 1Mminextents 1maxextents unlim…

最多变的混合模式-实色混合HardMix

最多变的混合模式-实色混合HardMix 之前写过一篇介绍27种图层混合模式的非常详细&#xff0c;如果你想完全了解底层的原理&#xff0c;这篇文章不会让你失望。 PS图层混合模式超详细解答-图层混合模式的原理 - 王先生的副业的文章 - 知乎 https://zhuanlan.zhihu.com/p/64396…

从小白到大神之路之学习运维第63天--------zabbix企业级监控(概述、单台服务器监控本身安装部署)

第三阶段基础 时 间&#xff1a;2023年7月18日 参加人&#xff1a;全班人员 内 容&#xff1a; zabbix企业级监控 目录 一、Zabbix概述 &#xff08;一&#xff09;Zabbix简介 &#xff08;二&#xff09;Zabbix运行条件&#xff1a; &#xff08;三&#xff09;Zab…

深入解析 YAML 配置文件:从语法到最佳实践

一、认识YAML YAML&#xff08;YAML Aint Markup Language&#xff09;是一种人类可读的数据序列化语言。它的设计目标是使数据在不同编程语言之间交换和共享变得简单。YAML采用了一种简洁、直观的语法&#xff0c;以易于阅读和编写的方式表示数据结构。YAML广泛应用于配置文件…

探索开源图片编辑工具:定制化编辑,激发想象

图片编辑是现代生活中广泛使用的技术&#xff0c;它不仅能够改善照片和图像的质量&#xff0c;还能创造出令人赞叹的视觉效果。随着开源文化的兴起&#xff0c;越来越多的开源工具涌现出来&#xff0c;为我们提供了实用且灵活的图片编辑功能。这些开源工具的出现为个人、设计师…

想用vivo手机设置一个5天后提醒我的闹铃,怎么设置?

在生活和工作中有很多待办事项&#xff0c;都不是需要当前立刻就去完成的&#xff0c;而且需要我们提前记住&#xff0c;并且在未来的某个指定日期去完成&#xff0c;例如两天后提交项目报告、下周五的重要会议、考试报名时间等。如果担心自己忘记这些待办事项&#xff0c;应该…