レイアウト システム

ここでは、Windows Presentation Foundation (WPF) のレイアウト システムについて説明します。見た目の良い、高いパフォーマンスのユーザー インターフェイスを作成するには、レイアウトの計算が行われる方法とタイミングを理解することが重要です。

このトピックは、次のセクションで構成されています。

  • レイアウト システム
  • 要素の境界ボックス
  • 子のサイズ測定と配置
  • パネル要素とカスタム レイアウトの動作
  • レイアウト パフォーマンスに関する留意事項
  • 次の内容
  • 関連トピック

レイアウト システム

"レイアウト" という用語は、Panel 要素の Children コレクションのメンバを測定し位置を決定して、それらを画面上に描画するプロセスを表します。これは負荷の高いプロセスで、Children コレクションが大きくなるにつれ、実行される計算の数も多くなります。コレクションを所有する Panel 要素で定義されるレイアウト動作のために、複雑になる場合もあります。Canvas などの比較的単純なレイアウトが使用され、Grid などのより複雑な Panel が不要な場合は、優れたパフォーマンスを実現できます。

UIElement の位置が変更されるたびに、レイアウト システムによって新しいパスがトリガされる可能性があります。そのため、不要な呼び出しによってアプリケーションのパフォーマンスが低下する可能性があるので、レイアウト システムを呼び出す可能性があるイベントを理解しておくことが重要です。

簡単に言うと、レイアウトは、要素のサイズ測定、配置、および画面上への描画を繰り返す再帰的なシステムです。レイアウト システムは、Children コレクションのメンバごとに、測定パスと配置パスという 2 つのパスを実行します。各子 Panel は、それぞれ固有のレイアウト動作を実現するために、独自の MeasureOverride メソッドと ArrangeOverride メソッドを提供します。これは、レイアウト システムが呼び出されるたびに発生する一連のイベントです。

  1. UIElement は、最初にそのコア プロパティを測定して、レイアウト プロセスを開始します。

  2. WidthHeightMargin など、FrameworkElement で定義されるサイズ設定プロパティが評価されます。

  3. Dock 方向またはスタック Orientation など、Panel 固有のロジックが適用されます。

  4. すべての子が測定された後、コンテンツが配置されます。

  5. Children コレクションが画面に描画されます。

  6. Children がコレクションに追加された場合、LayoutTransform が適用された場合、または UpdateLayout メソッドが呼び出された場合は、プロセスが再度呼び出されます。

このプロセス、およびプロセスが呼び出される方法は、以降のセクションで詳しく定義します。

要素の境界ボックス

Windows Presentation Foundation (WPF) でのアプリケーション レイアウトについて考えるときは、すべての要素を囲む境界ボックスを理解することが重要です。このアブストラクションは、レイアウト システムの動作を理解する上で役に立ちます。レイアウト システムで処理される各 FrameworkElement は、レイアウト パーティションに組み込まれる四角形として考えることができます。要素のレイアウト割り当て (スロット) の幾何学的な境界を返すことができる LayoutInformation クラスが公開されています。四角形のサイズは、システムにより、使用可能な画面スペース、制約のサイズ、余白やパディングなどのレイアウト固有のプロパティ、および親 Panel 要素の個々の動作を計算して決定されます。このデータを処理して、システムは特定の Panel のすべての子の位置を計算できます。親要素で定義されたサイズ設定特性 (Border など) は、その子に影響するという点に注意することが重要です。

例として、次の単純なレイアウト シナリオを取り上げます。

A typical Grid, no bounding box superimposed.

このレイアウトは、次の Extensible Application Markup Language (XAML) を使用して実現できます。

<Grid Name="myGrid" Background="LightSteelBlue" Height="150">
  <Grid.ColumnDefinitions>
    <ColumnDefinition Width="250"/>
  </Grid.ColumnDefinitions>
  <Grid.RowDefinitions>
    <RowDefinition />
    <RowDefinition />
    <RowDefinition />
  </Grid.RowDefinitions>
  <TextBlock Name="txt1" Margin="5" FontSize="16" FontFamily="Verdana" Grid.Column="0" Grid.Row="0">Hello World!</TextBlock>
  <Button Click="getLayoutSlot1" Width="125" Height="25" Grid.Column="0" Grid.Row="1">Show Bounding Box</Button>
  <TextBlock Name="txt2" Grid.Column="1" Grid.Row="2"/>
</Grid>

ただ 1 つの TextBlock 要素が Grid 内にホストされています。テキストは配置先の列の左上隅だけを占めていますが、実際にはそれよりもかなり大きいスペースが TextBlock に割り当てられています。GetLayoutSlot メソッドを使用して、任意の FrameworkElement の境界ボックスを取得できます。このメソッドを使用すると、TextBlock 要素の境界ボックスは多層構造になります (これは、レイアウトの座標を共有できる Panel 要素に TextBlockGrid ホストされることによって可能になります)。

The bounding box of the TextBlock is now visible.

パーティションを囲む白い線を見るとわかるように、TextBlock 要素に割り当てられているパーティションは、その要素が使用するスペースよりもかなり大きくなっています。Grid に要素が追加されると、この割り当ては、追加された要素の型やサイズに応じて縮小または拡張されます。

GetLayoutSlot メソッドを使用して、TextBlock のレイアウト スロットが返され、Path に変換されます。これは、要素の境界ボックスの表示に役立つ技法です。

public void getLayoutSlot1(object sender, System.Windows.RoutedEventArgs e)
{
    RectangleGeometry myRectangleGeometry = new RectangleGeometry();
    myRectangleGeometry.Rect = LayoutInformation.GetLayoutSlot(txt1);
    GeometryDrawing myGeometryDrawing = new GeometryDrawing();
    Path myPath = new Path();
    myPath.Data = myRectangleGeometry;
    myPath.Stroke = Brushes.LightGoldenrodYellow;
    myPath.StrokeThickness = 5;
    Grid.SetColumn(myPath, 0);
    Grid.SetRow(myPath, 0);
    myGrid.Children.Add(myPath);
    txt2.Text = "LayoutSlot is equal to " + LayoutInformation.GetLayoutSlot(txt1).ToString();
}
Public Sub getLayoutSlot1(ByVal sender As Object, ByVal e As RoutedEventArgs)
    Dim myRectangleGeometry As New RectangleGeometry
    myRectangleGeometry.Rect = LayoutInformation.GetLayoutSlot(txt1)
    Dim myGeometryDrawing As New GeometryDrawing
    Dim myPath As New Path
    myPath.Data = myRectangleGeometry
    myPath.Stroke = Brushes.LightGoldenrodYellow
    myPath.StrokeThickness = 5
    Grid.SetColumn(myPath, 0)
    Grid.SetRow(myPath, 0)
    myGrid.Children.Add(myPath)
    txt2.Text = "LayoutSlot is equal to " + LayoutInformation.GetLayoutSlot(txt1).ToString()
End Sub

子のサイズ測定と配置

Window オブジェクトのコンテンツが表示される際は、レイアウト システムが自動的に呼び出されます。コンテンツを表示するために、ウィンドウの Content は、画面上で Children を整理するためのフレームワークの定義に使用されるルート Panel を定義する必要があります。使用可能な Panel 要素の一覧およびカスタムのレイアウト要素の作成については、「パネル要素とカスタム レイアウトの動作」を参照してください。

レイアウトの最初のパスは測定パスです。ここで、Children コレクションの各メンバが評価されます。このプロセスは、Measure メソッドの呼び出しで開始されます。このメソッドは、親 Panel 要素の実装内で呼び出され、レイアウトを実行するために明示的に呼び出す必要はありません。

最初に、ClipVisibility などの UIElement のネイティブ サイズ プロパティが評価され、処理されます。これにより、MeasureCore に渡される constraintSize という値が生成されます。

次に、FrameworkElement で定義されたフレームワーク プロパティが処理され、constraintSize の値に反映されます。これらのプロパティは、多くの場合、HeightWidthMarginStyle など、基になる UIElement のサイズ特性を表しています。これらの各プロパティにより、要素の表示に必要なスペースが変更されることがあります。その場合は、constraintSize をパラメータとして MeasureOverride が呼び出されます。

測定パスの最終的な目標は、子の DesiredSize が決定されることです。これは、MeasureCore の呼び出し時に行われます。この値は、Measure に格納され、コンテンツの配置プロセスで使用されます。

配置プロセスは、Arrange メソッドの呼び出しで開始されます。配置パスでは、親 Panel 要素から、子の境界を表す四角形が生成されます。この値は、ArrangeCore メソッドに渡されて処理されます。

ArrangeCore メソッドは、子の DesiredSize を評価し、それ以外に、その要素の表示サイズに影響を与える余白があるかどうかを評価して、Panel の ArrangeOverride に渡されるarrangeSize を生成します。ArrangeOverride は、子の finalSize を生成し、最後に ArrangeCore メソッドが、余白や配置などのオフセット プロパティの最終的な評価を実行して、そのレイアウト スロット内に子を配置します。子は、割り当てられた領域全体を占める必要はなく、実際に、全体を占めないことも珍しくありません。次に、制御が親 Panel に戻されて、レイアウト プロセスが完了します。

パネル要素とカスタム レイアウトの動作

Windows Presentation Foundation (WPF) には、多くの複雑なレイアウトを有効にする Panel 要素の派生スイートが含まれています。要素のスタックなどの一般的なシナリオは StackPanel 要素を使用して簡単に実現できますが、Canvas を使用すると、より複雑で流動性の高いレイアウトを実現できます。

使用可能なレイアウト要素を次の表に示します。

パネル名 説明

Canvas

領域を定義します。この領域内では、Canvas 領域を基準にした座標に基づいて子要素を明示的に配置できます。

DockPanel

領域を定義します。この領域内では、子要素を互いに水平方向または垂直方向に整列できます。

Grid

列と行で構成されている柔軟なグリッド領域を定義します。

StackPanel

子要素を単一の行に整列します。行は、水平方向または垂直方向に並べることができます。

VirtualizingPanel

子データ コレクションを "仮想化" する Panel 要素のフレームワークを提供します。これは抽象クラスです。

WrapPanel

左から右へ順に子要素を配置し、ボックスの端で改行してコンテンツを次の行に送ります。その後は、Orientation プロパティの値に応じて、上から下、または右から左に向かって子要素が整列されます。

これらの各要素の使用法を示すコードのサンプルについては、「レイアウトのサンプル」を参照してください。

定義された Panel 要素のいずれを使用しても実現できないアプリケーション レイアウトが必要とされる場合は、Panel からの継承と、MeasureOverride メソッドおよび ArrangeOverride メソッドのオーバーライドにより、カスタムのレイアウト動作を実現できます。例については、「カスタム放射状パネルのサンプル」を参照してください。

レイアウト パフォーマンスに関する留意事項

レイアウトは、再帰プロセスです。Children コレクション内の各子要素は、システムの各呼び出しによって処理されます。そのため、システムのトリガは、できるだけ避ける必要があります。次に示すヒントは、より高いパフォーマンスを実現する上で役に立ちます。

依存関係プロパティの値によってレイアウト システムが初期化される可能性がある場合、そのプロパティはパブリック フラグでマークされます。AffectsMeasure および AffectsArrange は、どのプロパティの値の変更によってレイアウト システムによる再帰的な更新が実行されるかを判断する手がかりになります。一般的に、要素の境界ボックスのサイズに影響を与える可能性があるプロパティは、AffectsMeasure フラグを true に設定する必要があります。詳細については、「依存関係プロパティの概要」を参照してください。

LayoutTransform は、ユーザー インターフェイス (UI) のコンテンツに影響を与えるための非常に便利な手段です。ただし、変換の結果を他の要素の配置に影響させる必要がない場合は、RenderTransform を使用することをお勧めします。RenderTransform はレイアウト システムを呼び出しません。LayoutTransform はその変換を適用し、再帰的なレイアウトの更新を実行して、影響を受ける要素の新しい配置に反映させます。

UpdateLayout の不要な呼び出しは避けてください。このメソッドは、再帰的なレイアウトの更新を実行し、多くの場合は必要ありません。全面的な更新が必要でない限り、このメソッドの呼び出しはレイアウト システムに任せてください。

大きい Children コレクションを処理する場合は、通常の StackPanel ではなく、VirtualizingStackPanel の使用をお勧めします。子コレクションを "仮想化" することで、VirtualizingStackPanel は、現在、親のビューポート内に存在するオブジェクトのみをメモリ内に維持します。その結果、パフォーマンスは多くのシナリオで大幅に向上します。

次の内容

要素の測定および配置方法を理解することが、システムとしてのレイアウトを理解する第一歩です。使用可能な Panel 要素の詳細については、「パネルの概要」を参照してください。レイアウトに影響する可能性がある各配置プロパティをさらに詳しく理解するには、「配置、余白、パディングの概要」を参照してください。カスタムの Panel 要素の例については、「カスタム放射状パネルのサンプル」を参照してください。軽量アプリケーションにすべての要素を取り入れる準備ができている場合は、「Windows Presentation Foundation の概要」を参照してください。

参照

関連項目

FrameworkElement
UIElement

概念

パネルの概要
配置、余白、パディングの概要

その他の技術情報

XAMLPad