控件UI性能调优 -- SizeChanged不是万能的
简介
我们在之前的“UWP控件开发——用NuGet包装自己的控件“一文中曾提到XAML的布局系统 和平时使用上的一些问题(重写Measure/Arrange还是使用SizeChanged?),这篇博文就来为大家简单地描述一下XAML布局系统的行为,并且归纳几个规则。当然真正的XAML布局系统十分复杂,本文无意把情况弄得太复杂,就从一个最简单最直观的例子入手,来为大家提供一点理解XAML布局的新思路。
问题描述
假设我们有一个Templated Control,其XAML描述如下:
<Style TargetType="local:CustomControl1">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:CustomControl1">
<Border x:Name="OuterBorder"
BorderBrush="Yellow"
BorderThickness="20">
<Border x:Name="InnerBorder"
BorderThickness="20"
BorderBrush="Red" />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
两个Border嵌套,边宽20。我们的目的就是通过代码来改变InnerBorder的大小。比如长宽都变成OuterBorder的一半大。
首次尝试
我们很容易就写出了这样的代码:
public sealed class CustomControl1 : Control
{
public CustomControl1() {...} private Border _border;
private Border _inner;
protected override void OnApplyTemplate()
{
base.OnApplyTemplate(); _border = GetTemplateChild("OuterBorder") as Border;
_inner = GetTemplateChild("InnerBorder") as Border;
if (_border != null && _inner != null)
{
_border.SizeChanged += (s, e) => {
_inner.Width = _border.ActualWidth / ;
_inner.Height = _border.ActualHeight / ;
};
}
}
}
works perfectly。这一实现很好地达到了我们的需求。(而且对于这样的简单的情况设计器还是能够正常处理的)
对SizeChanged的概述
但是这却隐藏着问题。首先,SizeChanged事件是由一轮Measure/Arrange完成后触发的。
XAML的核心布局流程,是从根元素 即页面开始,递归向下。第一次挨个调用Measure,提供能用的大小,并确定每个子项所希望的空间大小;再来一次挨个调用Arrange,提供能用大小,按实际情况给子项分配空间(不一定能满足它们的需要)和确定位置。本例的过程中就涉及到OuterBorder和InnerBorder,它们以此能根据Border类布局规则确定自己的大小,即刨去BorderThickness。
这之后,OuterBorder和InnerBorder的实际大小就确定了。如果和上次布局的结果不一样,OuterBorder就会触发SizeChanged事件(是Chang*ed*哦),改变InnerBorder的设定大小。因为设定大小变化了,会引发新一轮递归Measure和Arrange。这一次之后,OuterBorder的大小不变,InnerBorder的大小变成OuterBorder的一半。之后没有事件和布局再被触发,大家相安无事。
但实际上,布局进行了两轮。如果Visual Tree很大的话,后果可想而知。
修改后的过程
那么,根据我们刚才介绍的过程,从Measure出发,实现如下(去掉SizeChanged的事件绑定并override MeasureOvrride方法):
public sealed class CustomControl1 : Control
{
public CustomControl1() {...} private Border _border;
private Border _inner;
protected override void OnApplyTemplate()
{
base.OnApplyTemplate(); _border = GetTemplateChild("Border") as Border;
_inner = GetTemplateChild("InnerBorder") as Border;
} protected override Size MeasureOverride(Size availableSize)
{
// availableSize就是OuterBorder的大小
if (_inner != null)
{
_inner.Width = availableSize.Width / ;
_inner.Height = availableSize.Height / ;
} return base.MeasureOverride(availableSize);
}
}
设定大小后再进入真正的measure环节,一次性搞定布局。原因就在于我们在布局开始之前就搞定了Size信息,而不是在布局结束后再把它辛辛苦苦计算出来的Size踩在地上并让它重来一遍。在我们设定的需求看来,甚至无需插手Arrange流程。
当然,这免不了地要自己计算Size,可能需要手动减去BorderThickness的大小,甚至还可能要自行调用一次Measure。复杂的具体情况需要具体分析。
性能对比
通过调试工具,我们来对比一下两种方法的实际性能:
SizeChanged | MeasureOverride |
在两种实现下,分别大力地快速拖动窗口大小。。。
其中柱形图是一段时间内UI线程的响应情况,占最大比重的橙色是布局行为。下面的扇形图是选中差不多的时间段内,布局消耗的占比情况。
可见通过提供Measure策略的方式,即使是这样简单的设定,性能提升也还看得出来。
如果我们发扬奥卡姆剃刀的精神,不要自己写这陌生的MeasureOverride,用Grid来做如何?
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="2*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="2*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Border x:Name="Border"
BorderBrush="Yellow"
BorderThickness="20"
Grid.RowSpan="3" Grid.ColumnSpan="3"/>
<Border x:Name="InnerBorder"
BorderThickness="20"
BorderBrush="Red"
Grid.Column="1" Grid.Row="1"/>
</Grid>
OnApplyTemplate和MeasureOverride都可以不要了,整个code behind十分清爽。行为看起来差不多,那么性能呢?
想必Grid作为标准控件,优化得应该很好了,但它本身就有一点复杂,和MesureOverride的实现在性能上有一点点差距。但毕竟我们这样简单的例子对于Grid太不公平了,对于更为复杂的情况,还是要使用Grid的。
总结
说了这么多,主要是表现一下不必要的布局对于性能的影响,以及对于这样的简单情况如何替代原有实现。
对于布局有影响的操作大致有:
- 改变大小:设置Width、Height、MaxHeight(如果影响到ActualHeight),或者修改Margin、Thickness等
- 改变内容:设置Content、ContentTemplate、DataTemplate、TextBox.Text等
- 改变某些属性:如Visible、Orientation、Image.Stretch等
- 手动调用布局方法:InvalidateMeasure、UpdateLayout等
如果调用了这些属性方法,就需要顾虑一下是否会造成不必要的布局了,特别是在SizeChanged这样的由布局触发的事件里。当然这也是一般论,如果控件本来就隐藏了,或者Template改变了原有外型,这些内容也自然随之变化。
P.S. RenderTransform是不造成重新布局的。
另外,就本文的例子来说,并不是要大家都把SizeChanged改写成MeasureOverride。
MeasureOverride给了一个好处,就是第一时间获知高层布局的相关信息,也就能赶在布局前最后设置一次属性;SizeChanged能给出复杂布局计算后的最新尺寸,如果自己来计算的话没有意义。总之还是要因地制宜。
虽然本文的例子十分简单,可能没有多少实际意义,不过希望通过它介绍的流程,能为大家的开发提供一点新的思路。
参考
[1] 开源的WPF中的Border.MeasureOverride实现:http://referencesource.microsoft.com/#PresentationFramework/src/Framework/System/Windows/Controls/Border.cs,00c166b0e025bc8d
[2] WPF中的Grid.MeasureOverride实现:http://referencesource.microsoft.com/#PresentationFramework/src/Framework/System/Windows/Controls/Grid.cs,f9ce1d6be154348a
[3] SizeChanged事件参考:https://msdn.microsoft.com/en-us/library/windows/apps/windows.ui.xaml.frameworkelement.sizechanged
控件UI性能调优 -- SizeChanged不是万能的的更多相关文章
- 性能调优的Windows窗体DataGridView控件
性能调优的Windows窗体DataGridView控件 . 净框架4.5 在处理大量数据时, DataGridView 控制可以消耗大量的内存开销,除非你仔细地使用它. 在客户有限的内存,你 ...
- 精通 WPF UI Virtualization (提升 OEA 框架中 TreeGrid 控件的性能)
原文:精通 WPF UI Virtualization (提升 OEA 框架中 TreeGrid 控件的性能) 本篇博客主要说明如何使用 UI Virtualization(以下简称为 UIV) 来提 ...
- CoreAnimation6-基于定时器的动画和性能调优
基于定时器的动画 定时帧 动画看起来是用来显示一段连续的运动过程,但实际上当在固定位置上展示像素的时候并不能做到这一点.一般来说这种显示都无法做到连续的移动,能做的仅仅是足够快地展示一系列静态图片,只 ...
- iOS-------应用性能调优的25个建议和技巧
性能对 iOS 应用的开发尤其重要,如果你的应用失去反应或者很慢,失望的用户会把他们的失望写满App Store的评论.然而由于iOS设备的限制,有时搞好性能是一件难事.开发过程中你会有很多需要注意的 ...
- iOS应用性能调优建议
本文来自iOS Tutorial Team 的 Marcelo Fabri,他是Movile的一名 iOS 程序员.这是他的个人网站:http://www.marcelofabri.com/,你还可以 ...
- iOS应用性能调优的25个建议和技巧
本文来自iOS Tutorial Team 的 Marcelo Fabri,他是Movile的一名 iOS 程序员.这是他的个人网站:http://www.marcelofabri.com/,你还可以 ...
- [转]iOS应用性能调优的25个建议和技巧
写在前面 本文来自iOS Tutorial Team 的 Marcelo Fabri,他是Movile的一名 iOS 程序员.这是他的个人网站:http://www.marcelofabri.com/ ...
- 【Java/Android性能优3】Android性能调优工具TraceView使用介绍
本文转自:http://blog.csdn.net/innost/article/details/9008691 在软件开发过程中,想必很多读者都遇到过系统性能问题.而解决系统性能问题的几个主要步骤是 ...
- PHP 性能分析第三篇: 性能调优实战
注意:本文是我们的 PHP 性能分析系列的第三篇,点此阅读 PHP 性能分析第一篇: XHProf & XHGui 介绍 ,或 PHP 性能分析第二篇: 深入研究 XHGui. 在本系列的 ...
随机推荐
- Daily Scrum 12.15
今日完成任务: 完成关于主页右侧资源显示的算法优化:解决了下载资源时的异常. 遇到困难: 编译课设这周要检查,小组成员的大部分时间在完成编译课设,时间很紧. 明日任务: 孙思权 完成第二组提供的数据库 ...
- 拼sql条件时判断 是不是当前时间是不是周五,如果今天不是周五,就选上周五
if (Request.QueryString["start"] == null) { for (int i = 0; i < 6; i++) { if (DateTime. ...
- Tiled Map地图编辑器键盘快捷键
Tiled是款不错的地图编辑器,不过快捷键真是隐蔽啊,不看github上得wiki根本不知道,用的过程中查英文文档总是觉得慢,所以翻译成了中文. 通用 右键点击图块(tile):复制图块到图章刷(拖动 ...
- Devexpress 汉化
DevExpress是一个比较有名的界面控件套件,提供了一系列的界面控件套件的DotNet界面控件.对于较老的版本(例如之前项目中遇到的dev9),对于汉化(应该说本地化Localization)支持 ...
- Spring 学习笔记 4. 尚硅谷_佟刚_Spring_属性配置细节
1,字面值 •字面值:可用字符串表示的值,可以通过 <value> 元素标签或 value 属性进行注入. •基本数据类型及其封装类.String 等类型都可以采取字面值注入的方式 •若字 ...
- 用Java程序判断是否是闰年
我们知道,(1)如果是整百的年份,能被400整除的,是闰年:(2)如果不是整百的年份,能被4整除的,也是闰年.每400年,有97个闰年.鉴于此,程序可以作以下设计: 第一步,判断年份是否被400整除, ...
- Linux下Chrome浏览器不支持WebGL的解决方式。
今天使用Chrome浏览器,总是报这样一个错误: Uncaught TypeError: Cannot read property 'canvas' of null. 细看之下是无法获取WebGL上下 ...
- Font Awesome图标库
Font Awesome 是一个非常方便的图标库.这些图标都是矢量图形,被保存在 .svg 的文件格式中.这些图标就和字体一样,你可以通过像素单位指定它们的大小,它们将会继承其父HTML元素的字体大小 ...
- 【转】JavaScript下对去重算法的优化
本人较懒,直接将链接附上: JavaScript下去重算法优化:http://www.linuxde.net/2013/02/12062.html
- windows 7下qtcreator里QWT文件的pro配置
http://blog.chinaunix.net/uid-20717410-id-272331.html 把编译好的qwt的include文件夹下面 所有的.h文件 复制到qt目录下 然后在pro里 ...