重要的应用程序接口(API)
布局是为 UI 定义视觉结构的过程。 在 XAML 中描述布局的主要机制是通过面板,这些面板是容器对象,可用于定位和排列其中的 UI 元素。 布局可以是 XAML 应用的昂贵部分,无论是 CPU 使用率还是内存开销。 下面是一些可用于提高 XAML 应用的布局性能的简单步骤。
化简布局结构
布局性能的最大收益来自简化 UI 元素树的分层结构。 面板存在于可视化树中,但它们是结构元素,而不是像素生成按钮或矩形等元素。 通过减少非像素生成元素的数量来简化树,通常可显著提高性能。
许多用户界面是通过嵌套面板来实现的,这会导致面板和元素形成深层且复杂的树状结构。 嵌套面板很方便,但在许多情况下,可以使用更复杂的单个面板实现相同的 UI。 使用单个面板可提供更好的性能。
何时减少布局结构
以简单方式减少布局结构(例如,从顶层页面减少一个嵌套面板)没有明显的效果。
最大的性能提升来自减少 UI 中重复的布局结构,例如 在 ListView 或 GridView 中。 这些 ItemsControl 元素使用 DataTemplate,它定义多次实例化的 UI 元素的子树。 当应用中多次重复同一子树时,对该子树性能的任何改进都会对应用的整体性能产生乘法影响。
例子
请考虑以下用户界面。
这些示例演示了实现相同 UI 的 3 种方法。 每个实现选择都会在屏幕上产生几乎相同的像素,但在实现详细信息方面存在很大差异。
选项 1:嵌套 StackPanel 元素
尽管这是最简单的模型,但它使用 5 个面板元素,并导致大量开销。
<StackPanel>
<TextBlock Text="Options:" />
<StackPanel Orientation="Horizontal">
<CheckBox Content="Power User" />
<CheckBox Content="Admin" Margin="20,0,0,0" />
</StackPanel>
<TextBlock Text="Basic information:" />
<StackPanel Orientation="Horizontal">
<TextBlock Text="Name:" Width="75" />
<TextBox Width="200" />
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Email:" Width="75" />
<TextBox Width="200" />
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Password:" Width="75" />
<TextBox Width="200" />
</StackPanel>
<Button Content="Save" />
</StackPanel>
选项 2:单个 网格
网格增加了一些复杂性,但只使用单个面板元素。
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock Text="Options:" Grid.ColumnSpan="2" />
<CheckBox Content="Power User" Grid.Row="1" Grid.ColumnSpan="2" />
<CheckBox Content="Admin" Margin="150,0,0,0" Grid.Row="1" Grid.ColumnSpan="2" />
<TextBlock Text="Basic information:" Grid.Row="2" Grid.ColumnSpan="2" />
<TextBlock Text="Name:" Width="75" Grid.Row="3" />
<TextBox Width="200" Grid.Row="3" Grid.Column="1" />
<TextBlock Text="Email:" Width="75" Grid.Row="4" />
<TextBox Width="200" Grid.Row="4" Grid.Column="1" />
<TextBlock Text="Password:" Width="75" Grid.Row="5" />
<TextBox Width="200" Grid.Row="5" Grid.Column="1" />
<Button Content="Save" Grid.Row="6" />
</Grid>
选项 3:单个 RelativePanel:
与使用嵌套面板相比,此单一面板也稍微复杂一些,但可能比 网格更容易理解和维护。
<RelativePanel>
<TextBlock Text="Options:" x:Name="Options" />
<CheckBox Content="Power User" x:Name="PowerUser" RelativePanel.Below="Options" />
<CheckBox Content="Admin" Margin="20,0,0,0"
RelativePanel.RightOf="PowerUser" RelativePanel.Below="Options" />
<TextBlock Text="Basic information:" x:Name="BasicInformation"
RelativePanel.Below="PowerUser" />
<TextBlock Text="Name:" RelativePanel.AlignVerticalCenterWith="NameBox" />
<TextBox Width="200" Margin="75,0,0,0" x:Name="NameBox"
RelativePanel.Below="BasicInformation" />
<TextBlock Text="Email:" RelativePanel.AlignVerticalCenterWith="EmailBox" />
<TextBox Width="200" Margin="75,0,0,0" x:Name="EmailBox"
RelativePanel.Below="NameBox" />
<TextBlock Text="Password:" RelativePanel.AlignVerticalCenterWith="PasswordBox" />
<TextBox Width="200" Margin="75,0,0,0" x:Name="PasswordBox"
RelativePanel.Below="EmailBox" />
<Button Content="Save" RelativePanel.Below="PasswordBox" />
</RelativePanel>
如这些示例所示,可通过多种方式实现相同的 UI。 在选择时,应慎重考虑所有权衡,包括性能、可读性和可维护性。
使用单元格网格用于重叠的用户界面
常见的 UI 要求是具有一个布局,其中元素相互重叠。 通常,填充、边距、对齐和转换用于以这种方式定位元素。 XAML 网格 控件经过优化,以提高重叠元素的布局性能。
重要 若要查看改进,请使用单元格 网格。 请勿定义 RowDefinitions 或 ColumnDefinitions。
例子
<Grid>
<Ellipse Fill="Red" Width="200" Height="200" />
<TextBlock Text="Test"
HorizontalAlignment="Center"
VerticalAlignment="Center" />
</Grid>
<Grid Width="200" BorderBrush="Black" BorderThickness="1">
<TextBlock Text="Test1" HorizontalAlignment="Left" />
<TextBlock Text="Test2" HorizontalAlignment="Right" />
</Grid>
使用面板的内置边框属性
Grid、 StackPanel、 RelativePanel 和 ContentPresenter 控件具有内置边框属性,可让你在它们周围绘制边框,而无需向 XAML 添加额外的 Border 元素。 支持内置边框的新属性包括: BorderBrush、 BorderThickness、 CornerRadius 和 Padding。 每个属性都是一个 DependencyProperty,因此可以通过绑定和动画来使用它们。 它们的设计旨在完全替代单独的 Border 元素。
如果 UI 在这些面板周围包含 边框 元素,请改用内置边框,从而在应用程序的布局结构中节省一个额外的元素。 如前所述,这可以节省大量资金,尤其是在重复 UI 的情况下。
例子
<RelativePanel BorderBrush="Red" BorderThickness="2" CornerRadius="10" Padding="12">
<TextBox x:Name="textBox1" RelativePanel.AlignLeftWithPanel="True"/>
<Button Content="Submit" RelativePanel.Below="textBox1"/>
</RelativePanel>
使用 SizeChanged 事件响应布局的变化
FrameworkElement 类公开了两个类似的事件来响应布局更改:LayoutUpdated 和 SizeChanged。 当元素在布局过程中调整大小时,你可能会使用某个事件来接收通知信息。 这两个事件的语义不同,在两个事件之间进行选择时,有重要的性能注意事项。
为了获得良好的性能,SizeChanged 几乎总是正确的选择。 SizeChanged 的语义直观。 在布局中,当更新 FrameworkElement 的大小时,它会被触发。
LayoutUpdated 也会在布局期间触发,但它具有全局语义——每当任何元素被更新时,它都会在所有元素上触发。 通常只在事件处理程序中执行本地处理,在这种情况下,代码的运行频率比需要高。 仅当需要知道何时重新定位元素而不更改大小(这不常见)时,才使用 LayoutUpdated 。
在面板之间进行选择
在各个面板之间进行选择时,性能通常不是一个考虑因素。 该选择通常是通过考虑哪个面板提供最接近所实现 UI 的布局行为做出的。 例如,如果要在 Grid、 StackPanel 和 RelativePanel 之间进行选择,则应选择提供与实现心理模型最接近的映射的面板。
每个 XAML 面板都针对良好的性能进行优化,并且所有面板都为类似的 UI 提供类似的性能。