简介

我们在之前的“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 / 2;_inner.Height = _border.ActualHeight / 2;};}}
}

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 / 2;_inner.Height = availableSize.Height / 2;}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不是万能的相关推荐

  1. 一直以为对性能调优十分了解,直到阿里大牛到来,才知道菜是原罪

    什么是性能调优? 什么是性能调校呢?一般是当用户抱怨"太慢了"."性能不足"."软硬件需要升级了"等问题时,提供较佳的性能.但不是要解决用户 ...

  2. 精通 WPF UI Virtualization (提升 OEA 框架中 TreeGrid 控件的性能)

    精通 WPF UI Virtualization (提升 OEA 框架中 TreeGrid 控件的性能) 原文:精通 WPF UI Virtualization (提升 OEA 框架中 TreeGri ...

  3. 大数据技术之_19_Spark学习_07_Spark 性能调优 + 数据倾斜调优 + 运行资源调优 + 程序开发调优 + Shuffle 调优 + GC 调优 + Spark 企业应用案例

    大数据技术之_19_Spark学习_07 第1章 Spark 性能优化 1.1 调优基本原则 1.1.1 基本概念和原则 1.1.2 性能监控方式 1.1.3 调优要点 1.2 数据倾斜优化 1.2. ...

  4. CoreAnimation6-基于定时器的动画和性能调优

    基于定时器的动画 定时帧 动画看起来是用来显示一段连续的运动过程,但实际上当在固定位置上展示像素的时候并不能做到这一点.一般来说这种显示都无法做到连续的移动,能做的仅仅是足够快地展示一系列静态图片,只 ...

  5. iOS应用性能调优的25个建议和技巧【转】

    转载自:http://blog.jobbole.com/37984/ 首页 最新文章 资讯 程序员 设计 IT技术 创业 在国外 营销 趣文 特别分享 更多 > - Navigation - 首 ...

  6. eBay的Elasticsearch性能调优实践

    https://www.sohu.com/a/220443841_467759 Elasticsearch 是一个基于 Apache Lucene 的开源搜索和分析引擎,允许用户近实时地存储.搜索和分 ...

  7. Spark的性能调优

    下面这些关于Spark的性能调优项,有的是来自官方的,有的是来自别的的工程师,有的则是我自己总结的. 基本概念和原则 首先,要搞清楚Spark的几个基本概念和原则,否则系统的性能调优无从谈起: 每一台 ...

  8. 【Elasticsearch】eBay上的Elasticsearch性能调优实践

    1.概述 翻译:eBay上的Elasticsearch性能调优实践 中文版:eBay上的Elasticsearch性能调优实践 Elasticsearch 是一个基于 Apache Lucene 的开 ...

  9. php xingnengfenxi_PHP 性能分析第三篇: 性能调优实战

    在本系列的 第一篇 中,我们介绍了 XHProf .而在 第二篇 中,我们深入研究了 XHGui UI, 现在最后一篇,让我们把 XHProf /XHGui 的知识用到工作中! 性能调优 不用运行的代 ...

最新文章

  1. 链表中倒数第k个节点 1
  2. FPGA设计思想之“逻辑复制”
  3. adb devices 找不到设备的解决方法,亲测,超管用
  4. linux中ed编辑器手册,脚本编辑器 - Navicat 15 for Linux 产品手册
  5. Linux中如何使用Htop监控工具?【网络安全】
  6. 操作系统:Windows映射网络文件夹的方法介绍
  7. HashMap 源码阅读
  8. java 自定义事件_在Java中创建自定义事件
  9. natapp在linux服务器上的使用
  10. 如何阻止事件冒泡和浏览器的默认行为
  11. 怎么把PPT幻灯片里背景图片拿出来
  12. 【Godot】组合键的实现
  13. CAN芯片_ TJA1051T/3
  14. wireshark-filter帮助手册
  15. js 操作数组 push splice
  16. 为什么c语言中会引入ASCII,C语言中ASCII码是什么意思?
  17. ARM Cortex-A系列编程指南之ARMv8 A -- 第二章 ARMv8 A架构和处理器
  18. 扫地机器人哪个牌子好?合格的扫地机器人推荐
  19. android 微信缩小通话界面_Android仿微信多人音视频通话界面
  20. revit2018注册表删除_如何完全卸载Revit

热门文章

  1. 局部配置和全局配置_06. 教你零基础搭建小程序(解读全局配置文件-tabBar字段)...
  2. python pandas合并多个excel_python pandas合并多个excel(xls和xlsx)文件(弹窗选择文件夹和保存文件)...
  3. ci php做记录删除,CI(CodeIgniter)框架中的增删改查操作_PHP教程
  4. 请编写一个程序,请将字符串中所有字母全部向后移一位,最后一个字母放在字符串的开头,最后将新的字符串输出。
  5. 不采取任何措施 盒盖_得了癌症如果不化疗能活多久?医生的答案很实在
  6. SPI 读取不同长度 寄存器_[读书笔记]《计算机科学速成课》—6 寄存器和内存
  7. java定义一个矩阵的类_java写入一个矩阵,如何编程求该矩阵的秩
  8. 计算机专业接本应用心理学,专接本接应用心理学但遇到阻挠?
  9. java写一个99到0_Java中一个普通的循环为何从10开始到99连续相乘会得到0?
  10. latex 算法_GitHub项目awesome-latex-drawing新增内容(四):绘制贝叶斯网络