duilib中消息的流程十分复杂,窗口想处理消息,可以通过重载以下几个函数来实现:
- virtual LRESULT MessageHandler(UINT uMsg, WPARAM wParam, LPARAM lParam, bool& bHandled);
- virtual LRESULT HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam);
- virtual void Notify(TNotifyUI& msg)
(参考:http://www.cppblog.com/suiaiguo/archive/2009/07/18/90412.html)
从消息的发送途径来看,消息可以分成2种:队列消息和非队列消息。消息队列可以分成系统消息队列和线程消息队列。系统消息队列由Windows维护,线程消息队列则由每个GUI线程自己进行维护。队列消息送到系统消息队列,然后到线程消息队列,一般来讲,系统总是将消息Post在消息队列的末尾(有时为了效率,也会有些小聪明:如同一个窗口的多个 WM_PAINT被合并成一个 WM_PAINT 消息)。非队列消息直接送给目的窗口过程。
对于队列消息,最常见的是鼠标和键盘触发的消息,例如WM_MOUSERMOVE, WM_CHAR等消息,还有一些其它的消息,例如:WM_PAINT、 WM_TIMER和WM_QUIT。当鼠标、键盘事件被触发后,相应的鼠标或键盘驱动程序就会把这些事件转换成相应的消息,然后输送到系统消息队列,由 Windows系统去进行处理。Windows系统则在适当的时机,从系统消息队列中取出一个消息,然后把取出的消息送往创建窗口的线程的相应队列。线程看到自己的消息队列中有消息,就从队列中取出来,发送到合适的窗口过程去处理。
非队列消息将会绕过系统队列和消息队列,直接将消息发送到窗口过程,。系统发送非队列消息通知窗口,系统发送消息通知窗口。例如,当用户激活一个窗口系统发送WM_ACTIVATE, WM_SETFOCUS, WM_SETCURSOR。这些消息通知窗口它被激活了。非队列消息也可以由当应用程序调用系统函数产生。例如,当程序调用SetWindowPos系统发送WM_WINDOWPOSCHANGED消息。一些函数也发送非队列消息,如SendMessage()。
注意:CPaintManagerUI::MessageLoop() 函数为静态成员函数
void CPaintManagerUI::MessageLoop()
{
MSG msg = { 0 };
while( ::GetMessage(&msg, NULL, 0, 0) )
{
if( !CPaintManagerUI::TranslateMessage(&msg) )
{
::TranslateMessage(&msg); //将按键转化为ASCII字符
::DispatchMessage(&msg); //将消息分发到特定窗口的窗口过程中,消息处理完成才会返回
}
}
}
消息到来时,首先由 CPaintManagerUI::TranslateMessage(&msg) 函数进行过滤,只有通过过滤后的消息,才能分发到各个窗口。 TranslateMessage() 函数同样为静态成员函数,其实现如下(简化):
bool CPaintManagerUI::TranslateMessage(const LPMSG pMsg)
{
//1.当窗口有父窗口时,循环搜索当前的hwnd所以对应的窗口或其长辈窗口(父窗口、祖父窗口);
//2.当窗口无父窗口时,循环搜索当前的hwnd所以对应的窗口;
//如果找到,则处理;否则直接返回false
CPaintManagerUI* pT = static_cast(m_aPreMessages[i]);
if (pT->TranslateAccelerator(pMsg))
return true;
if( pT->PreMessageHandler(pMsg->message, pMsg->wParam, pMsg->lParam, lRes) )
return true;
return false;
}
其中 m_aPreMessages 为CPaintManagerUI数组,每个元素对应于每个窗口。下面将分别介绍TranslateAccelerator() 函数和 PreMessageHandler() 函数。
win32提供的同名函数 TranslateAccelerator() 有两个功能:进行消息翻译,并将消息投递到消息队列中,故如果 TranslateAccelerator(msg) 返回为true时,将不能再进行 ::TranslateMessage() 和 ::DispatchMessage(&msg)。 CPaintManagerUI的TranslateAccelerator直接对消息进行了处理,与win32提供的同名函数效果基本相同。
bool CPaintManagerUI::TranslateAccelerator(LPMSG pMsg)
{
for (int i = 0; i < m_aTranslateAccelerator.GetSize(); i++)
{
LRESULT lResult = static_cast(m_aTranslateAccelerator[i])->TranslateAccelerator(pMsg);
if( lResult == S_OK ) return true;
}
return false;
}
PreMessageHandler()函数用于消息过滤,过滤器实现 IMessageFilterUI 接口中的MessageHandler()接口函数;通过 CPaintManagerUI::AddMessageFilter(IMessageFilterUI* pFilter) 函数实现过滤器的添加。
bool CPaintManagerUI::PreMessageHandler(UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT& /*lRes*/)
{
//使用过滤器对消息过滤
for( int i = 0; i < m_aPreMessageFilters.GetSize(); i++ )
{
bool bHandled = false;
IMessageFilterUI filter = static_cast(m_aPreMessageFilters[i]);
LRESULT lResult = filter->MessageHandler(uMsg, wParam, lParam, bHandled);
if( bHandled )
return true;
}
//常规过滤一些消息
switch( uMsg )
{
case WM_KEYDOWN:
case WM_SYSCHAR:
case WM_SYSKEYDOWN:
}
return false;
}
将 IMessageFilterUI 列于此处:
class IMessageFilterUI
{
public:
virtual LRESULT MessageHandler(UINT uMsg, WPARAM wParam, LPARAM lParam, bool& bHandled) = 0;
};
将 AddMessageFilter() 列于此处:
bool CPaintManagerUI::AddMessageFilter(IMessageFilterUI* pFilter)
{
ASSERT(m_aMessageFilters.Find(pFilter)<0);
return m_aMessageFilters.Add(pFilter);
}
分发到窗口后,将由窗口的窗口过程回调函数进行处理。在窗口创建之前,即WM_NCCREATE消息时,将CWindowWnd对象指针保存在GWLP_USERDATA中,在处理其他消息中,利用此分别调用不同窗口的处理函数:pThis->HandleMessage(uMsg, wParam, lParam)。
继承CWindowWnd的窗口可以通过重载HandleMessage()函数从而个性化处理各种消息。
LRESULT CALLBACK CWindowWnd::__WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
CWindowWnd* pThis = NULL;
if( uMsg == WM_NCCREATE )
{
//将CWindowWnd指针保存到hwnd所指向的内核对象中(即窗口)
}
else
{
pThis = reinterpret_cast(::GetWindowLongPtr(hWnd, GWLP_USERDATA));
if( uMsg == WM_NCDESTROY && pThis != NULL )
{
//重置hwnd所指向的内核对象(即窗口)
}
}
if( pThis != NULL )
{
return pThis->HandleMessage(uMsg, wParam, lParam);
}
else
{
return ::DefWindowProc(hWnd, uMsg, wParam, lParam);
}
}
从上可以看出,每个窗口对象的 HandleMessage() 函数基本上可理解为该窗口的窗口过程函数;其中 HandleMessage() 为CWindowWnd中定义的虚函数。
下面将 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);
}
HandleCustomMessage()函数为 WindowImplBase 定义的虚函数,继承WindowImplBase的窗口可以重写该函数。
注意最后的 m_PaintManager.MessageHandler(uMsg, wParam, lParam, lRes),CPaintManagerUI中的 MessageHandler() 函数已经帮我们实现了大量的功能。 所有的notify都是在此处理的。 后面将对notify的流程进行分析。现将 m_PaintManager.MessageHandler() 简单粘贴如下:
bool CPaintManagerUI::MessageHandler(UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT& lRes)
{
//消息的notify
TNotifyUI* pMsg = NULL;
while( pMsg = static_cast(m_aAsyncNotify.GetAt(0)) )
{
m_aAsyncNotify.Remove(0);
if( pMsg->pSender != NULL )
{
if( pMsg->pSender->OnNotify ) pMsg->pSender->OnNotify(pMsg);
}
for( int j = 0; j < m_aNotifiers.GetSize(); j++ )
{
static_cast(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(m_aMessageFilters[i])->MessageHandler(uMsg, wParam, lParam, bHandled);
if( bHandled )
{
lRes = lResult;
return true;
}
}
// Custom handling of events
switch( uMsg )
{
case WM_APP + 1:
case WM_CLOSE:
case WM_ERASEBKGND:
case WM_PAINT: //重要,负责绘制窗口中的各个控件
....
}
//消息的notify
pMsg = NULL;
while( pMsg = static_cast(m_aAsyncNotify.GetAt(0)) )
{
m_aAsyncNotify.Remove(0);
if( pMsg->pSender != NULL )
{
if( pMsg->pSender->OnNotify ) pMsg->pSender->OnNotify(pMsg);
}
for( int j = 0; j < m_aNotifiers.GetSize(); j++ )
{
static_cast(m_aNotifiers[j])->Notify(*pMsg);
}
delete pMsg;
}
return false;
}
通过上面的分析,目前适合窗口重载的消息处理函数有以下几个:
//窗口过程之前
virtual LRESULT IMessageFilterUI::MessageHandler(UINT uMsg, WPARAM wParam, LPARAM lParam, bool& bHandled) = 0;
//进入窗口过程中之后
virtual LRESULT CWindowWnd::HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam);
virtual LRESULT WindowImplBase::HandleCustomMessage(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
当然可能还会有一些其他函数,只是上面的这几个比较重要。记住!!!