日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

控件UI性能调优 -- SizeChanged不是万能的

發(fā)布時間:2025/3/8 编程问答 15 豆豆
生活随笔 收集整理的這篇文章主要介紹了 控件UI性能调优 -- SizeChanged不是万能的 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

簡介

我們在之前的“UWP控件開發(fā)——用NuGet包裝自己的控件“一文中曾提到XAML的布局系統(tǒng) 和平時使用上的一些問題(重寫Measure/Arrange還是使用SizeChanged?),這篇博文就來為大家簡單地描述一下XAML布局系統(tǒng)的行為,并且歸納幾個規(guī)則。當然真正的XAML布局系統(tǒng)十分復雜,本文無意把情況弄得太復雜,就從一個最簡單最直觀的例子入手,來為大家提供一點理解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。這一實現(xiàn)很好地達到了我們的需求。(而且對于這樣的簡單的情況設計器還是能夠正常處理的)

?

對SizeChanged的概述

但是這卻隱藏著問題。首先,SizeChanged事件是由一輪Measure/Arrange完成后觸發(fā)的。

XAML的核心布局流程,是從根元素 即頁面開始,遞歸向下。第一次挨個調(diào)用Measure,提供能用的大小,并確定每個子項所希望的空間大小;再來一次挨個調(diào)用Arrange,提供能用大小,按實際情況給子項分配空間(不一定能滿足它們的需要)和確定位置。本例的過程中就涉及到OuterBorderInnerBorder,它們以此能根據(jù)Border類布局規(guī)則確定自己的大小,即刨去BorderThickness。

?

這之后,OuterBorderInnerBorder實際大小就確定了。如果和上次布局的結果不一樣,OuterBorder就會觸發(fā)SizeChanged事件(是Chang*ed*哦),改變InnerBorder設定大小。因為設定大小變化了,會引發(fā)新一輪遞歸Measure和Arrange。這一次之后,OuterBorder的大小不變,InnerBorder的大小變成OuterBorder的一半。之后沒有事件和布局再被觸發(fā),大家相安無事。

但實際上,布局進行了兩輪。如果Visual Tree很大的話,后果可想而知。

?

修改后的過程

那么,根據(jù)我們剛才介紹的過程,從Measure出發(fā),實現(xiàn)如下(去掉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環(huán)節(jié),一次性搞定布局。原因就在于我們在布局開始之前就搞定了Size信息,而不是在布局結束后再把它辛辛苦苦計算出來的Size踩在地上并讓它重來一遍。在我們設定的需求看來,甚至無需插手Arrange流程。

當然,這免不了地要自己計算Size,可能需要手動減去BorderThickness的大小,甚至還可能要自行調(diào)用一次Measure。復雜的具體情況需要具體分析。

?

性能對比

通過調(diào)試工具,我們來對比一下兩種方法的實際性能:

SizeChangedMeasureOverride

在兩種實現(xiàn)下,分別大力地快速拖動窗口大小。。。

其中柱形圖是一段時間內(nèi)UI線程的響應情況,占最大比重的橙色是布局行為。下面的扇形圖是選中差不多的時間段內(nèi),布局消耗的占比情況。

可見通過提供Measure策略的方式,即使是這樣簡單的設定,性能提升也還看得出來。

?

如果我們發(fā)揚奧卡姆剃刀的精神,不要自己寫這陌生的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>

OnApplyTemplateMeasureOverride都可以不要了,整個code behind十分清爽。行為看起來差不多,那么性能呢?

想必Grid作為標準控件,優(yōu)化得應該很好了,但它本身就有一點復雜,和MesureOverride的實現(xiàn)在性能上有一點點差距。但畢竟我們這樣簡單的例子對于Grid太不公平了,對于更為復雜的情況,還是要使用Grid的。

?

總結

說了這么多,主要是表現(xiàn)一下不必要的布局對于性能的影響,以及對于這樣的簡單情況如何替代原有實現(xiàn)。

對于布局有影響的操作大致有:

  • 改變大小:設置WidthHeight、MaxHeight(如果影響到ActualHeight),或者修改MarginThickness
  • 改變內(nèi)容:設置ContentContentTemplateDataTemplateTextBox.Text
  • 改變某些屬性:如VisibleOrientationImage.Stretch
  • 手動調(diào)用布局方法:InvalidateMeasureUpdateLayout

如果調(diào)用了這些屬性方法,就需要顧慮一下是否會造成不必要的布局了,特別是在SizeChanged這樣的由布局觸發(fā)的事件里。當然這也是一般論,如果控件本來就隱藏了,或者Template改變了原有外型,這些內(nèi)容也自然隨之變化。

P.S. RenderTransform是不造成重新布局的。

?

另外,就本文的例子來說,并不是要大家都把SizeChanged改寫成MeasureOverride

MeasureOverride給了一個好處,就是第一時間獲知高層布局的相關信息,也就能趕在布局前最后設置一次屬性;SizeChanged能給出復雜布局計算后的最新尺寸,如果自己來計算的話沒有意義。總之還是要因地制宜。

?

雖然本文的例子十分簡單,可能沒有多少實際意義,不過希望通過它介紹的流程,能為大家的開發(fā)提供一點新的思路。

?

參考

[1] 開源的WPF中的Border.MeasureOverride實現(xiàn):http://referencesource.microsoft.com/#PresentationFramework/src/Framework/System/Windows/Controls/Border.cs,00c166b0e025bc8d

[2] WPF中的Grid.MeasureOverride實現(xiàn):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不是万能的的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。