XAML レイアウトの最適化
重要な API
レイアウトは、UI の視覚的構造を定義するプロセスです。 XAML でレイアウトを記述するための主要なメカニズムではパネルを使用します。パネルは、UI 要素を内部に配置して整列できるコンテナー オブジェクトです。 レイアウトは、CPU 使用率とメモリ オーバーヘッドの両方で、XAML アプリの負荷の高い部分です。 ここでは、XAML アプリのレイアウトのパフォーマンスを向上させるための簡単な手順を示します。
レイアウトの構造を減らす
レイアウトのパフォーマンス向上の効果が最も大きいのは、UI 要素のツリーの階層構造を簡略化することです。 パネルはビジュアル ツリーに存在しますが、これらは構造型の要素であり、Button や Rectangle などのピクセル生成要素ではありません。 一般に非ピクセル生成要素の数を減らしてツリーを簡略化すると、パフォーマンスの大幅な向上につながります。
多くの UI は、パネルを入れ子にして実装されており、結果として、パネルと要素のツリーが深くて複雑な構造になっています。 パネルを入れ子にすると便利ですが、多くの場合、同じ UI をより複雑な 1 つのパネルを使って実現できます。 1 つのパネルを使用する方が、パフォーマンスが向上します。
レイアウトの構造を減らす場合
単純な方法でレイアウトの構造を減らす (たとえば、トップレベルのページから、入れ子になったパネルを 1 つ減らす) だけでは、大きな効果は得られません。
パフォーマンス向上の効果が最も大きいのは、ListView や GridView などの UI 内で繰り返されるレイアウト構造を減らすことです。 これらの ItemsControl 要素は DataTemplate を使用します。これは、何度もインスタンス化される UI 要素のサブツリーを定義します。 アプリ内で同じサブツリーが何度も複製されている場合、そのサブツリーのパフォーマンスが少しでも向上すれば、アプリの全体的なパフォーマンスへの効果は何倍にもなります。
例
次のような 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: 1 つの Grid
Grid は多少複雑になりますが、使用するパネル要素は 1 つのみです。
<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: 1 つの RelativePanel:
この 1 つのパネルも、入れ子になったパネルを使用する場合よりも少し複雑ですが、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 を実現するにはさまざまな方法があります。 パフォーマンス、読みやすさ、保守容易性を含む、すべてのトレードオフを慎重に考慮して選択する必要があります。
重なり合った UI として単一セルのグリッドを使う
一般的な UI 要件として、要素が互いに重なり合ったレイアウトにすることがあります。 通常、この方法で要素を配置する場合は、パディング、余白、整列、変換が使用されます。 XAML Grid コントロールは、重なり合った要素のレイアウトのパフォーマンスを向上させるように最適化されています。
重要 パフォーマンスの向上を確認するために、単一セルの 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 の各コントロールには組み込みの境界線プロパティがあり、XAML に Border 要素を追加せずに周囲に境界線を描画できます。 組み込みの境界線をサポートする新しいプロパティは、BorderBrush、BorderThickness、CornerRadius、Padding です。 これらはそれぞれ DependencyProperty であり、バインディングやアニメーションで使用できます。 これらは、個別の Border 要素を完全に置き換えるように設計されています。
UI でこれらのパネルの周囲に Border 要素を使っている場合は、代わりに組み込みの境界線を使うことによって、アプリのレイアウト構造内で余分な要素を削減できます。 既に説明したように、特に繰り返される 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 クラスは、レイアウト変更に応答するための2 つの類似したイベント (LayoutUpdated および SizeChanged) を公開します。 レイアウト時に、要素のサイズが変更された場合、これらのイベントのいずれかを使用して通知を受信していることがあります。 2 つのイベントのセマンティクスは異なり、どちらを選択するかは、パフォーマンスに関する重要な考慮事項です。
パフォーマンスを向上させるには、ほとんどの場合 SizeChanged が正しい選択です。 SizeChanged のセマンティクスは直感的です。 このイベントは、レイアウト中に FrameworkElement のサイズが更新されたときに発生します。
LayoutUpdated もレイアウト時に発生しますが、そのセマンティクスはグローバルです。任意の要素が更新されるたびにすべての要素について発生します。 イベント ハンドラーではローカルな処理のみを行うことが一般的であり、その場合、コードは必要以上に頻繁に実行されます。 LayoutUpdated は、サイズを変更せずに要素が再配置されたことを知る必要がある場合 (一般的ではありません) にのみ使用します。
パネルの選択
通常、パフォーマンスは、個々のパネルを選択する際の検討事項ではありません。 パネルの選択は、実装しようとする UI に最も近いレイアウトの動作を実現するパネルがどれかを検討して行われます。 たとえば、Grid、StackPanel、RelativePanel のいずれかを選択する場合、実装の概念的モデルに最も近いマッピングを提供するパネルを選択する必要があります。
どの XAML パネルもパフォーマンスが高くなるように最適化されており、同様の UI ではどのパネルのパフォーマンスにも差はありません。