Поделиться через


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

Основные API

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

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

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

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

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

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

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

Примеры

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

Пример макета формы

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

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

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

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

Важно Чтобы увидеть улучшение, используйте одноэлементную сетку. Не определяйте 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 имеют встроенные свойства границы, которые позволяют нарисовать границу вокруг них без добавления дополнительного элемента Border в XAML. Новые свойства, поддерживающие встроенную границу: BorderBrush, BorderThickness, CornerRadius и Padding. Каждое из этих свойств является DependencyProperty, поэтому их можно использовать с привязками и анимациями. Они предназначены для полной замены отдельного элемента 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 оптимизирована для хорошей производительности, и все панели обеспечивают аналогичную производительность для аналогичного пользовательского интерфейса.