本文遷移于個(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)圖:
在講之前堂油,我們需要了解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ò)程欺冀。
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?