次の方法で共有


レイアウト

このトピックでは、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 void getLayoutSlot1(object sender, System.Windows.RoutedEventArgs e)
{
    RectangleGeometry myRectangleGeometry = new RectangleGeometry();
    myRectangleGeometry.Rect = LayoutInformation.GetLayoutSlot(txt1);
    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();
}
Private Sub getLayoutSlot1(ByVal sender As Object, ByVal e As RoutedEventArgs)
    Dim myRectangleGeometry As New RectangleGeometry
    myRectangleGeometry.Rect = LayoutInformation.GetLayoutSlot(txt1)
    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

レイアウト システム

最も単純なレイアウトは、要素のサイズ変更、配置、描画につながる再帰システムです。 具体的には、レイアウトは、Panel 要素の Children コレクションのメンバーを測定および配置するプロセスを記述します。 レイアウトは集中的なプロセスです。 Children コレクションが大きいほど、実行する必要がある計算の数が多くなります。 複雑さは、コレクションを所有する Panel 要素によって定義されたレイアウト動作に基づいて導入することもできます。 Canvasなどの比較的単純な Panelは、Gridなどのより複雑な Panelよりもパフォーマンスが大幅に向上する可能性があります。

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

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

  2. FrameworkElement で定義されたサイズ変更プロパティは、WidthHeightMarginなど、評価されます。

  3. Panel固有のロジックが適用され、Dock の方向や Orientationの積み重ねなどが考慮されます。

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

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

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

このプロセスと呼び出し方法については、次のセクションで詳しく説明します。

子の測定と配置

レイアウト システムは、Children コレクションのメンバーごとに2つのパス (計測パスおよび配置工程) を完了します。 各子 Panel には、独自のレイアウト動作を実現するための独自の MeasureOverride メソッドと ArrangeOverride メソッドが用意されています。

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

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

第 2 に、FrameworkElement で定義されたフレームワーク プロパティが処理され、constraintSizeの値に影響します。 これらのプロパティは、一般に、HeightWidthMarginStyleなど、基になる UIElementのサイズ設定特性を表します。 これらの各プロパティは、要素を表示するために必要なスペースを変更できます。 MeasureOverride は、パラメーターとして constraintSize を使用して呼び出されます。

手記

HeightWidthActualHeightActualWidthのプロパティには違いがあります。 たとえば、ActualHeight プロパティは、他の高さの入力とレイアウト システムに基づいて計算される値です。 この値は、実際のレンダリング パスに基づいてレイアウト システム自体によって設定されるため、入力の変更の基礎となるプロパティの設定値 (Heightなど) よりもわずかに遅れる可能性があります。

ActualHeight は計算値であるため、レイアウト システムによるさまざまな操作の結果として、複数または増分の変更が報告される可能性があることに注意する必要があります。 レイアウト システムでは、子要素の必要なメジャー領域、親要素による制約などを計算できます。

メジャー パスの最終的な目標は、子が MeasureCore 呼び出し中に発生する DesiredSizeを決定することです。 DesiredSize 値は、コンテンツ配置パス中に使用するために Measure によって格納されます。

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

ArrangeCore メソッドは、子の DesiredSize を評価し、要素のレンダリングされたサイズに影響を与える可能性のある追加の余白を評価します。 ArrangeCore は、パラメーターとして PanelArrangeOverride メソッドに渡される arrangeSizeを生成します。 ArrangeOverride は、子の finalSize を生成します。 最後に、ArrangeCore メソッドは、余白や配置などのオフセット プロパティの最終的な評価を行い、そのレイアウト スロット内に子を配置します。 子は、割り当てられた領域全体を埋める必要はありません (また、多くの場合は埋める必要はありません)。 その後、コントロールが親 Panel に返され、レイアウト プロセスが完了します。

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

WPF には、Panelから派生する要素のグループが含まれています。 これらの Panel 要素により、多くの複雑なレイアウトが可能になります。 たとえば、StackPanel 要素を使用することで要素の積み重ねを簡単に実現できる一方で、Canvasを使用することで、より複雑で自由に流れるレイアウトを実現できます。

次の表は、使用可能なレイアウト Panel 要素をまとめたものです。

パネル名 説明
Canvas Canvas 領域を基準にした座標によって子要素を明示的に配置できるエリアを定義します。
DockPanel 子要素を互いに相対的に水平方向または垂直方向に配置できる領域を定義します。
Grid 列と行で構成される柔軟なグリッド領域を定義します。
StackPanel 子要素を、水平方向または垂直方向に配置できる単一の行に配置します。
VirtualizingPanel 子データコレクションを仮想化する Panel 要素のフレームワークを提供します。 これは抽象クラスです。
WrapPanel 子要素を左から右に順番に配置し、コンテンツを格納ボックスの端にある次の行に分割します。 後続の順序は、Orientation プロパティの値に応じて、上から下または右から左に順番に行われます。

定義済みの Panel 要素を使用してできないレイアウトを必要とするアプリケーションの場合、カスタム レイアウト動作は、Panel から継承し、MeasureOverride メソッドと ArrangeOverride メソッドをオーバーライドすることによって実現できます。

レイアウトのパフォーマンスに関する考慮事項

レイアウトは再帰的なプロセスです。 Children コレクション内の各子要素は、レイアウト システムの各呼び出し中に処理されます。 そのため、レイアウト システムをトリガーする必要がない場合は避ける必要があります。 次の考慮事項は、パフォーマンスの向上に役立ちます。

  • どのプロパティ値の変更によって、レイアウト システムによる再帰的な更新が強制されるのか注意してください。

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

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

    LayoutTransform は、ユーザー インターフェイス (UI) のコンテンツに影響を与える非常に便利な方法です。 ただし、変換の効果が他の要素の位置に影響を与える必要がない場合は、レイアウト システムを呼び出さないため、代わりに RenderTransformRenderTransform 使用することをお勧めします。 LayoutTransform 変換を適用し、影響を受ける要素の新しい位置を考慮するように再帰的なレイアウトの更新を強制します。

  • UpdateLayoutへの不要な呼び出しを避けます。

    UpdateLayout メソッドは、再帰的なレイアウトの更新を強制するため、多くの場合は必要ありません。 完全な更新が必要な場合を除き、レイアウト システムを使用してこのメソッドを呼び出してください。

  • 大規模な Children コレクションを使用する場合は、通常の StackPanelの代わりに VirtualizingStackPanel を使用することを検討してください。

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

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

WPF グラフィックス システムでは、デバイスに依存しない単位を使用して、解像度とデバイスの独立性を実現します。 各デバイスに依存しないピクセルは、システムのドット/インチ (dpi) 設定で自動的にスケーリングされます。 これにより、WPF アプリケーションはさまざまな dpi 設定に対して適切なスケーリングを行い、アプリケーションを自動的に dpi 対応にします。

ただし、この dpi に依存しないため、アンチエイリアシングのために不規則なエッジ レンダリングが発生する可能性があります。 これらのアーティファクトは、通常、ぼやけたエッジまたは半透明のエッジと見なされ、デバイス ピクセル間ではなく、デバイス ピクセルの中央にエッジの位置が収まるときに発生する可能性があります。 レイアウト システムは、レイアウトの丸めを使用してこれを調整する方法を提供します。 レイアウト丸めは、レイアウト システムがレイアウト パス中に整数以外のピクセル値を丸める場所です。

レイアウトの丸めは、既定では無効になっています。 レイアウトの丸めを有効にするには、UseLayoutRounding プロパティを任意の FrameworkElementtrue するように設定します。 依存関係プロパティであるため、値はビジュアル ツリー内のすべての子に伝達されます。 UI 全体のレイアウト丸めを有効にするには、ルート コンテナーで UseLayoutRoundingtrue に設定します。 例については、UseLayoutRoundingを参照してください。

次の予定

要素の測定と配置方法を理解することは、レイアウトを理解するための最初のステップです。 使用可能な Panel 要素の詳細については、「パネルの概要」を参照してください。 レイアウトに影響を与える可能性があるさまざまな配置プロパティについて理解を深めるために、「配置、余白、パディングの概要」を参照してください。 軽量アプリケーションですべてをまとめる準備ができたら、「チュートリアル: 初めての WPF デスクトップ アプリケーションの」を参照してください。

参照