Оптимизация макета XAML

Важные API

Макет — это процесс определения визуальной структуры пользовательского интерфейса. Основной механизм описания макета в XAML — это панели, которые являются объектами контейнеров, которые позволяют размещать и упорядочивать элементы пользовательского интерфейса в них. Макет может быть дорогой частью приложения XAML как в использовании ЦП, так и в нагрузке на память. Ниже приведены некоторые простые шаги, которые можно предпринять для повышения производительности макета приложения XAML.

Уменьшение структуры макета

Наибольшее повышение производительности макета происходит от упрощения иерархической структуры дерева элементов пользовательского интерфейса. Панели существуют в визуальном дереве, но они являются структурными элементами, а не пикселями, которые создают такие элементы, как кнопка или прямоугольник. Упрощение дерева путем уменьшения числа элементов, не являющихся пиксельными элементами, обычно обеспечивает значительное увеличение производительности.

Многие пользовательские интерфейсы реализуются вложенными панелями, что приводит к глубоким, сложным деревьям панелей и элементов. Это удобно для вложенных панелей, но во многих случаях один и тот же пользовательский интерфейс можно достичь с более сложной одной панелью. Использование одной панели обеспечивает лучшую производительность.

Когда уменьшить структуру макета

Сокращение структуры макета в тривиальном способе, например сокращение одной вложенной панели с страницы верхнего уровня, не имеет заметного эффекта.

Наибольшее повышение производительности достигается от уменьшения структуры макета, повторяющейся в пользовательском интерфейсе, например в ListView или GridView. Эти элементы ItemsControl используют DataTemplate, который определяет поддерев элементов пользовательского интерфейса, созданных много раз. При дублировании одного поддеревного дерева во многих случаях в приложении любые улучшения производительности этого поддерева влияют на общую производительность приложения.

Примеры

Рассмотрим следующий пользовательский интерфейс.

Form layout example

В этих примерах показаны 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>
  <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. Один ОтносительныйPanel:

Эта отдельная панель также немного сложнее, чем использование вложенных панелей, но может быть проще понимать и поддерживать, чем сетка.

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

Как показано в этих примерах, существует множество способов достижения одного пользовательского интерфейса. Необходимо тщательно рассмотреть все компромиссы, включая производительность, удобочитаемость и удобство обслуживания.

Использование сетки с одной ячейкой для перекрывающегося пользовательского интерфейса

Обычное требование пользовательского интерфейса заключается в наличии макета, в котором элементы пересекаются друг с другом. Обычно для размещения элементов таким образом используются поля, поля, выравнивания и преобразования. Элемент управления "Сетка XAML" оптимизирован для повышения производительности макета элементов, перекрывающихся.

Важно! Чтобы добиться видимого улучшения, используйте элемент Grid с одной ячейкой. Не определяйте RowDefinitions или ColumnDefinitions.

Примеры

<Grid>
    <Ellipse Fill="Red" Width="200" Height="200" />
    <TextBlock Text="Test" 
               HorizontalAlignment="Center" 
               VerticalAlignment="Center" />
</Grid>

Text overlaid on a circle

<Grid Width="200" BorderBrush="Black" BorderThickness="1">
    <TextBlock Text="Test1" HorizontalAlignment="Left" />
    <TextBlock Text="Test2" HorizontalAlignment="Right" />
</Grid>

Two text blocks in a grid

Использование встроенных свойств границы панели

Элементы управления Grid, StackPanel, RelativePanel и ContentPresenter имеют свойства встроенной границы, позволяющие рисовать вокруг них границу без добавления дополнительного элемента Border. Новые свойства, поддерживающие встроенную границу: BorderBrush, BorderThickness, CornerRadius и Padding. Каждое из них — зависимостьProperty, поэтому их можно использовать с привязками и анимациями. Они предназначены для полной замены отдельного элемента Border .

Если в пользовательском интерфейсе есть элементы Border вокруг этих панелей, используйте встроенную границу, которая сохраняет дополнительный элемент в структуре макета приложения. Как упоминание ранее, это может быть значительной экономией, особенно в случае повторения пользовательского интерфейса.

Примеры

<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 только в том случае, если необходимо знать, когда элемент перемещается без изменения размера (что редко).

Выбор между панелями

Производительность обычно не учитывается при выборе между отдельными панелями. Этот выбор обычно делается с учетом того, какая панель предоставляет поведение макета, ближайшее к реализуемому пользовательскому интерфейсу. Например, если вы выбираете сетку, StackPanel и RelativePanel, следует выбрать панель, которая обеспечивает ближайшее сопоставление с вашей психической моделью реализации.

Каждая панель XAML оптимизирована для хорошей производительности, и все панели обеспечивают аналогичную производительность для аналогичного пользовательского интерфейса.