3消息循环中的消息处理

1.简介

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)

2.消息类型

(参考: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()。

3.消息循环

注意:CPaintManagerUI::MessageLoop() 函数为静态成员函数

void CPaintManagerUI::MessageLoop()
{
    MSG msg = { 0 };
    while( ::GetMessage(&msg, NULL, 0, 0) ) 
    {
        if( !CPaintManagerUI::TranslateMessage(&msg) ) 
        {
            ::TranslateMessage(&msg); //将按键转化为ASCII字符
            ::DispatchMessage(&msg); //将消息分发到特定窗口的窗口过程中,消息处理完成才会返回
        }
    }
}

3.1 分发到窗口过程之前

消息到来时,首先由 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() 函数。

3.1.1 函数 TranslateAccelerator()

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

3.1.2 函数 PreMessageHandler()

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

3.2 分发到窗口过程之后

分发到窗口后,将由窗口的窗口过程回调函数进行处理。在窗口创建之前,即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;
}

4.总结

通过上面的分析,目前适合窗口重载的消息处理函数有以下几个:

//窗口过程之前
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);

当然可能还会有一些其他函数,只是上面的这几个比较重要。记住!!!

你可能感兴趣的:(DUILIB)