wxWidgets源码分析(7) - 窗口尺寸
窗口尺寸
概述
类型 | 说明 |
---|---|
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();
}
注意看到,这里调用了两个方法,CalcMin
和RecalcSizes
。
先看下CalcMin
方法,每种Sizer的处理方式不同,会有各自的方法,比如wxBoxSizer::CalcMin
方法,它首先找到所有的item,调用item的CalcMin
方法,这个方法只不过最终获取到了最小的窗口值而已,并没有进行窗口调整动作:
- 如果item也是一个Sizer,那么继续调用sizer的
GetMinSize
; - 如果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
方法:
- item根据自身的属性,比如对齐属性,expand属性,设置自己绑定的窗口的大小;
- 调用绑定窗口的大小,绑定的窗口可能是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内部的子窗口,也就是控件的窗口,此时调用的消息处理函数是子窗口的处理函数。
注意:
- 对于首次设置,也就是用户没有调整过窗口的大小,此时需要设置的位置和大小与当前Window中保存的大小是相同的,所以直接构建一个SizeEvent消息,然后调用HandleWindowEvent就可以了;
- 对于用户调整窗口大小的厂家,此时的位置和消息与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));
要求:
- 文本必须以'\n'结尾;
- 在通过
PositionToCoords
计算高度时,必须保证此时TextCtrl框的宽度为最终真实宽度;
wxWidgets源码分析(7) - 窗口尺寸的更多相关文章
- wxWidgets源码分析(6) - 窗口关闭过程
目录 窗口关闭过程 调用流程 关闭文档 删除视图 删除文档对象 关闭Frame App清理 多文档窗口的关闭 多文档父窗口关闭 多文档子窗口关闭 窗口的正式删除 窗口关闭过程总结 如何手工删除view ...
- wxWidgets源码分析(5) - 窗口管理
窗口管理 所有的窗口均继承自wxTopLevelWindows: WXDLLIMPEXP_DATA_CORE(wxWindowList) wxTopLevelWindows; wxTopLevelWi ...
- wxWidgets源码分析(8) - MVC架构
目录 MVC架构 wxDocManager文档管理器 模板类创建文档对象 视图对象的创建 创建顺序 框架菜单命令的执行过程 wxDocParentFrame菜单入口 wxDocManager类的处理 ...
- wxWidgets源码分析(9) - wxString
目录 wxString wxString的中文字符支持 Windows Linux Unicode Linux UTF-8 总结 wxString与通用字符串的转换 wxString对象的创建 将wx ...
- wxWidgets源码分析(4) - 消息处理过程
目录 消息处理过程 消息如何到达wxWidgets Win32消息与wxWidgets消息的转换 菜单消息处理 消息处理链(基于wxEvtHandler) 消息处理链(基于wxWindow) 总结 消 ...
- wxWidgets源码分析(2) - App主循环
目录 APP主循环 MainLoop 消息循环对象的创建 消息循环 消息派发 总结 APP主循环 MainLoop 前面的wxApp的启动代码可以看到,执行完成wxApp::OnInit()函数后,接 ...
- wxWidgets源码分析(1) - App启动过程
目录 APP启动过程 wxApp入口定义 wxApp实例化准备 wxApp的实例化 wxApp运行 总结 APP启动过程 本文主要介绍wxWidgets应用程序的启动过程,从app.cpp入手. wx ...
- Sentinel源码分析-滑动窗口统计原理
滑动窗口技术是Sentinel比较关键的核心技术,主要用于数据统计 通过分析StatisticSlot来慢慢引出这个概念 @Override public void entry(Context con ...
- wxWidgets源码分析(3) - 消息映射表
目录 消息映射表 静态消息映射表 静态消息映射表处理过程 动态消息映射表 动态消息映射表处理过程 消息映射表 消息是GUI程序的核心,所有的操作行为均通过消息传递. 静态消息映射表 使用静态Event ...
随机推荐
- Codeforces Round #651 (Div. 2) E. Binary Subsequence Rotation(dp)
题目链接:https://codeforces.com/contest/1370/problem/E 题意 给出两个长为 $n$ 的 $01$ 串 $s$ 和 $t$,每次可以选择 $s$ 的一些下标 ...
- Uva 12436 Rip Van Winkle's Code
Rip Van Winkle was fed up with everything except programming. One day he found a problem whichrequir ...
- 一张图解决ThreadLocal
一张图解决ThreadLocal 一.前言 年底梳理知识体系时,研究了一下ThreadLocal的源码,整理了一张核心图. 想着,都走到这一步了,那就写一篇深度解读的文章吧.看过我之前文章的小伙伴都知 ...
- CF1475-D. Cleaning the Phone
CF1475-D. Cleaning the Phone 题意: 手机上有很多应用非常占用内存,你要清理内存.对于每个应用\(i\)有以下描述:应用\(i\)占用了\(a_i\)的空间,它的方便度为\ ...
- spark 一、编程指南
总览 第一.每个spark 应用都有一个驱动程序去运行着主函数和再每个节点上的并行操作. spark提供了一个RDD(弹性分布式数据集)的数据集合,可以通过不同的节点并行操作运算,可以通过hdfs文件 ...
- HDU - 5115 Dire Wolf (非原创)
Dire wolves, also known as Dark wolves, are extraordinarily large and powerful wolves. Many, if not ...
- axios增加自定义headers,页面上出现,服务端收不到
问题 axios增加自定义headers,页面上出现,服务端收不到 原因 vue-cli起的服务是用node-http-proxy中间件处理的 默认是只有几个常用的header,自定义header是直 ...
- mybatis(六)插件机制及分页插件原理
转载:https://www.cnblogs.com/wuzhenzhao/p/11120848.html MyBatis 通过提供插件机制,让我们可以根据自己的需要去增强MyBatis 的功能.需要 ...
- u-boot 移植 --->2、在u-boot新增SOC和板子
本次主要是要新增一个samsung的芯片到u-boot中,网上查阅资料发现s5pc1xx是与手上的S5PV210的友善的Tiny版子寄存器兼容的比较多,所以就准备以他为基础增加一个我的板子的支持到u- ...
- windows 10 remote desktop
windows 10 remote desktop https://support.microsoft.com/en-us/help/4028379/windows-10-how-to-use-rem ...