duilib圖形庫(kù)事件消息機(jī)制

本文遷移于個(gè)人博客 https://blog.csdn.net/cjm19960920/article/details/95416652

windows上常用圖形庫(kù)有很多榔袋,各自實(shí)現(xiàn)的底層機(jī)制也是各顯神通扶认,由于項(xiàng)目需要,也使用過(guò)相應(yīng)的圖形庫(kù),為了更深入地了解這些圖形庫(kù),自己閱讀了相關(guān)的源碼及博客,也希望借以在這篇博客里介紹C++常用圖形庫(kù)duilib的事件消息機(jī)制替饿。

windows圖形庫(kù)首要要解決窗體類跟窗體句柄的關(guān)系毅哗,我們知道晒哄,一個(gè)窗口對(duì)應(yīng)一個(gè)對(duì)象训柴,于是設(shè)計(jì)出了類敞葛,類很容易就可以存放窗口的句柄疏旨,通過(guò)這個(gè)類就能一窺window的奧秘侈沪,但是怎么通過(guò)窗口句柄找到這個(gè)對(duì)象堤撵,是一個(gè)較為麻煩的問(wèn)題价脾,一個(gè)容易想到的方法在窗口類里維護(hù)一份全局的窗口句柄到窗口類的對(duì)應(yīng)關(guān)系蔬充,可以使用map集合蝶俱,如:

#include <map>
class Window
{
public:
    Window();
    ~Window();

public:
    BOOL Create();
protected:
    LRESULT WndProc(UINT message, WPARAM wParam, LPARAM lParam);
protected:
    HWND m_hWnd;
protected:
    static LRESULT CALLBACK StaticWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
    static std::map<HWND, Window *> m_sWindows;
};

當(dāng)窗口類Create時(shí)饥漫,指定StaticWndProc 為窗口回調(diào)函數(shù)榨呆,并將 hWnd 與 this 存入 m_sWindows中:?

BOOL Window::Create()
{
    LPCTSTR lpszClassName = _T("ClassName");
    HINSTANCE hInstance = GetModuleHandle(NULL);
    WNDCLASSEX wcex    = { sizeof(WNDCLASSEX) };
    wcex.lpfnWndProc   = StaticWndProc;
    wcex.hInstance     = hInstance;
    wcex.lpszClassName = lpszClassName;
    RegisterClassEx(&wcex);
    m_hWnd = CreateWindow(lpszClassName, NULL, WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);
    if (m_hWnd == NULL)
    {
        return FALSE;
    }
    m_sWindows.insert(std::make_pair(m_hWnd, this));
    ShowWindow(m_hWnd, SW_SHOW);
    UpdateWindow(m_hWnd);
    return TRUE;
}

? 在 StaticWindowProc 中,由 hWnd 找到 this庸队,然后轉(zhuǎn)發(fā)給成員函數(shù):?

LRESULT CALLBACK Window::StaticWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    std::map<HWND, Window *>::iterator it = m_sWindows.find(hWnd);
    assert(it != m_sWindows.end() && it->second != NULL);
    return it->second->WndProc(message, wParam, lParam);
}

至此完成了句柄到對(duì)象的對(duì)應(yīng)關(guān)系积蜻,據(jù)說(shuō)MFC采用的就是類似做法闯割,此法的缺點(diǎn)是每次窗口過(guò)程函數(shù)StaticWndProc回調(diào)時(shí)都要從map中根據(jù)句柄找到對(duì)象,再轉(zhuǎn)發(fā)成員函數(shù)竿拆,此時(shí)的hWnd相當(dāng)于索引宙拉,當(dāng)窗口句柄較多時(shí)會(huì)存在效率問(wèn)題,那么存不存在一種更高效查找的方法丙笋,比如下面代碼所示:

LRESULT CALLBACK Window::StaticWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    return ((Window *)hWnd)->WndProc(message, wParam, lParam);
}

將hWnd用來(lái)存this指針谢澈,而傳說(shuō)中的WTL所采取的thunk技術(shù)就是采用這個(gè)思路,其主要實(shí)現(xiàn)時(shí)在系統(tǒng)調(diào)用窗口過(guò)程函數(shù)時(shí)御板,讓他先走到我們的另一處代碼锥忿,讓我們有機(jī)會(huì)修改堆棧中的hwnd,其代碼類似是這樣的:?

__asm
{
    //調(diào)用 WndProc 時(shí),堆棧結(jié)構(gòu)為:RetAddr, hWnd, message, wParam, lParam, ... 故 [esp+4] 函
//數(shù)棧頂指針寄存器+4  WinProc的參數(shù)的壓棧方法是典型的__stdcall調(diào)用方式(右邊先壓棧)
    mov dword ptr [esp+4], pThis  ;
    jmp WndProc
}

thunk技術(shù)的具體實(shí)現(xiàn)細(xì)節(jié)較繁瑣怠肋,有興趣的同學(xué)可以繼續(xù)探索敬鬓。

****Duilib:****

從上面的例子可以看出,封裝一個(gè)窗體類笙各,與生成的窗體相關(guān)聯(lián)钉答,并且去處理窗體的窗體消息并不是簡(jiǎn)單,MFC和WTL都有自己一套方法杈抢,而duilib作為國(guó)內(nèi)首個(gè)開源 的directui 界面庫(kù)希痴,其解決的思路要簡(jiǎn)潔明了的多。

當(dāng)duilib窗口類Create時(shí)春感,代碼如下:?

HWND CWindowWnd::Create(HWND hwndParent, LPCTSTR pstrName, DWORD dwStyle, DWORD dwExStyle, int x, int y, int cx, int cy, HMENU hMenu)
{
    if( GetSuperClassName() != NULL && !RegisterSuperclass() ) return NULL;
    if( GetSuperClassName() == NULL && !RegisterWindowClass() ) return NULL;
    m_hWnd = ::CreateWindowEx(dwExStyle, GetWindowClassName(), pstrName, dwStyle, x, y, cx, cy, hwndParent, hMenu, CPaintManagerUI::GetInstance(), this);
    ASSERT(m_hWnd!=NULL);
    return m_hWnd;
}

通過(guò)CreateWindowEx函數(shù)來(lái)創(chuàng)建窗體,CreateWindowEx函數(shù)允許用戶傳遞一個(gè)自定義數(shù)據(jù)虏缸,因此duilib正好把自己類對(duì)象的this指針傳了進(jìn)去鲫懒。

接著當(dāng)窗體開始建立時(shí)就會(huì)發(fā)送消息到相關(guān)的消息處理回調(diào)函數(shù),duilib中對(duì)應(yīng)的是__WndProc函數(shù)刽辙,函數(shù)代碼如下:?

RESULT 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 ) {
        return pThis->HandleMessage(uMsg, wParam, lParam);
    } 
    else {
        return ::DefWindowProc(hWnd, uMsg, wParam, lParam);
    }
}

通常認(rèn)為窗口創(chuàng)建時(shí)會(huì)發(fā)出消息WM_CREATE窥岩,但是在WM_CREATE消息之前還有一個(gè)消息是被發(fā)出的,那就是WM_NCCREATE消息宰缤,一個(gè)完整的流程是WM_NCCREATE->WM_CREATE->WM_DESTROY->WM_NCDESTROY,可以看到在duilib處理函數(shù)中圍繞這個(gè)消息做了文章颂翼。首先我們來(lái)看此時(shí)__WndProc窗體過(guò)程回調(diào)函數(shù)最后一個(gè)參數(shù)的介紹:

Parameters

wParam

----This parameter is not used.

lParam

---- A pointer to the CREATESTRUCT structure that contains information about the window being created. The members of CREATESTRUCT are identical to the parameters of the CreateWindowEx function.

其中l(wèi)Param參數(shù)是關(guān)鍵,這個(gè)參數(shù)此時(shí)是傳進(jìn)來(lái)CREATESTRUCT結(jié)構(gòu)慨灭,這個(gè)結(jié)構(gòu)體介紹如下:?

typedef struct tagCREATESTRUCT {
   LPVOID lpCreateParams;
   HANDLE hInstance;
   HMENU hMenu;
   HWND hwndParent;
   int cy;
   int cx;
   int y;
   int x;
   LONG style;
   LPCSTR lpszName;
   LPCSTR lpszClass;
   DWORD dwExStyle;
} CREATESTRUCT;

參數(shù)

******lpCreateParams******

將與要使用數(shù)據(jù)的點(diǎn)創(chuàng)建一個(gè)窗口朦乏。

******hInstance******

識(shí)別模塊擁有新窗口模塊的實(shí)例句柄。

******hMenu******

標(biāo)識(shí)新窗口將使用菜單氧骤。 子窗口呻疹,如果包含整數(shù) ID.

******hwndParent******

標(biāo)識(shí)擁有新窗口的窗口。 新窗口筹陵,如果是頂級(jí)窗口刽锤,該成員是 ****NULL****镊尺。

******cy******

指定窗口的新高度。

******cx******

指定窗口的新寬度并思。

******y******

指定新窗口左上角的 y 坐標(biāo)庐氮。 如果新窗口是子窗口,坐標(biāo)系是相對(duì)于父窗口宋彼;否則是相對(duì)于屏幕坐標(biāo)原點(diǎn)弄砍。

******x******

指定新窗口左上角的 x坐標(biāo)。 如果新窗口是子窗口宙暇,坐標(biāo)系是相對(duì)于父窗口输枯;否則是相對(duì)于屏幕坐標(biāo)原點(diǎn)。

******style******

指定新窗口中 style占贫。

******lpszName******

為指定新窗口的名稱以 NULL 結(jié)尾的字符串的位置桃熄。

******lpszClass******

為指定新窗口的窗口類名的 null 終止的字符串的結(jié)構(gòu);WNDCLASS (點(diǎn)有關(guān)更多信息型奥,請(qǐng)參見(jiàn) Windows SDK瞳收。)

******dwExStyle******

對(duì)于新窗口指定 擴(kuò)展樣式

這個(gè)結(jié)構(gòu)體的第一個(gè)參數(shù)正是在CreateWindowEx函數(shù)傳入的自定義數(shù)據(jù)厢汹,也就是窗體類的this指針螟深,duilib接下來(lái)通過(guò)這個(gè)結(jié)構(gòu)體獲取到窗體類的指針,并使其m_hWnd成員變量賦值為窗體的句柄烫葬,接著把這個(gè)這個(gè)指針通過(guò)SetWindowLongPtr函數(shù)與窗體句柄關(guān)聯(lián)了起來(lái)界弧!然后可以看到如果處理的不是WM_NCCREATE消息,就是用GetWindowLongPtr函數(shù)通過(guò)窗體句柄獲取到窗體類的指針搭综,再去調(diào)用相關(guān)的消息處理函數(shù)垢箕。duilib使用這個(gè)方法巧妙的將窗體類和窗體句柄關(guān)聯(lián)起來(lái),而沒(méi)有像WTL的thunk技術(shù)那么麻煩兑巾。在使用duilib的時(shí)候条获,我們同樣可以使用GetWindowLongPtr函數(shù)直接從窗體布局獲取到窗體類指針,這在某些時(shí)候特別有用蒋歌。

講完duilib如何解決窗體類跟窗體句柄的關(guān)系后帅掘,接下來(lái)重點(diǎn)介紹duilib的消息處理剖析,以下為duilib核心的大體結(jié)構(gòu)圖:

?
duilib

在講之前堂油,我們需要了解Win32的消息機(jī)制:

1.消息產(chǎn)生修档。
2.系統(tǒng)將消息排列到其應(yīng)該排放的線程消息隊(duì)列中。
3.線程中的消息循環(huán)調(diào)用GetMessage(or PeekMessage)獲取消息府框。
4.傳送消息TranslateMessage and DispatchMessage to 窗口過(guò)程(Windows procedure)萍悴。
5.在窗口過(guò)程里進(jìn)行消息處理

我們看到消息經(jīng)過(guò)幾個(gè)步驟,DuiLib可以在某些步驟間進(jìn)行消息過(guò)濾。首先癣诱,第1计维、2和3步驟,DuiLib并不關(guān)心撕予。DuiLib對(duì)消息處理集中在第4鲫惶、5步驟中,即發(fā)送消息至窗口過(guò)程的前后進(jìn)行消息過(guò)濾实抡,對(duì)應(yīng)CPaintManagerUI類中(也就是上面提到的窗體管理器)欠母。
DuiLib的消息渠,也就是所謂的消息循環(huán)在CPaintManagerUI::MessageLoop()或者CWindowWnd::ShowModal()中實(shí)現(xiàn)吆寨。倆套代碼的核心基本一致赏淌,以ShowModal為例:?

UINT CWindowWnd::ShowModal()
{
    ASSERT(::IsWindow(m_hWnd));
    UINT nRet = 0;
    HWND hWndParent = GetWindowOwner(m_hWnd);
    ::ShowWindow(m_hWnd, SW_SHOWNORMAL);
    ::EnableWindow(hWndParent, FALSE);
    MSG msg = { 0 };
    while( ::IsWindow(m_hWnd) && ::GetMessage(&msg, NULL, 0, 0) ) {
        if( msg.message == WM_CLOSE && msg.hwnd == m_hWnd ) {
            nRet = msg.wParam;
            ::EnableWindow(hWndParent, TRUE);
            ::SetFocus(hWndParent);
        }
        if( !CPaintManagerUI::TranslateMessage(&msg) ) {
            ::TranslateMessage(&msg);
            ::DispatchMessage(&msg);
        }
        if( msg.message == WM_QUIT ) break;
    }
    ::EnableWindow(hWndParent, TRUE);
    ::SetFocus(hWndParent);
    if( msg.message == WM_QUIT ) ::PostQuitMessage(msg.wParam);
    return nRet;
}

在win32消息路由步驟3和4之間,DuiLib調(diào)用CPaintManagerUI::TranslateMessage做了過(guò)濾啄清,通過(guò)這套消息循環(huán)代碼六水,我們能做到在消息發(fā)送到窗口過(guò)程前進(jìn)行常規(guī)過(guò)濾,我們可以看到DuiLib中幾乎所有的demo在創(chuàng)建完消息后辣卒,都調(diào)用了這倆個(gè)消息循環(huán)函數(shù)掷贾。下面是TranslateMessage代碼:?

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++ ) //m_aPreMessages存儲(chǔ)了當(dāng)前進(jìn)程
//中所有的CPaintManagerUI實(shí)例指針                    
        {
            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;
                    // 這里進(jìn)行消息過(guò)濾
                    if( pT->PreMessageHandler(pMsg->message, pMsg->wParam, pMsg->lParam, lRes) ) 
                        return true;
                    return false;
                }
                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;
}

我們從函數(shù)CPaintManagerUI::TranslateMessage代碼中能夠看到,這個(gè)過(guò)濾是在大循環(huán):
for( int i = 0; i < m_aPreMessages.GetSize(); i++ ) 中被調(diào)用的荣茫。如果m_aPreMessages.GetSize()為0想帅,也就不會(huì)調(diào)用過(guò)濾函數(shù)。從代碼中追溯其定義:static CStdPtrArray m_aPreMessages; 是個(gè)靜態(tài)變量啡莉,MessageLoop港准,TranslateMessage等也都是靜態(tài)函數(shù)。其值在CPaintManagerUI::Init中被初始化:?

void CPaintManagerUI::Init(HWND hWnd)
{
    ASSERT(::IsWindow(hWnd));
    // Remember the window context we came from
    m_hWndPaint = hWnd;
    m_hDcPaint = ::GetDC(hWnd);
    // We'll want to filter messages globally too
    m_aPreMessages.Add(this);
}

可以看到咧欣,m_aPreMessages存儲(chǔ)的類型為CPaintManagerUI* ,也就說(shuō)浅缸,這個(gè)靜態(tài)成員數(shù)組里,存儲(chǔ)了當(dāng)前進(jìn)程中所有的CPaintManagerUI實(shí)例指針该押,所以,如果有多個(gè)CPaintManagerUI實(shí)例阵谚, 也不會(huì)存在過(guò)濾問(wèn)題蚕礼,互不干擾,都能各自過(guò)濾梢什。

接著來(lái)講****CPaintManagerUI::TranslateMessage****函數(shù)中的調(diào)用到的PreMessageHandler函數(shù)奠蹬。?

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); // 這里調(diào)用接口 IMessageFilterUI::MessageHandler 來(lái)進(jìn)行消息過(guò)濾
        if( bHandled ) {
            return true;
        }
}
…… ……
return false;
}

可以看到在PreMessageHandler函數(shù)中主要是依次在m_aPreMessageFilters數(shù)組中調(diào)用接口 IMessageFilterUI::MessageHandler 來(lái)進(jìn)行消息過(guò)濾,IMessageFilterUI嗡午,此接口只有一個(gè)成員:MessageHandler純虛函數(shù)囤躁,我們的窗口類要提前過(guò)濾消息,只要實(shí)現(xiàn)這個(gè)IMessageFilterUI,調(diào)用CPaintManagerUI::AddPreMessageFilter狸演,將我們的窗口類實(shí)例指針添加到CPaintManagerUI::m_aPreMessageFilters 數(shù)組中言蛇。當(dāng)消息到達(dá)窗口過(guò)程之前,就會(huì)會(huì)先調(diào)用我們的窗口類的成員函數(shù):MessageHandler宵距。下面是AddPreMessageFilter代碼:?

bool CPaintManagerUI::AddPreMessageFilter(IMessageFilterUI* pFilter)
{
    // 將實(shí)現(xiàn)好的接口實(shí)例腊尚,保存到數(shù)組 m_aPreMessageFilters 中。
    ASSERT(m_aPreMessageFilters.Find(pFilter)<0);
    return m_aPreMessageFilters.Add(pFilter);
}

至此duilib在窗口過(guò)程前的消息過(guò)濾暫且告一段落满哪,接著婿斥,消息抵達(dá)窗口過(guò)程后,如何處理哨鸭。首先民宿,要清楚,窗口過(guò)程在哪兒像鸡?使用DuiLib開發(fā)活鹰,我們的窗口類無(wú)外呼,繼承倆個(gè)基類:一個(gè)是功能簡(jiǎn)陋一點(diǎn) 的:CWindowWnd坟桅,一個(gè)是功能健全一點(diǎn)的:WindowImplBase(繼承于CWindowWnd)华望。然后,我們實(shí)例化窗口類仅乓,調(diào)用這倆個(gè)基類的Create函數(shù)赖舟,創(chuàng)建窗口,其內(nèi)部注冊(cè)了之前講過(guò)的窗口過(guò)程函數(shù):?

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 ) {
        return pThis->HandleMessage(uMsg, wParam, lParam);
    } 
    else {
    //缺省的窗口過(guò)程來(lái)為應(yīng)用程序沒(méi)有處理的任何窗口消息提供缺省的處理夸楣。該函數(shù)確保每一個(gè)消息       
    //得到處理宾抓。
        return ::DefWindowProc(hWnd, uMsg, wParam, lParam);
    }
}

函數(shù)里面主要做了一些轉(zhuǎn)換,細(xì)節(jié)可以自行研究豫喧,最終石洗,會(huì)調(diào)用pThis→HandleMessage(uMsg, wParam, lParam);。也即是說(shuō)紧显,HandleMessage相當(dāng)于一個(gè)窗口過(guò)程(雖然它不是讲衫,但功能類似)。他是CWindowWnd的虛函數(shù):
virtual LRESULT HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam);
所以孵班,如果我們的窗口類實(shí)現(xiàn)了HandleMessage涉兽,就相當(dāng)于再次過(guò)濾了窗口過(guò)程,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); // 調(diào)用父類HandleMessage
}

需要注意得是 CPaintManagerUI::MessageHandler篙程,名稱為MessageHandler枷畏,而不是HandleMessage。
兩者分別在窗口過(guò)程函數(shù)前后進(jìn)行過(guò)濾虱饿,沒(méi)有特殊需求拥诡,一定要調(diào)用MessageHandler函數(shù)触趴,此函數(shù)處理了絕大部分常用的消息響應(yīng)。而且如果要響應(yīng)Notify事件(后面會(huì)介紹)渴肉,不調(diào)用此函數(shù)將無(wú)法響應(yīng)冗懦。
好現(xiàn)在我們已經(jīng)知道,倆個(gè)地方可以截獲消息:

  • IMessageFilterUI接口宾娜,調(diào)用CPaintManagerUI:: AddPreMessageFilter批狐,進(jìn)行消息發(fā)送到窗口過(guò)程前的過(guò)濾。
  • HandleMessage函數(shù)前塔,當(dāng)消息發(fā)送到窗口過(guò)程中時(shí)嚣艇,最先進(jìn)行過(guò)濾。

下面繼續(xù)看看void Notify(TNotifyUI& msg)是如何響應(yīng)的华弓。我們的窗口繼承于INotifyUI接口食零,就必須實(shí)現(xiàn)此函數(shù):?

class INotifyUI
{
public:
    virtual void Notify(TNotifyUI& msg) = 0;
};

上面說(shuō)了,在我們的HandleMessage要調(diào)用CPaintManagerUI::MessageHandler來(lái)進(jìn)行后續(xù)處理寂屏。下面是一個(gè)代碼片段:?

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);
        }
        // 先看這里贰谣,其它代碼先忽略;我們看到一個(gè)轉(zhuǎn)換操作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;
        }
    }
    … …
}

其中m_aNotifiers定義為CStdPtrArray數(shù)組迁霎,目前還看不出其指向的實(shí)際類型吱抚。看看考廉,什么時(shí)候給該數(shù)組添加成員:?

bool CPaintManagerUI::AddNotifier(INotifyUI* pNotifier)
{
    ASSERT(m_aNotifiers.Find(pNotifier)<0);
    return m_aNotifiers.Add(pNotifier);
}

不錯(cuò)秘豹,正是AddNotifier,類型也有了:即上面提到INotifyUI接口昌粤。所以既绕,duilib教程里會(huì)在響應(yīng)WM_CREATE消息的時(shí)候,調(diào)用 AddNotifier(this)涮坐,將自身加入數(shù)組中凄贩,然后在CPaintManagerUI::MessageHandler就能枚舉調(diào)用。由于 AddNotifer的參數(shù)為INotifyUI*袱讹,所以疲扎,我們要實(shí)現(xiàn)此接口。

所以捷雕,當(dāng)HandleMessage函數(shù)被調(diào)用后椒丧,緊接著會(huì)調(diào)用我們的Notify函數(shù)。如果你沒(méi)有對(duì)消息過(guò)濾的特殊需求非区,實(shí)現(xiàn)INotifyUI即可瓜挽,在Notify函數(shù)中處理消息響應(yīng)盹廷。

上面的Notify調(diào)用征绸,是響應(yīng)系統(tǒng)產(chǎn)生的消息。程序本身也能手動(dòng)產(chǎn)生,其函數(shù)為:
void CPaintManagerUI::SendNotify(TNotifyUI& Msg, bool bAsync /= false/)
DuiLib將發(fā)送的Notify消息分為了同步和異步消息管怠。同步就是立即調(diào)用(類似SendMessage)淆衷,異步就是先放到隊(duì)列中,下次再處理渤弛。(類似PostMessage)祝拯。?

void CPaintManagerUI::SendNotify(TNotifyUI& Msg, bool bAsync /*= false*/)
{
    … …
    if( !bAsync ) {
        // Send to all listeners
        // 同步調(diào)用OnNotify,注意不是Notify
        if( Msg.pSender != NULL ) {
            if( Msg.pSender->OnNotify ) Msg.pSender->OnNotify(&Msg);
        }
        // 還會(huì)再次通知所有注冊(cè)了INotifyUI的窗口她肯。
        for( int i = 0; i < m_aNotifiers.GetSize(); i++ ) {
            static_cast<INotifyUI*>(m_aNotifiers[i])->Notify(Msg);
        }
    }
else {
        // 異步調(diào)用佳头,添加到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開始處發(fā)現(xiàn)一些代碼:?

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);
    }
    for( int j = 0; j < m_aNotifiers.GetSize(); j++ ) {
        static_cast<INotifyUI*>(m_aNotifiers[j])->Notify(*pMsg);
    }
    delete pMsg;
}

可以看到MessageHandler首先從異步隊(duì)列中一個(gè)消息并調(diào)用OnNotify。OnNotify和上面的Notify也不一樣晴氨。OnNotify是響應(yīng)消息的另外一種方式康嘉。它的定義為:CEventSource OnNotify; 屬于CControlUI類。重載了一些運(yùn)算符籽前,如 operator()亭珍;要讓控件響應(yīng)手動(dòng)發(fā)送(SendNotify)的消息,就要給控件的OnNotify枝哄,添加消息代理肄梨。在DuiLib的TestApp1中的OnPrepare函數(shù)里,有:?

CSliderUI* pSilder = static_cast<CSliderUI*>(m_pm.FindControl(_T("alpha_controlor")));
if( pSilder ) pSilder->OnNotify += MakeDelegate(this, &CFrameWindowWnd::OnAlphaChanged);

至于代理的代碼實(shí)現(xiàn)挠锥,就不展示了众羡,這里簡(jiǎn)單說(shuō)明,就是將類成員函數(shù)瘪贱,作為回調(diào)函數(shù)纱控,加入到OnNotify中,然后調(diào)用 pMsg→pSender→OnNotify(pMsg)的時(shí)候菜秦,循環(huán)調(diào)用所有的類函數(shù)甜害,實(shí)現(xiàn)通知的效果。代理代碼處理的很巧妙球昨,結(jié)合多態(tài)和模板尔店,能將任何類成員函數(shù)作為回調(diào)函數(shù)。

查閱CSliderUI代碼主慰,發(fā)現(xiàn)他在自身的DoEvent函數(shù)內(nèi)調(diào)用了諸如:m_pManager->SendNotify(this, DUI_MSGTYPE_VALUECHANGED);類似的代碼嚣州,調(diào)用它,我們就會(huì)得到通知」猜荩現(xiàn)在该肴,又多了兩種消息處理的方式:

  • INotifyUI,調(diào)用CPaintManagerUI::AddNotifier藐不,將自身加入Notifier隊(duì)列匀哄。
  • MakeDelegate(this, &CFrameWindowWnd::OnAlphaChanged);秦效,當(dāng)程序某個(gè)地方調(diào)用了 CPaintManagerUI::SendNotify,并且 Msg.pSender正好是注冊(cè)的this涎嚼,我們的類成員回調(diào)函數(shù)將被調(diào)用阱州。

搜尋CPaintManagerUI代碼,我們發(fā)現(xiàn)還有一些消息過(guò)濾再里面:?

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

m_aMessageFilters也是IMessageFilterUI array法梯,和m_aPreMessageFilters類似苔货。上面我們介紹的是CPaintManagerUI::AddPreMessageFilter,那這個(gè)又是在哪兒做的過(guò)濾立哑?還是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;
        }
    }
… …

這個(gè)片段是在夜惭,異步OnNotify和Nofity消息響應(yīng),被調(diào)用后铛绰。才被調(diào)用的滥嘴,優(yōu)先級(jí)也就是最低。但它始終會(huì)被調(diào)用至耻,因?yàn)楫惒絆nNotify和 Nofity消息響應(yīng)沒(méi)有返回值若皱,不會(huì)因?yàn)橄⒁呀?jīng)被處理,而直接退出尘颓。DuiLib再次給用戶一個(gè)處理消息的機(jī)會(huì)走触。用戶可以選擇將bHandled設(shè)置 為True,從而終止消息繼續(xù)傳遞疤苹。我覺(jué)得互广,這個(gè)通常是為了彌補(bǔ)OnNotify和Nofity沒(méi)有返回值的問(wèn)題,在m_aMessageFilters 做集中處理卧土。

處理完所有的消息響應(yīng)后惫皱,如果消息沒(méi)有被截?cái)啵珻PaintManagerUI::MessageHandler繼續(xù)處理大多數(shù)默認(rèn)的消息尤莺,它會(huì)處理在其管理范圍中的所有控件的大多數(shù)消息和事件等旅敷。

然后,消息機(jī)制還沒(méi)有完颤霎,這只是CPaintManagerUI::MessageHandler中的消息機(jī)制媳谁,如果繼承的是 WindowImplBase, WindowImplBase實(shí)現(xiàn)了DuiLib窗口的大部分功能。WindowImplBase繼承了CWindowWnd友酱,重載了 HandleMessage晴音,也就是說(shuō),消息發(fā)送的窗口過(guò)程后缔杉,第一個(gè)調(diào)用的是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處理一些消息锤躁,使用類似成員函數(shù)名On***來(lái)處理消息,所以或详,可以重載這些函數(shù)達(dá)到消息過(guò)濾的目的系羞。 然后加缘,我們看到,有一個(gè)函數(shù):WindowImplBase::HandleCustomMessage觉啊,它是虛函數(shù),我們可以重寫此函數(shù)沈贝,進(jìn)行消息過(guò)濾杠人,由于還沒(méi)有調(diào)用m_PaintManager.MessageHandler,所以在收到Notify消息之前進(jìn)行的過(guò)濾宋下。

于是又多了兩種方式:

  • WindowImplBase的虛函數(shù)
  • WindowImplBase::HandleCustomMessage函數(shù)

最后可以得出總結(jié)嗡善,DuiLib消息響應(yīng)方式:

  • IMessageFilterUI接口,調(diào)用CPaintManagerUI::AddPreMessageFilter学歧,進(jìn)行消息發(fā)送到窗口過(guò)程前的過(guò)濾罩引。
  • HandleMessage函數(shù),當(dāng)消息發(fā)送到窗口過(guò)程中時(shí)枝笨,最先進(jìn)行過(guò)濾袁铐。
  • INotifyUI,調(diào)用CPaintManagerUI::AddNotifier横浑,將自身加入Notifier隊(duì)列剔桨。
  • MakeDelegate(this, &CFrameWindowWnd::OnAlphaChanged);,當(dāng)程序某個(gè)地方調(diào)用了 CPaintManagerUI::SendNotify徙融,并且Msg.pSender正好是this洒缀,我們的類成員回調(diào)函數(shù)將被調(diào)用。
  • WindowImplBase的虛函數(shù)
  • WindowImplBase::HandleCustomMessage函數(shù)

以上即duilib從對(duì)象如何關(guān)聯(lián)窗體句柄到實(shí)現(xiàn)窗體消息機(jī)制的大致過(guò)程欺冀。

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末树绩,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子隐轩,更是在濱河造成了極大的恐慌,老刑警劉巖职车,帶你破解...
    沈念sama閱讀 212,542評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異军援,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)称勋,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,596評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)赡鲜,“玉大人庐船,你說(shuō)我怎么就攤上這事〕案” “怎么了筐钟?”我有些...
    開封第一講書人閱讀 158,021評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵赋朦,是天一觀的道長(zhǎng)宠哄。 經(jīng)常有香客問(wèn)我,道長(zhǎng)毛嫉,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,682評(píng)論 1 284
  • 正文 為了忘掉前任暴区,我火速辦了婚禮辛臊,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘缰盏。我一直安慰自己淹遵,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,792評(píng)論 6 386
  • 文/花漫 我一把揭開白布济炎。 她就那樣靜靜地躺著须尚,像睡著了一般侍咱。 火紅的嫁衣襯著肌膚如雪耐床。 梳的紋絲不亂的頭發(fā)上楔脯,一...
    開封第一講書人閱讀 49,985評(píng)論 1 291
  • 那天,我揣著相機(jī)與錄音堪嫂,去河邊找鬼。 笑死淹办,一個(gè)胖子當(dāng)著我的面吹牛恶复,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 39,107評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼拓哟,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼伶授!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起违诗,我...
    開封第一講書人閱讀 37,845評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤诸迟,失蹤者是張志新(化名)和其女友劉穎愕乎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體绅项,經(jīng)...
    沈念sama閱讀 44,299評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡比肄,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,612評(píng)論 2 327
  • 正文 我和宋清朗相戀三年芳绩,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片铺浇。...
    茶點(diǎn)故事閱讀 38,747評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡鳍侣,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出倚聚,到底是詐尸還是另有隱情惑折,我是刑警寧澤,帶...
    沈念sama閱讀 34,441評(píng)論 4 333
  • 正文 年R本政府宣布白热,位于F島的核電站粗卜,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏攻臀。R本人自食惡果不足惜纱昧,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,072評(píng)論 3 317
  • 文/蒙蒙 一识脆、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧灼捂,春花似錦纵东、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,828評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至胡诗,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間煌恢,已是汗流浹背瑰抵。 一陣腳步聲響...
    開封第一講書人閱讀 32,069評(píng)論 1 267
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留婿崭,地道東北人肴颊。 一個(gè)月前我還...
    沈念sama閱讀 46,545評(píng)論 2 362
  • 正文 我出身青樓婿着,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親祟身。 傳聞我的和親對(duì)象是個(gè)殘疾皇子袜硫,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,658評(píng)論 2 350

推薦閱讀更多精彩內(nèi)容