优化 XAML 布局Optimize your XAML layout

重要的 APIImportant APIs

布局是为 UI 定义可视结构的过程。Layout is the process of defining the visual structure for your UI. 描述 XAML 中的布局的主要机制是通过面板,面板是可使你在其中定位和安排 UI 元素的容器对象。The primary mechanism for describing layout in XAML is through panels, which are container objects that enable you to position and arrange the UI elements within them. 布局可能是 XAML 应用中最耗费资源的部分,无论在 CPU 使用率还是内存开销方面。Layout can be an expensive part of a XAML app—both in CPU usage and memory overhead. 以下是可为提高 XAML 应用的布局性能而采取的步骤。Here are some simple steps you can take to improve the layout performance of your XAML app.

减少布局结构Reduce layout structure

布局性能的最大收益来自于简化 UI 元素树的层次结构。The biggest gain in layout performance comes from simplifying the hierarchical structure of the tree of UI elements. 可视化树中存在面板,但它们是结构元素,而不是像素生成元素(如 ButtonRectangle)。Panels exist in the visual tree, but they are structural elements, not pixel producing elements like a Button or a Rectangle. 通过减少非像素生成元素来简化树通常可以显著提高性能。Simplifying the tree by reducing the number of non-pixel-producing elements typically provides a significant performance increase.

许多 UI 通过嵌套面板来实现,这可能导致更深层、复杂的面板和元素树。Many UIs are implemented by nesting panels which results in deep, complex trees of panels and elements. 嵌套面板很方便,但在许多情况下,使用更复杂的单个面板可以实现相同的 UI。It is convenient to nest panels, but in many cases the same UI can be achieved with a more complex single panel. 使用单个面板可提供更好的性能。Using a single panel provides better performance.

何时减少布局结构When to reduce layout structure

通过琐碎的方式减少布局结构(例如,从你的顶层页面中减少一个嵌套面板)没有明显的影响。Reducing layout structure in a trivial way—for example, reducing one nested panel from your top-level page—does not have a noticeable effect.

最大的性能收益来自于减少 UI 中(如在 ListViewGridView 中)的重复布局结构。The largest performance gains come from reducing layout structure that's repeated in the UI, like in a ListView or GridView. 这些 ItemsControl 元素使用 DataTemplate,后者可定义多次实例化的 UI 元素的子树。These ItemsControl elements use a DataTemplate, which defines a subtree of UI elements that is instantiated many times. 当在应用中多次重复相同的子树时,对该子树的任何性能改进都对应用的整体性能具有倍增效果。When the same subtree is being duplicated many times in your app, any improvements to the performance of that subtree has a multiplicative effect on the overall performance of your app.

示例Examples

请考虑以下 UI。Consider the following UI.

表单布局示例

这些示例显示了实现相同 UI 的 3 种方法。These examples shows 3 ways of implementing the same UI. 每个实现选择都会在屏幕上产生几乎相同的像素,但在实现细节方面截然不同。Each implementation choice results in nearly identical pixels on the screen, but differs substantially in the implementation details.

选项 1:嵌套 StackPanel 元素Option1: Nested StackPanel elements

虽然这是最简单的模型,它仍然使用 5 个面板元素并导致大量的开销。Although this is the simplest model, it uses 5 panel elements and results in significant overhead.

  <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:单个 GridOption 2: A single Grid

Grid 增加了一些复杂性,但仅使用单个面板元素。The Grid adds some complexity, but uses only a single panel element.

<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:单个 RelativePanelOption 3: A single RelativePanel:

此单个面板同样比使用嵌套面板更复杂一些,但可能比 Grid 更易于理解和维护。This single panel is also a bit more complex than using nested panels, but may be easier to understand and maintain than a Grid.

<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 的方法。As these examples show, there are many ways of achieving the same UI. 你应该在选择之前权衡所有利弊,包括性能、可读性和可维护性。You should choose by carefully considering all the tradeoffs, including performance, readability, and maintainability.

为重叠 UI 使用单个单元网格Use single-cell grids for overlapping UI

常见的 UI 要求是具有元素互相重叠的布局。A common UI requirement is to have a layout where elements overlap each other. 通常填充、边距、对齐和转换用于以这种方式定位元素。Typically padding, margins, alignments, and transforms are used to position the elements this way. 已优化 XAML Grid 控件以提高重叠元素的布局性能。The XAML Grid control is optimized to improve layout performance for elements that overlap.

重要提示  若要查看改进,请使用单个单元 GridImportant  To see the improvement, use a single-cell Grid. 不要定义 RowDefinitionsColumnDefinitionsDo not define RowDefinitions or ColumnDefinitions.

示例Examples

<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>

网格中的两个文本块

使用面板的内置边框属性Use a panel's built-in border properties

GridStackPanelRelativePanelContentPresenter 控件具有内置边框属性,可用于在其周围绘制边框,而无需向 XAML 添加额外的 Border 元素。Grid, StackPanel, RelativePanel, and ContentPresenter controls have built-in border properties that let you draw a border around them without adding an additional Border element to your XAML. 支持内置边框的新属性是:BorderBrushBorderThicknessCornerRadiusPaddingThe new properties that support the built-in border are: BorderBrush, BorderThickness, CornerRadius, and Padding. 其中每一属性都是 DependencyProperty,因此你可以将其用于绑定和动画。Each of these is a DependencyProperty, so you can use them with bindings and animations. 它们设计为一个单独 Border 元素的完整替代。They’re designed to be a full replacement for a separate Border element.

如果你的 UI 具有围绕这些面板的 Border 元素,请改用内置边框,这可以在应用的布局结构中节省额外的元素。If your UI has Border elements around these panels, use the built-in border instead, which saves an extra element in the layout structure of your app. 如前面所述,这可以节省大量资源,在重复 UI 的情况下尤其如此。As mentioned previously, this can be a significant savings, especially in the case of repeated UI.

示例Examples

<RelativePanel BorderBrush="Red" BorderThickness="2" CornerRadius="10" Padding="12">
    <TextBox x:Name="textBox1" RelativePanel.AlignLeftWithPanel="True"/>
    <Button Content="Submit" RelativePanel.Below="textBox1"/>
</RelativePanel>

使用 SizeChanged 事件响应布局更改Use SizeChanged events to respond to layout changes

FrameworkElement 类公开两个相似的事件,用于响应布局更改:LayoutUpdatedSizeChangedThe FrameworkElement class exposes two similar events for responding to layout changes: LayoutUpdated and SizeChanged. 当在布局期间调整元素大小时,你可能正在使用其中一个事件接收通知。You might be using one of these events to receive notification when an element is resized during layout. 两个事件的语义是不同的,因为在两者之间进行选择时有重要的性能注意事项。The semantics of the two events are different, and there are important performance considerations in choosing between them.

若要获取良好性能,选择 SizeChanged 在大多数情况下都没有错。For good performance, SizeChanged is almost always the right choice. SizeChanged 具有直观的语义。SizeChanged has intuitive semantics. FrameworkElement 的大小已更新时,将在布局期间引发它。It is raised during layout when the size of the FrameworkElement has been updated.

LayoutUpdated 还会在布局期间引发,但它具有全局语义,无论何时更新元素,都会将其引发。LayoutUpdated is also raised during layout, but it has global semantics—it is raised on every element whenever any element is updated. 通常只在事件处理程序中进行本地处理,在此情况下将以多于必需的频率运行代码。It is typical to only do local processing in the event handler, in which case the code is run more often than needed. 仅当你需要知道元素何时在不更改大小的情况下重新定位(此情况不常见)时使用 LayoutUpdatedUse LayoutUpdated only if you need to know when an element is repositioned without changing size (which is uncommon).

在面板之间选择Choosing between panels

在个别面板之间进行选择时,性能通常不是一个注意事项。Performance is typically not a consideration when choosing between individual panels. 进行该选择时通常考虑哪个面板提供最接近与你要实现的 UI 的布局行为。That choice is typically made by considering which panel provides the layout behavior that is closest to the UI you’re implementing. 例如,如果你正在 GridStackPanelRelativePanel 之间进行选择,你应该选择提供最接近于你对该实现的心理模型的面板。For example, if you’re choosing between Grid, StackPanel , and RelativePanel, you should choose the panel that provides the closest mapping to your mental model of the implementation.

优化每个 XAML 面板以实现良好性能,并且所有面板都为相似的 UI 提供相似的性能。Every XAML panel is optimized for good performance, and all the panels provide similar performance for similar UI.