wxWidgets源碼分析(5) - 視窗管理

來源:互聯網
上載者:User

標籤: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實現視窗的建立:

  1. 將視窗添加到wxTopLevelWindows隊列中,這個全域變數儲存了當前所有topWindow的指標;
  2. CreateBase用於設定基礎參數;
  3. 根據視窗類別型不同,分別調用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進行註冊:

  1. 首先查詢此視窗類別是否已經存在,如果存在則使用登入的;
  2. 註冊的視窗類別的名字是wxWindow,的訊息處理函數是wxWndProc
  3. 註冊成功後將此類型放到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函數:

  1. 根據視窗的標記是否有wxFULL_REPAINT_ON_RESIZE決定使用那種視窗類別;
  2. 調用::CreateWindowEx建立真正的視窗,視窗HANDLE儲存在m_hWnd中。
  3. 注意到這裡還有一個比較重要的函數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視窗關聯的時機有兩個:

  1. 視窗建立完成後調用wxWindowMSW::SubclassWin關聯;
  2. 在訊息處理函數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) - 視窗管理

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.