duilib界面庫消息流程分析

對照duilib源碼,主要看下CWindowWnd類與CPaintManagerUI類中對消息分發的處理。

1. CPaintManagerUI類的MessageLoop函數

void CPaintManagerUI::MessageLoop()  
{  
    MSG msg = { 0 };  
    while( ::GetMessage(&msg, NULL, 0, 0) ) {    //獲取消息  
        if( !CPaintManagerUI::TranslateMessage(&msg) ) { //消息過濾  
            ::TranslateMessage(&msg);  
            ::DispatchMessage(&msg); //分發到窗口的消息處理窗口中. 也就是調用CWindowWnd類的__WndProc函數或是__ControlProc函數.  
        }  
    }  
}

消息第一次會由CPaintManagerUI類的TranslateMessage消息接收到.

2. 調用CWindowWnd::Create創建窗口

Create中主要完成以下操作:

  • 如果要子類下Window的控件(就是系統的控件, 而不是duilib的模擬控件), 就設置__ControlProc函數爲消息回調函數.
  • 不子類化, 就註冊窗口類. 此時設置__WndProc爲窗口消息處理回調函數.
  • 用CreateWindowEx API函數創建窗口.

注:
__WndProc函數定義:

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); //來自於CreateWindowEx函數的最後一個參數( 也就是CWindowWnd對象指針了 )  
        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 ) {  
        return pThis->HandleMessage(uMsg, wParam, lParam);  //在此調用繼承類的消息處理函數  
    }   
    else {  
        return ::DefWindowProc(hWnd, uMsg, wParam, lParam); //未綁定類對象, 就調用默認的窗口消息處理函數  
    }  
}

消息第二次就由__WndProc接收到, 然後再傳到CWindowWnd類的HandlerMessage函數中.

3. CWindowWnd類的窗口繼承類對於HandlerMessage虛函數的實現

LRESULT CMainWnd::HandleMessage( UINT uMsg, WPARAM wParam, LPARAM lParam )  
{  
    LRESULT lRes = 0;        //消息處理返回值.  
    BOOL    bHandled = TRUE; //消息是否要繼續往下傳.  
    switch ( uMsg )  
    {  
    case WM_CREATE: 
        lRes = OnInitResource( bHandled );  //進行初始化工作. 比如最重要的XML加載解析工作. 
        break;  
    default:  
        bHandled  = FALSE;  
        break;
    }  

    if ( bHandled )  
    {  
        return lRes;  
    }  

    //傳給CPaintManagerUI::MessageHandler函數進行具體的控件處理工作  
    if ( m_pm.MessageHandler( uMsg, wParam, lParam, lRes ) )    
    {  
        return lRes;  
    }  

    //沒處理過的就調用CWindowWnd類的默認消息處理函數吧. 
    return CWindowWnd::HandleMessage( uMsg, wParam, lParam );   
}

在這裏就是用戶要按消息進行具體的處理了. 之後要傳到CPaintManagerUI類對象的MessageHandler函數. 未處理的消息就要返回給CWindowWnd類的默認消息處理函數來處理了.

4. CPaintManagerUI類的TranslateMessage, MessageHandler函數

BOOL CPaintManagerUI::TranslateMessage(const LPMSG pMsg)  
{  
    HWND hwndParent = ::GetParent(pMsg->hwnd); //獲取消息接收窗口的父窗口  
    UINT uStyle = GetWindowStyle(pMsg->hwnd); //獲取窗口的樣式  
    LRESULT lRes = 0;  
    for( int i = 0; i < m_aPreMessages.GetSize(); i++ ) { //這個m_aPreMessage保存着CPaintManagerUI類對象.   
        CPaintManagerUI* pT = static_cast<CPaintManagerUI*>(m_aPreMessages[i]);  
        if( pMsg->hwnd == pT->GetPaintWindow() //消息是否屬於當前CPaintManagerUI綁定的窗口  
         || (hwndParent == pT->GetPaintWindow() && ((uStyle & WS_CHILD) != 0)) ) //消息是否爲當前窗口中窗口的消息, (如ActiveX控件 )  
        {  
            if( pT->PreMessageHandler(pMsg->message, pMsg->wParam, pMsg->lParam, lRes) ) return TRUE; //此時就調用PreMessageHandler過濾函數.  
        }  
    }  
    return FALSE;  
}

m_aPreMessage爲靜態成員變量, 在CPaintManagerUI::Init進行窗口與此類綁定時添加到此變量中.

5. CPaintManagerUI::PreMessageHandler消息過濾函數

BOOL CPaintManagerUI::PreMessageHandler(UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT& /*lRes*/)  
{  
    // 遍歷當前的消息過濾列表. m_aPreMessageFilter的元素爲IMessageFilterUI接口.只一個虛函數MessageHandler.   
    // 用戶可以添加此接口的繼承類變量到m_aPreMessageFilters列表中. ( 調用AddMessageFilter函數實現 )  
    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;  
        }  
    }  
    // 以下是對幾個按鍵消息的過濾.  
    // WM_KEYDOWN     檢查是否爲VK_TAB鍵, 要進行控件焦點的移動.  
    // WM_SYSCHAR     獲取與wParam中的字符加速鍵匹配的控件, 並激活它.  
    // WM_SYSKEYDOWN  生成控件事件( 用TEventUI來模擬 )  
}

6. CPaintManagerUI::MessageHandler函數

  1. 遍歷m_aMessageFilters列表中的IMessageFilterUI接口, 並調用MessageHandler函數, 再次進行相關的消息過濾功能.(與上面的m_aPreMessageFilters類似)

  2. 在此會處理窗口的WM_PAINT消息. 顯示所有控件的外觀與狀態.

  3. 處理鼠標事件, 實現控件激活和相關事件.

  4. 處理WM_TIMER消息, 所有控件要用CPaintManagerUI的SetTimer, KillTimer等函數實現計時器功能.

  5. 處理CPaintManagerUI類的自定消息, WM_APP + 1與 +2,
    WM_APP + 1是用於控件延遲銷燬控件對象
    WM_APP + 2銷燬異步消息的處理.
    ( 異步控件消息用CPaintManagerUI::SendNotify函數, 把消息對象添加到m_aAsyncNotify列表中, 再PostMessage函數WM_APP + 2 )

  6. 其它基本的窗口相關消息的處理.
    CPaintManagerUI把DUILIB內部的事件都是用TEventUI結構的形式調用CControlUI類的Event函數來投遞的.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章