博客
关于我
wxWidgets源码分析(5) - 窗口管理
阅读量:433 次
发布时间:2019-03-06

本文共 7622 字,大约阅读时间需要 25 分钟。

窗口管理

所有的窗口均继承自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代码,都无需知道平台信息。

转载地址:http://voyfz.baihongyu.com/

你可能感兴趣的文章
vue-autoui自匹配webapi的UI控件
查看>>
EFCore之SQL扩展组件BeetleX.EFCore.Extension
查看>>
vuejs集成echarts的一些问题
查看>>
BeetleX数据分析中间服务V3
查看>>
Http压力测试工具HttpTest4Net
查看>>
azure存储压测的问题(农码主观意识太强被坑了)
查看>>
Kafka实战-简单示例
查看>>
Hadoop项目实战-用户行为分析之应用概述(二)
查看>>
使用用户自定义控件实现asp.net的的权限管理
查看>>
Nhibernate初学
查看>>
关于提高效率
查看>>
Struts2学习之旅二 tiles布局和权限管理
查看>>
java8-CompleableFuture的使用1
查看>>
面试-PA和XSYX面试小结
查看>>
面试刷题14:介绍一下你工作中用到的设计模式?
查看>>
面试刷题22:CAS和AQS是什么?
查看>>
云原生系列1 pod基础
查看>>
Crunch
查看>>
如何将 IPhone 的文件导入 Linux
查看>>
机器学习实战 - 读书笔记(14) - 利用SVD简化数据
查看>>