標籤:str 自己 red 否則 window send 視窗建立過程 name hash
視窗管理
所有的視窗均繼承自wxTopLevelWindows:
WXDLLIMPEXP_DATA_CORE(wxWindowList) wxTopLevelWindows;wxTopLevelWindows
Frame視窗建立過程
在使用Frame視窗的時候,我們一般從wxFrame繼承,建立時通過調用Create方法進行建立:
debugWXFrame::debugWXFrame(wxWindow* parent,wxWindowID id){ ... Create(parent, id, wxEmptyString, wxDefaultPosition, ...); ...}
我們先看下wxFrame類的繼承關係:
wxFrame -> wxFrameBase -> wxTopLevelWindow -> -> wxTopLevelWindowMSW(wxTopLevelWindowNative) -> wxTopLevelWindowBase -> wxNonOwnedWindow -> wxNonOwnedWindowBase -> wxWindow
註:wxTopLevelWindow繼承自wxTopLevelWindowNative,但是wxTopLevelWindowNative是宏定義,在include/wx/toplevel.h檔案中有如下定義:
#if defined(__WXMSW__) #include "wx/msw/toplevel.h" #define wxTopLevelWindowNative wxTopLevelWindowMSW
接著我們看看視窗建立的過程,wxFrame調用wxTopLevelWindow::Create實現視窗的建立:
- 將視窗添加到
wxTopLevelWindows隊列中,這個全域變數儲存了當前所有topWindow的指標;
CreateBase用於設定基礎參數;
- 根據視窗類別型不同,分別調用
CreateDialog或者CreateFrame執行建立。
bool wxFrame::Create(wxWindow *parent, ...){ if ( !wxTopLevelWindow::Create(parent, id, title, pos, size, style, name) ) return false; ...}bool wxTopLevelWindowMSW::Create(wxWindow *parent, ...){ // notice that we should append this window to wxTopLevelWindows list // before calling CreateBase() as it behaves differently for TLW and // non-TLW windows wxTopLevelWindows.Append(this); bool ret = CreateBase(parent, id, pos, sizeReal, style, name); if ( !ret ) return false; if ( parent ) parent->AddChild(this); if ( GetExtraStyle() & wxTOPLEVEL_EX_DIALOG ) { ... ret = CreateDialog(dlgTemplate, title, pos, sizeReal); free(dlgTemplate); } else // !dialog { ret = CreateFrame(title, pos, sizeReal); } return ret;}
繼續看CreateFrame,根據繼承關係我們可以找到實際調用的是wxTopLevelWindowMSW::CreateFrame,這個函數調用MSWCreate實現視窗的建立:
bool wxTopLevelWindowMSW::CreateFrame(const wxString& title, const wxPoint& pos, const wxSize& size){ WXDWORD exflags; WXDWORD flags = MSWGetCreateWindowFlags(&exflags); const wxSize sz = IsAlwaysMaximized() ? wxDefaultSize : size; return MSWCreate(MSWGetRegisteredClassName(), title.t_str(), pos, sz, flags, exflags);}
我們看下這個函數的參數MSWGetRegisteredClassName,這個函數用於註冊視窗類別
註:Windows程式在建立自訂視窗時,需要先調用::RegisterClass註冊該視窗類別,建立自訂的視窗類別時,在使用該視窗類別前必須註冊該視窗類別,使用RegisterClass註冊視窗類別。
MSWGetRegisteredClassName通過調用wxApp::GetRegisteredClassName進行註冊:
- 首先查詢此視窗類別是否已經存在,如果存在則使用登入的;
- 註冊的視窗類別的名字是
wxWindow,的訊息處理函數是wxWndProc;
- 註冊成功後將此類型放到
gs_regClassesInfo中
註:這裡註冊了兩種視窗類別型,一個是帶有CS_HREDRAW | CS_VREDRAW標記的,另外一種不帶此標記,當視窗建立時攜帶wxFULL_REPAINT_ON_RESIZE標記時,會使用不帶標記視窗類別型,否則使用帶有標記的視窗類別型:
實現代碼:
/* static */const wxChar *wxWindowMSW::MSWGetRegisteredClassName(){ return wxApp::GetRegisteredClassName(wxT("wxWindow"), COLOR_BTNFACE);}const wxChar *wxApp::GetRegisteredClassName(const wxChar *name, ...){ const size_t count = gs_regClassesInfo.size(); for ( size_t n = 0; n < count; n++ ) { if ( gs_regClassesInfo[n].regname == name ) return gs_regClassesInfo[n].regname.c_str(); } // we need to register this class WNDCLASS wndclass; wndclass.lpfnWndProc = (WNDPROC)wxWndProc; wndclass.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS | extraStyles; ClassRegInfo regClass(name); wndclass.lpszClassName = regClass.regname.t_str(); if ( !::RegisterClass(&wndclass) ) ... wndclass.style &= ~(CS_HREDRAW | CS_VREDRAW); wndclass.lpszClassName = regClass.regnameNR.t_str(); if ( !::RegisterClass(&wndclass) ) ... gs_regClassesInfo.push_back(regClass); return gs_regClassesInfo.back().regname.t_str();}
繼續看視窗的實際建立MSWCreate函數:
- 根據視窗的標記是否有
wxFULL_REPAINT_ON_RESIZE決定使用那種視窗類別;
- 調用
::CreateWindowEx建立真正的視窗,視窗HANDLE儲存在m_hWnd中。
- 注意到這裡還有一個比較重要的函數
SubclassWin(m_hWnd)用於儲存此視窗的handle到系統中,以後APP那邊收到訊息後,通過這個handle來尋找哪個wxWindow與此Handle關聯,後面會詳述。
bool wxWindowMSW::MSWCreate(const wxChar *wclass, ...){ int x, y, w, h; (void)MSWGetCreateWindowCoords(pos, size, x, y, w, h); int controlId = style & WS_CHILD ? GetId() : 0; wxString className(wclass); if ( !HasFlag(wxFULL_REPAINT_ON_RESIZE) ) { className += wxApp::GetNoRedrawClassSuffix(); } wxWindowCreationHook hook(this); m_hWnd = (WXHWND)::CreateWindowEx ( extendedStyle, className.t_str(), ... ); SubclassWin(m_hWnd); return true;}
另外需要關注的一個調用就是wxWindowCreationHook hook(this);這個類用於將當前視窗的指標寫到全域變數gs_winBeingCreated中,在wxWinProc函數中有使用,下文有講述。
wxWindowCreationHook::wxWindowCreationHook(wxWindowMSW *winBeingCreated){ gs_winBeingCreated = winBeingCreated;}
視窗全域管理
wxWidgets是基於Windows的Win32API來實現視窗操作的,包括訊息機制,有一個問題就是wxWindow類如何與Win32的視窗類別關聯起來呢?
其實wxWidgets的實現很簡單,通過一個全域Map表來儲存,索引是HWND,目標是wxWindow指標,這樣就可以通過Win32的HANDLE直接找到wxWindow了。
wxGUIEventLoop::PreProcessMessage中我們看到有如下代碼:
bool wxGUIEventLoop::PreProcessMessage(WXMSG *msg){ HWND hwnd = msg->hwnd; wxWindow *wndThis = wxGetWindowFromHWND((WXHWND)hwnd);
其中wxGetWindowFromHWND就是用於從HWND映射到wxWindow指標,這個函數實現中,通過調用wxFindWinFromHandle擷取對應的視窗,這裡還有處理就是如果當前的HWND找不到視窗,則使用該視窗的父視窗再去尋找。
extern wxWindow *wxGetWindowFromHWND(WXHWND hWnd){ HWND hwnd = (HWND)hWnd; wxWindow *win = NULL; if ( hwnd ) { win = wxFindWinFromHandle(hwnd); } while ( hwnd && !win ) { hwnd = ::GetParent(hwnd); win = wxFindWinFromHandle(hwnd); } return win;}
繼續看wxFindWinFromHandle,它是通過在gs_windowHandles中尋找,接著我們再看下gs_windowHandles的定義,他是一個HashMap表,類似於std::map的功能,具體實現可自行看代碼:
wxWindow *wxFindWinFromHandle(HWND hwnd){ WindowHandles::const_iterator i = gs_windowHandles.find(hwnd); return i == gs_windowHandles.end() ? NULL : i->second;}WX_DECLARE_HASH_MAP(HWND, wxWindow *, wxPointerHash, wxPointerEqual, WindowHandles);WindowHandles gs_windowHandles;
視窗HWND與wxWindows關聯的時機
管理機制有了,我們看看這個視窗是什麼時候註冊的呢?總的來說,HWND與wxWindows視窗關聯的時機有兩個:
- 視窗建立完成後調用
wxWindowMSW::SubclassWin關聯;
- 在訊息處理函數
wxWndProc中關聯。
建立完成後關聯
在視窗建立完成後,調用SubclassWin將此視窗放到全域變數gs_windowHandles中,函數中調用wxAssociateWinWithHandle進行HWND和wxWindow的關聯:
void wxWindowMSW::SubclassWin(WXHWND hWnd){ wxAssociateWinWithHandle(hwnd, this); // we‘re officially created now, send the event wxWindowCreateEvent event((wxWindow *)this); (void)HandleWindowEvent(event);}
繼續追蹤wxAssociateWinWithHandle,這個是一個全域函數,就是將資料關聯起來:
void wxAssociateWinWithHandle(HWND hwnd, wxWindowMSW *win){ gs_windowHandles[hwnd] = (wxWindow *)win;}
在wxWndProc中關聯
視窗訊息處理函數統一為wxWndProc,這個函數在進來後首先根據HWND尋找wxWindows,尋找不到則直接關聯此HWND到最新建立的視窗上。
關鍵全域變數 gs_winBeingCreated ,這個變數在 wxWindowMSW::MSWCreate 中調用,建立視窗時通過wxWindowCreationHook建構函式將自己寫到gs_winBeingCreated中這樣在第一次收到訊息,並且找不到HWND對應的wxWindows時,在這裡關聯進去。
// Main window procLRESULT WXDLLEXPORT APIENTRY _EXPORT wxWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam){ wxWindowMSW *wnd = wxFindWinFromHandle(hWnd); // 關聯HWND和wxWindows if ( !wnd && gs_winBeingCreated ) { wxAssociateWinWithHandle(hWnd, gs_winBeingCreated); wnd = gs_winBeingCreated; gs_winBeingCreated = NULL; wnd->SetHWND((WXHWND)hWnd); } LRESULT rc; if ( wnd && wxGUIEventLoop::AllowProcessing(wnd) ) rc = wnd->MSWWindowProc(message, wParam, lParam); else rc = ::DefWindowProc(hWnd, message, wParam, lParam); return rc;}
為何會出現在兩個地方?
在windows系統中,一旦調用CreateEx函數建立視窗完成後,視窗就會立刻收到訊息,此時就會調用wxWndProc進行處理,問題是,此時還沒有調用wxWindowMSW::SubclassWin進行關聯,所以解決辦法就是在使用的時候立刻執行關聯,這個關聯的視窗通過全域變數來傳遞。
總結
wxWidgets提供了一套完整的視窗管理機制,有效與Windows系統結合起來,形成了很好的中介層,徹底屏蔽了Win32API,這樣使用者無論在哪個平台上編寫GUI代碼,都無需知道平台資訊。
wxWidgets源碼分析(5) - 視窗管理