窗口尺寸

概述

类型 说明
Size 当前窗口实际大小,通过wxWindow::SetSize()获取;
Client Size 客户区大小;
Best Size 最合适的大小,用户可以实现wxWindow::DoGetBestSize()方法,自定义返回最合适的大小;
Best Client Size 最合适的客户区大小,用户可以实现DoGetBestClientSize方法实现自定义;
Minimal Size 用户可以通过wxWindow::SetMinSize()方法设置窗口的最小大小,下限值;
Maximum Size 用户可以通过wxWindow::SetMaxSize()方法设置窗口的最大大小,上限值;
Initial Size 初始大小,用户使用构造函数创建对象时传递的大小,一般用户会传递wxDefaultSize,此时控件会使用Best Size作为控件大小;
Virtual Size 本控件可以显示的虚拟控件大小,如果此大小比实际窗口大小大,则可以通过滚动方式来显示;

窗口Size消息的处理

消息的派发过程可以参考wxWindow消息处理过程文档,这里重点讨论WM_SIZE消息的处理。

从前面的文档知道,wxWidgets注册窗口时同时指定了窗口处理函数wxWndProc,当收到消息后系统会调用此函数来处理消息。

先以wxDialog窗口类为入口点,分析SIZE消息处理流程:

wxWndProc
-> wxDialog::MSWWindowProc
-> wxWindow::MSWWindowProc
-> wxWindowMSW::MSWHandleMessage
-> wxWindowMSW::HandleSize
-> wxWindowBase::HandleWindowEvent
-> wxEvtHandler::SafelyProcessEvent

所有都使用窗口的默认处理,走到了EventHandler类的SafelyProcessEvent函数 ,接下来的流程与正常消息处理流程相同,查找动态/静态消息注册表,找到就处理。

对于wxDialog类来说,它继承自wxTopLevelWindow,再上一层为wxTopLevelWindowBase类,我们可以看到在wxTopLevelWindowBase类的消息注册表中定义了SIZE消息的处理方法:

BEGIN_EVENT_TABLE(wxTopLevelWindowBase, wxWindow)
EVT_CLOSE(wxTopLevelWindowBase::OnCloseWindow)
EVT_SIZE(wxTopLevelWindowBase::OnSize)
END_EVENT_TABLE()

wxTopLevelWindowBase::OnSize函数定义如下,它调用了DoLayout进行实际操作:

如果当前窗口中绑定了Sizer对象,则会走第一个分支,调用Layout继续现有处理,如果没有绑定,那么此时需要找到子窗口(此时必须有唯一的子窗口),将子窗口设置为自己的ClientSize大小。

// wxTopLevelWindowBase
void OnSize(wxSizeEvent& WXUNUSED(event)) { DoLayout(); } void wxTopLevelWindowBase::DoLayout()
{
// if we're using constraints or sizers - do use them
if ( GetAutoLayout() )
{
Layout();
}
else
{
// 检查必须只有一个子窗口...如果有多个则不处理
// do we have any children at all?
if ( child && child->IsShown() )
{
// exactly one child - set it's size to fill the whole frame
int clientW, clientH;
DoGetClientSize(&clientW, &clientH);
child->SetSize(0, 0, clientW, clientH);
}
}
}

一般情况下我们需要Sizer类进行窗口控件布局,所以都走第一种场景,也就是调用wxWindowBase::Layout继续处理:

bool wxWindowBase::Layout()
{
if ( GetSizer() )
{
int w = 0, h = 0;
GetVirtualSize(&w, &h);
GetSizer()->SetDimension( 0, 0, w, h );
}
...
}

调用Sizer类的SetDimension方法,进而调用wxSizer::Layout方法

void wxSizer::Layout()
{
// (re)calculates minimums needed for each item and other preparations
// for layout
CalcMin(); // Applies the layout and repositions/resizes the items
wxWindow::ChildrenRepositioningGuard repositionGuard(m_containingWindow); RecalcSizes();
}

注意看到,这里调用了两个方法,CalcMinRecalcSizes

先看下CalcMin方法,每种Sizer的处理方式不同,会有各自的方法,比如wxBoxSizer::CalcMin方法,它首先找到所有的item,调用item的CalcMin方法,这个方法只不过最终获取到了最小的窗口值而已,并没有进行窗口调整动作:

  1. 如果item也是一个Sizer,那么继续调用sizer的GetMinSize
  2. 如果item直接绑定的窗口,那么则调用窗口的GetEffectiveMinSize方法,进而继续调用wxWindowBase::GetMinSize
wxSize wxSizerItem::CalcMin()
{
if (IsSizer())
{
m_minSize = m_sizer->GetMinSize(); // if we have to preserve aspect ratio _AND_ this is
// the first-time calculation, consider ret to be initial size
if ( (m_flag & wxSHAPED) && wxIsNullDouble(m_ratio) )
SetRatio(m_minSize);
}
else if ( IsWindow() )
{
// Since the size of the window may change during runtime, we
// should use the current minimal/best size.
m_minSize = m_window->GetEffectiveMinSize();
} return GetMinSizeWithBorder();
}

继续看wxBoxSizer::RecalcSizes函数,这个函数首先根据变化方向进行计算窗口,安排每个item的位置和大小,并调用item的SetDimension方法:

  1. item根据自身的属性,比如对齐属性,expand属性,设置自己绑定的窗口的大小;
  2. 调用绑定窗口的大小,绑定的窗口可能是Sizer,也有可能是window,对于window则调用window的wxWindow::SetSize方法。
wxBoxSizer::RecalcSizes
-> wxSizerItem::SetDimension void wxSizerItem::SetDimension( const wxPoint& pos_, const wxSize& size_ )
{
wxPoint pos = pos_;
wxSize size = size_;
if (m_flag & wxSHAPED)
...
switch ( m_kind )
{
case Item_Window:
{
// Use wxSIZE_FORCE_EVENT here since a sizer item might
// have changed alignment or some other property which would
// not change the size of the window. In such a case, no
// wxSizeEvent would normally be generated and thus the
// control wouldn't get laid out correctly here.
m_window->SetSize(pos.x, pos.y, size.x, size.y,
wxSIZE_ALLOW_MINUS_ONE|wxSIZE_FORCE_EVENT );
break;
}
case Item_Sizer:
m_sizer->SetDimension(pos, size);
break;
}
}

wxWindow继承自wxWindowBase,调用关系如下,随后在函数内构造一个wxSizeEvent,然后调用消息处理函数,处理流程与通常的消息处理流程相同。

注意这个窗口是Dialog内部的子窗口,也就是控件的窗口,此时调用的消息处理函数是子窗口的处理函数。

注意:

  1. 对于首次设置,也就是用户没有调整过窗口的大小,此时需要设置的位置和大小与当前Window中保存的大小是相同的,所以直接构建一个SizeEvent消息,然后调用HandleWindowEvent就可以了;
  2. 对于用户调整窗口大小的厂家,此时的位置和消息与Windows中保存的不同,所以需要走另外的分支;
wxWindow::SetSize
-> wxWindowMSW::DoSetSize void wxWindowMSW::DoSetSize(int x, int y, int width, int height, int sizeFlags)
{
if ( x == currentX && y == currentY &&
width == currentW && height == currentH &&
!(sizeFlags & wxSIZE_FORCE) )
{
if (sizeFlags & wxSIZE_FORCE_EVENT)
{
wxSizeEvent event( wxSize(width,height), GetId() );
event.SetEventObject( this );
HandleWindowEvent( event );
}
return;
}
}

如果我们的控件定义了处理函数,此时就会调用控件的OnSize函数。

用户调整Size消息的处理

上文得知,用户调整窗口的大小时,的wxWindowMSW::DoSetSize方法的处理方式是不同的,调用的是DoMoveWindow方法进行窗口大小位置进行设置,最终实现是调用Win32的::DeferWindowPos函数,该函数为指定的窗口更新指定的多窗口位置结构,然后函数返回该更新结构的句柄,实际上,这个函数会通过Windows消息发送给指定的子窗口。

void wxWindowMSW::DoSetSize(int x, int y, int width, int height, int sizeFlags)
{
if ( x == currentX && y == currentY &&
width == currentW && height == currentH &&
!(sizeFlags & wxSIZE_FORCE) )
{
if (sizeFlags & wxSIZE_FORCE_EVENT)
{
wxSizeEvent event( wxSize(width,height), GetId() );
event.SetEventObject( this );
HandleWindowEvent( event );
}
return;
} DoMoveWindow(x, y, width, height);
} void wxWindowMSW::DoMoveWindow(int x, int y, int width, int height)
{
...
DoMoveSibling(m_hWnd, x, y, width, height);
...
} wxWindowMSW::DoMoveSibling(WXHWND hwnd, int x, int y, int width, int height)
{
hdwp = ::DeferWindowPos(hdwp, (HWND)hwnd, NULL, x, y, width, height,
SWP_NOZORDER | SWP_NOOWNERZORDER | SWP_NOACTIVATE);
}

调整窗口大小

程序调整窗口大小

Sizer中每次回计算窗口的最小大小,然后应用,所以,如果用户调用SetSize接口设置空间窗口的大小,实际上是不生效的。

解决办法就是调用SetMinSize设置窗口的最小窗口大小。

还可以调用SetInitialSize设置窗口大小,此函数会调用SetMinSize函数,然后再计算最合适的大小,所以我们建议调用SetInitialSize来进行设置:

可以在窗口类的构造函数后,加入到父窗口的Sizer前调用,如下,这样父窗口的Sizer进行设置窗口大小时,会直接使用用户设置的Size:

tcPortion0 = new wxTextCtrl(this, ID_TEXTCTRL1, _("Text"));
// 设置窗口大小
tcPortion0->SetInitialSize();
BoxSizer1->Add(tcPortion0, 0, wxALL|wxEXPAND, 5);

还有另外一种场景,就是软件使用过程中动态设置窗口大小,此时就要使用到父窗口的Layout方法了,如果调用了子窗口的SetInitialSize函数,但是没有调用父窗口的Layout方法,结果就是子窗口的大小调整了,但是父窗口的布局却没有变化,显示会乱掉。

调用了父窗口的Layout后,父窗口的Sizer会重新计算控件的大小,重新调整布局。

tcPortion0->SetInitialSize();
Layout();

wxScrolledWindow设置窗口大小

wxScrolledWindow提供了滚动条功能,当窗口的可视区域大于当前窗口大小时,一般的窗口将无法显示完整,在wxScrolledWindow中就可以通过滚动条滚动来显示所有区域。

用户所需要操作的就是调用类中的SetVirtualSize来设置虚拟窗口大小,然后调用SetScrollRate设置滚动粒度。

    SetVirtualSize(GetSizer()->GetSize());
SetScrollRate(1,1);

还有一种比较简单的用法,直接调用GetBestVirtualSize来获取最佳的虚拟大小。

    SetVirtualSize(GetBestVirtualSize());
SetScrollRate(1,1);

获取TextCtrl控件最合适大小

有些情况下,我们希望能够根据当前Text框的内容设置高度,此时我们可以使用下面的方法来设置窗口大小:

SetInitialSize(wxSize(GetSize().x, PositionToCoords(GetLastPosition()).y));

要求:

  1. 文本必须以'\n'结尾;
  2. 在通过PositionToCoords计算高度时,必须保证此时TextCtrl框的宽度为最终真实宽度;

wxWidgets源码分析(7) - 窗口尺寸的更多相关文章

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

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

  2. wxWidgets源码分析(5) - 窗口管理

    窗口管理 所有的窗口均继承自wxTopLevelWindows: WXDLLIMPEXP_DATA_CORE(wxWindowList) wxTopLevelWindows; wxTopLevelWi ...

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

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

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

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

  5. wxWidgets源码分析(4) - 消息处理过程

    目录 消息处理过程 消息如何到达wxWidgets Win32消息与wxWidgets消息的转换 菜单消息处理 消息处理链(基于wxEvtHandler) 消息处理链(基于wxWindow) 总结 消 ...

  6. wxWidgets源码分析(2) - App主循环

    目录 APP主循环 MainLoop 消息循环对象的创建 消息循环 消息派发 总结 APP主循环 MainLoop 前面的wxApp的启动代码可以看到,执行完成wxApp::OnInit()函数后,接 ...

  7. wxWidgets源码分析(1) - App启动过程

    目录 APP启动过程 wxApp入口定义 wxApp实例化准备 wxApp的实例化 wxApp运行 总结 APP启动过程 本文主要介绍wxWidgets应用程序的启动过程,从app.cpp入手. wx ...

  8. Sentinel源码分析-滑动窗口统计原理

    滑动窗口技术是Sentinel比较关键的核心技术,主要用于数据统计 通过分析StatisticSlot来慢慢引出这个概念 @Override public void entry(Context con ...

  9. wxWidgets源码分析(3) - 消息映射表

    目录 消息映射表 静态消息映射表 静态消息映射表处理过程 动态消息映射表 动态消息映射表处理过程 消息映射表 消息是GUI程序的核心,所有的操作行为均通过消息传递. 静态消息映射表 使用静态Event ...

随机推荐

  1. Shiro权限项目

    目录 环境配置 spring容器 springmvc freemarker mybatis shiro 工具类 TokenManager.java Result.java 功能实现 登录 注册 个人中 ...

  2. Codeforces Round #635 (Div. 1)

    传送门 A. Linova and Kingdom 题意: 给定一颗以\(1\)为根的树,现在要选定\(k\)个结点为黑点,一个黑点的贡献为从他出发到根节点经过的白点数量. 问黑点贡献总和最大为多少. ...

  3. EFCore学习记录--数据访问技术人门

    1.安装Microsoft.EntityFrameworkCore.Sqlite.Microsoft.EntityFrameworkCore.Tools包2.创建模型 数据库上下文模型:Bloggin ...

  4. Docker运行时资源限制

    Docker 运行时资源限制Docker 基于 Linux 内核提供的 cgroups 功能,可以限制容器在运行时使用到的资源,比如内存.CPU.块 I/O.网络等. 内存限制概述Docker 提供的 ...

  5. 二进制方式安装docker(非root用户启动docker)

    二进制方式安装docker(非root用户启动docker) 一.下载安装包: 地址:https://download.docker.com/linux/static/stable/x86_64/ 这 ...

  6. JVM实战篇

    1.1 JVM参数 1.1.1 标准参数 -version -help -server -cp 1.1.2 -X参数 非标准参数,也就是在JDK各个版本中可能会变动 -Xint 解释执行 -Xcomp ...

  7. javascript输出数据到文件

    function export(name, data) { var urlObject = window.URL || window.webkitURL || window var export_bl ...

  8. java之 javassist简单使用

    0x01.javassist介绍 什么是javassist,这个词一听起来感觉就很懵,对吧~ public void DynGenerateClass() { ClassPool pool = Cla ...

  9. 痞子衡嵌入式:超级下载算法(RT-UFL)开发笔记(3) - 统一FlexSPI驱动访问

    大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家介绍的是超级下载算法开发笔记(3)之统一FlexSPI驱动访问. 文接上篇 <超级下载算法(RT-UFL)开发笔记(2) - 识别当前i. ...

  10. javascript questions & code review

    javascript questions & code review refs https://github.com/learning-js-by-reading-source-codes/j ...