窗口管理

所有的窗口均继承自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 proc
LRESULT 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) - 窗口管理的更多相关文章

  1. wxWidgets源码分析(7) - 窗口尺寸

    目录 窗口尺寸 概述 窗口Size消息的处理 用户调整Size消息的处理 调整窗口大小 程序调整窗口大小 wxScrolledWindow设置窗口大小 获取TextCtrl控件最合适大小 窗口尺寸 概 ...

  2. wxWidgets源码分析(6) - 窗口关闭过程

    目录 窗口关闭过程 调用流程 关闭文档 删除视图 删除文档对象 关闭Frame App清理 多文档窗口的关闭 多文档父窗口关闭 多文档子窗口关闭 窗口的正式删除 窗口关闭过程总结 如何手工删除view ...

  3. Memcached源码分析之内存管理

    先再说明一下,我本次分析的memcached版本是1.4.20,有些旧的版本关于内存管理的机制和数据结构与1.4.20有一定的差异(本文中会提到). 一)模型分析在开始解剖memcached关于内存管 ...

  4. wxWidgets源码分析(8) - MVC架构

    目录 MVC架构 wxDocManager文档管理器 模板类创建文档对象 视图对象的创建 创建顺序 框架菜单命令的执行过程 wxDocParentFrame菜单入口 wxDocManager类的处理 ...

  5. TOMCAT源码分析——生命周期管理

    前言 从server.xml文件解析出来的各个对象都是容器,比如:Server.Service.Connector等.这些容器都具有新建.初始化完成.启动.停止.失败.销毁等状态.tomcat的实现提 ...

  6. wxWidgets源码分析(9) - wxString

    目录 wxString wxString的中文字符支持 Windows Linux Unicode Linux UTF-8 总结 wxString与通用字符串的转换 wxString对象的创建 将wx ...

  7. Duilib源码分析(四)绘制管理器—CPaintManagerUI—(前期准备四)

    接下来,分析uilib.h中的WinImplBase.h和UIManager.h: WinImplBase.h:窗口实现基类,已实现大部分的工作,基本上窗口类均可直接继承该类,可发现该类继承于多个类, ...

  8. Mybatis 源码分析之事物管理

    Mybatis 提供了事物的顶层接口: public interface Transaction { /** * Retrieve inner database connection * @retur ...

  9. Spring源码分析笔记--事务管理

    核心类 InfrastructureAdvisorAutoProxyCreator 本质是一个后置处理器,和AOP的后置处理器类似,但比AOP的使用级别低.当开启AOP代理模式后,优先使用AOP的后置 ...

随机推荐

  1. G - 棋盘游戏

    小希和Gardon在玩一个游戏:对一个N*M的棋盘,在格子里放尽量多的一些国际象棋里面的"车",并且使得他们不能互相攻击,这当然很简单,但是Gardon限制了只有某些格子才可以放, ...

  2. AtCoder - agc043_a 和 POJ - 2336 dp

    题意: 给你一个n行m列由'#'和'.'构成的矩阵,你需要从(1,1)点走到(n,m)点,你每次只能向右或者向下走,且只能走'.'的位置. 你可以执行操作改变矩阵: 你可以选取两个点,r0,c0;r1 ...

  3. Luogu 2017 Autumn Camping 游记

    颓得不行的我到D2才想起来自己可以写一篇low得不能再low的游记,然后就动笔了...... Day0 愉快地看着三联,想着别人放一天我放四天的悠闲生活,内心甚是平静.然而晚上回到家就开始浪了,看完了 ...

  4. Codeforces Round #682 (Div. 2) B. Valerii Against Everyone (思维)

    题意:给你一组数\(b\),对于每个\(b_i\),相对应的\(a_i=2^{b_i}\),问你是否能找出两个不相交的区间,使得两个区间的\(a_i\)的元素和相等. 题解:对于任意一个\(2^k\) ...

  5. Codeforces Round #670 (Div. 2) C. Link Cut Centroids (dfs,树)

    C. Link Cut Centroids Fishing Prince loves trees, and he especially loves trees with only one centro ...

  6. 牛客编程巅峰赛S1第11场 - 黄金&钻石 C.牛牛找子集 (二分)

    题意:有一\(n\)个数,从中找数构成相同的子集,要求子集元素个数为\(k\),求构成子集个数最多的情况,输出子集(字典序最小). 题解:我们可以对子集的个数二分答案,首先用桶记录每个元素的个数,然后 ...

  7. Python-collections模块之defaultdict

    defaultdict defaultdict 是 dict 类型的子类,正如其名,初始化时,可以给key指定默认值,什么意思呢?直接看代码.如果是普通的dict对象,访问一个不存在的key时,会报错 ...

  8. C++中overload 、override、overwrite 之间的区别

    Overload(重载):在C++程序中,可以将语义.功能相似的几个函数用同一个名字表示,但参数或返回值不同(包括类型.顺序不同),即函数重载.(1)相同的范围(在同一个类中):(2)函数名字相同:( ...

  9. Pycharm+任务栏悬浮+docked mode

    先点下所想改变模式的模块, 然后: Window -> Activate tool window -> docked mode.

  10. iOS WebView All In One

    iOS WebView All In One WKWebView / UIWebView Swift Playground //: A UIKit based Playground for prese ...