レイアウト システム

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

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

  • 要素の境界ボックス

  • レイアウト システム

  • 子のサイズ測定と配置

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

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

  • サブピクセル レンダリングとレイアウトの丸め

  • 次の内容

要素の境界ボックス

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

次の図は、単純な配置を表しています。

一般的なグリッド、重ね合わせる境界ボックスなし。

このレイアウトは、次の 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 要素の境界ボックスを示しています。

TextBlock の境界ボックスが表示されます。

黄色い四角形で示すように、TextBlock 要素には、実際には外観よりかなり大きいスペースが割り当てられています。 Grid に要素が追加されると、この割り当ては、追加された要素の型やサイズに応じて縮小または拡張されます。

TextBlock のレイアウト スロットは、GetLayoutSlot メソッドを使用して、Path に変換されます。 この方法は、要素の境界ボックスを表示する場合に便利です。

Private 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
private 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();
}

レイアウト システム

簡単に言うと、レイアウトは、要素のサイズ測定、配置、および描画を繰り返す再帰的なシステムです。 具体的には、レイアウトとは、Panel 要素の Children コレクションのメンバーを測定し位置を決定するプロセスを表します。 レイアウトは、負荷の高いプロセスです。 Children コレクションが大きくなるにつれ、実行が必要な計算の数も多くなります。 コレクションを所有する Panel 要素で定義されるレイアウト動作のために、複雑になる場合もあります。 Canvas などの比較的単純な Panel では、Grid などのより複雑な Panel に比べて、大幅に優れたパフォーマンスを実現できます。

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

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

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

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

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

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

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

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

子のサイズ測定と配置

レイアウト システムは、Children コレクションのメンバーごとに、測定パスと配置パスという 2 つのパスを実行します。 各子 Panel では、それぞれ固有のレイアウト動作を実現するために、独自の MeasureOverride メソッドと ArrangeOverride メソッドが用意されています。

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

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

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

メモメモ

Height および WidthActualHeight および ActualWidth の各プロパティには違いがあります。たとえば、ActualHeight プロパティは、他の高さの入力やレイアウト システムを基に計算された値です。この値は、実際の描画パスに基づいて、レイアウト システム自体によって設定されるため、入力変更の基準となる Height などのプロパティの設定値よりも少し遅れることがあります。

ActualHeight は計算値であるため、レイアウト システムによるさまざまな操作の結果として、複数または追加の変更がレポートされる可能性があることに注意してください。子要素、親要素による制約などに必要な測度空間をレイアウト システムが計算している場合があります。

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

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

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

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

WPF には、Panel から派生した要素のグループが含まれています。 これらの Panel 要素により、多くの複雑なレイアウトができます。 たとえば、要素のスタックは StackPanel 要素を使用して簡単に実現できますが、Canvas を使用すると、より複雑で流動性の高いレイアウトを実現できます。

次の表に、使用可能なレイアウトの Panel 要素をまとめています。

パネル名

説明

Canvas

Canvas 領域に対する座標により子要素を明示的に配置できる領域を定義します。

DockPanel

子要素を互いに水平方向または垂直方向に整列する区分を定義します。

Grid

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

StackPanel

単一行に子要素を整列します。行は水平方向または垂直方向にできます。

VirtualizingPanel

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

WrapPanel

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

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

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

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

  • プロパティ値をどのように変更するとレイアウト システムの再帰的な更新が実行されるかに注意します。

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

  • 可能な場合は、LayoutTransform の代わりに RenderTransform を使用します。

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

  • UpdateLayout の不要な呼び出しは避けてください。

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

  • 大きい Children コレクションで作業する場合は、通常の StackPanel の代わりに、VirtualizingStackPanel の使用をお勧めします。

    子コレクションを仮想化することで、VirtualizingStackPanel は、現在、親の ViewPort 内に存在するオブジェクトのみをメモリ内に維持します。 その結果、パフォーマンスは多くのシナリオで大幅に向上します。

サブピクセル レンダリングとレイアウトの丸め

WPF のグラフィックス システムでは、解像度およびデバイスに依存しないようにするために、デバイスに依存しない単位が使用されます。 デバイスに依存しない各ピクセルは、システムの dots per inch (dpi) 設定に従って自動的にスケーリングされます。 これにより、さまざまな dpi 設定で WPF アプリケーションが適切にスケーリングされ、アプリケーションが自動的に dpi に対応するようになります。

ただし、この dpi に依存しない方法では、アンチエイリアシングによる端の描画にむらが生じる可能性があります。 このような不鮮明な表示 (半透明のエッジ) は、端がデバイス ピクセル間ではなく、1 つのデバイス ピクセル内に位置するときに発生する場合があります。 レイアウト システムでは、このような場合の調整方法として、レイアウトの丸めが用意されています。 レイアウトの丸めでは、レイアウト パスの実行中に整数以外のピクセル値が見つかった場合、レイアウト システムによって丸め処理が行われます。

レイアウトの丸めは既定では無効になっています。 レイアウトの丸めを有効にするには、UseLayoutRounding プロパティを FrameworkElement のいずれかで true に設定します。 これは依存関係プロパティであるため、値はビジュアル ツリー内のすべての子に反映されます。 UI 全体でレイアウトの丸めを有効にするには、ルート コンテナーで UseLayoutRounding を true に設定します。 例については、「UseLayoutRounding」を参照してください。

次の内容

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

参照

参照

FrameworkElement

UIElement

概念

パネルの概要

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

パフォーマンスの最適化 : レイアウトとデザイン