UWP control development-SizeChanged performance tuning, uwpsizechanged
Introduction
In the previous article "UWP control development -- packaging your own controls with NuGet", we mentioned some issues in the layout system and time-use of XAML (rewrite Measure/Arrange or use SizeChanged ?), This blog post briefly describes the behavior of the XAML Layout System and summarizes several rules. Of course, the real XAML layout system is very complicated. This article does not mean to make the situation too complex. We will start with the simplest and most intuitive example to provide you with a new idea of understanding the XAML layout.
Problem description
Suppose we have a Templated Control, and its XAML description is as follows:
<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>
The two Border are nested and the side width is 20. Our goal is to change the size of InnerBorder through code. For example, the length and width are both half the width of OuterBorder.
First try
We can easily write such code:
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. This implementation meets our needs. (The designer can still handle such a simple situation)
SizeChanged Overview
But this hides the problem. First, the SizeChanged event is triggered after a round of Measure/Arrange.
The core layout process of XAML is to recursively go down from the root element, that is, the page. The first one calls Measure one by one to provide the available size and determine the desired space size for each subitem. The second one calls Arrange one by one to provide the available size, allocate space to sub-items based on actual conditions (may not meet their needs) and determine locations. In this example, OuterBorder and InnerBorder are involved, so that they can determine their own size according to the Border class layout rules, that is, to split BorderThickness.
After thatActualThe size is determined. If the result is different from the previous layout, OuterBorder triggers the SizeChanged event (Chang *Ed *Oh) changed InnerBorder'sSetSize. A new round of recursive Measure and Arrange will be triggered because the set size changes. After this time, the OuterBorder size remains the same, and the InnerBorder size is half that of OuterBorder. After that, no events and layout will be triggered, so everyone will be at ease.
But in fact, the layout went through two rounds. If the Visual Tree is large, the consequences can be imagined.
Modification Process
Then, based on the process we just introduced, implement the following from Measure (remove the SizeChanged event binding and override MeasureOvrride method ):
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 is the OuterBorder size if (_ inner! = Null) {_ inner. Width = availableSize. Width/2; _ inner. Height = availableSize. Height/2;} return base. MeasureOverride (availableSize );}}
After setting the size, go to the real measure step to get the layout done at one time. The reason is that we have completed the Size information before the layout starts, instead of stepping on the ground and asking it to repeat it after the layout is over. In our set needs, we don't even need to intervene in the Arrange process.
Of course, this inevitably requires you to calculate the Size yourself. You may need to manually subtract the BorderThickness Size, or even call the measurement yourself. Complex details need to be analyzed.
Performance Comparison
Through the debugging tool, we can compare the actual performance of the two methods:
SizeChanged |
MeasureOverride |
|
|
|
|
In the two implementations, the window size is quickly dragged respectively...
The column chart is the response of the UI thread within a period of time, and the orange color that accounts for the largest proportion is the layout behavior. The following slice chart shows the proportion of layout consumption in the selected time range.
It can be seen that, by providing a Measure policy, performance improvement can be seen even with such simple settings.
If we develop the spirit of Occam Razor, do not write this strange MeasureOverride by yourself. How can we use 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 and MeasureOverride can be avoided, and the entire code behind is refreshing. The behavior looks similar. What about the performance?
Grid should have been well optimized as a standard control, but it is a little complicated. It is slightly different from the implementation of MesureOverride in terms of performance. After all, this simple example is unfair to the Grid. For more complex cases, we still need to use the Grid.
Summary
After talking about this, it mainly shows the impact of unnecessary layout on performance and how to replace the original implementation in such a simple situation.
Operations that have an impact on the layout include:
- Change size: Set Width and Height, or modify Margin and Thickness.
- Change Content: Set Content, ContentTemplate, DataTemplate, TextBox. Text, etc.
- Change certain attributes, such as Visible, Orientation, and Image. Stretch.
- Manually call the layout method: InvalidateMeasure, UpdateLayout, etc.
If these attribute methods are called, you need to worry about whether they will cause unnecessary layout, especially in events such as SizeChanged triggered by layout. Of course, this is also a general theory. If the control is hidden, or the Template changes the original appearance, the content will naturally change.
P.S. RenderTransform does not cause re-layout.
In addition, for the example in this article, we do not need to rewrite SizeChanged to MeasureOverride.
MeasureOverride provides the benefit of first learning the relevant information about the High-Level layout, and finally setting the attribute before the layout; SizeChanged can provide the latest size after calculation of the Complex layout, it makes no sense to calculate it by yourself. In short, we need to adapt to local conditions.
Although the example in this article is very simple and may not have much practical significance, I hope that the process described in it will provide a new idea for your development.
Reference
[1] Border. MeasureOverride implementation in open-source WPF: http://referencesource.microsoft.com/#PresentationFramework/src/Framework/System/Windows/Controls/Border.cs,00c166b0e025bc8d
[2] Grid. MeasureOverride implementation in WPF: http://referencesource.microsoft.com/#PresentationFramework/src/Framework/System/Windows/Controls/Grid.cs,f9ce1d6be154348a