次の方法で共有


BoxPanel(カスタム パネルの例)

カスタム Panel クラスのコードを記述し、ArrangeOverride メソッドと MeasureOverride メソッドを実装し、Children プロパティを使用する方法について説明します。

重要な API: パネルArrangeOverrideMeasureOverride

このコード例はカスタム パネルの実装を示していますが、さまざまなレイアウト シナリオに合わせてパネルをカスタマイズする方法に影響するレイアウトの概念について説明することに多くの時間を費やしていません。 これらのレイアウトの概念と、それらが特定のレイアウト シナリオにどのように適用されるかについて詳しくは、「 XAML カスタム パネルの概要」をご覧ください。

パネルは、XAML レイアウト システムが実行され、アプリ UI がレンダリングされるときに、そのパネルに含まれる子要素のレイアウト動作を提供するオブジェクトです。 PANEL クラスからカスタム クラスを派生させることで、XAML レイアウトのカスタム パネル を定義できます。 パネルの動作を提供するには、ArrangeOverride メソッドと MeasureOverride メソッドをオーバーライドし、子要素を測定して配置するためのロジックを実装します。 この例は Panel から派生しています。 Panelから開始すると、ArrangeOverride メソッドと MeasureOverride メソッドには開始時の動作がありません。 コードは、子要素が XAML レイアウト システムに認識され、UI にレンダリングされるゲートウェイを提供しています。 そのため、コードがすべての子要素を考慮し、レイアウト システムが期待するパターンに従うのが本当に重要です。

あなたのレイアウトシナリオ

カスタム パネルを定義するときは、レイアウト シナリオを定義します。

レイアウト シナリオは、次の方法で表されます。

  • パネルに子要素がある場合の処理
  • パネルに独自のスペースに制約がある場合
  • パネルのロジックによって、最終的に子の UI レイアウトがレンダリングされるすべての測定、配置、位置、およびサイズを決定する方法

そのことを念頭に置いて、次に示す BoxPanel は特定のシナリオを対象にしています。 この例で最も重要なコードを保持するために、シナリオについてはまだ詳しく説明しません。代わりに、必要な手順とコーディング パターンに集中します。 最初にシナリオの詳細を知りたい場合 は、「 BoxPanelのシナリオ」に進んでから、コードに戻ります。

最初に、Panel から派生を始めます

最初に Panel からカスタム クラスを派生 します。 これを行う最も簡単な方法は、Microsoft Visual Studio のソリューション エクスプローラーからプロジェクトの | | Class コンテキスト メニュー オプションを使用して、このクラス用に別のコード ファイルを定義することです。 クラス (およびファイル) BoxPanelに名前を付けます。

クラスのテンプレート ファイルは、Windows アプリ専用ではないため、 を使用した多くの ステートメントで開始することはありません。 まず、using ステートメントを追加します。 テンプレートファイルは、いくつかの不要と思われる ステートメントを使用した で始まり、それらを削除することも可能です。 一般的なカスタム パネル コードに必要な型を解決できる using ステートメントの一覧を次に示します。

using System;
using System.Collections.Generic; // if you need to cast IEnumerable for iteration, or define your own collection properties
using Windows.Foundation; // Point, Size, and Rect
using Windows.UI.Xaml; // DependencyObject, UIElement, and FrameworkElement
using Windows.UI.Xaml.Controls; // Panel
using Windows.UI.Xaml.Media; // if you need Brushes or other utilities

これで Panel を解決できるようになったので、それを BoxPanel の基底クラスにします。 また、 BoxPanel パブリックにします。

public class BoxPanel : Panel
{
}

クラスレベルで、いくつかの intダブル の値を定義します。これらの値は、いくつかのロジック関数で共有されますが、パブリック API として公開する必要はありません。 この例では、 maxrcrowcountcolcountcellwidthcellheightmaxcellheightaspectratioという名前です。

これを完了すると、完全なコード ファイルは次のようになります ( 使用に関するコメントを削除すると、その理由がわかります)。

using System;
using System.Collections.Generic;
using Windows.Foundation;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media;

public class BoxPanel : Panel 
{
    int maxrc, rowcount, colcount;
    double cellwidth, cellheight, maxcellheight, aspectratio;
}

ここからは、メソッドのオーバーライドや依存関係プロパティなどのサポートなど、一度に 1 つのメンバー定義を示します。 これらは、上記のスケルトンに任意の順序で追加できます。

MeasureOverride

protected override Size MeasureOverride(Size availableSize)
{
    // Determine the square that can contain this number of items.
    maxrc = (int)Math.Ceiling(Math.Sqrt(Children.Count));
    // Get an aspect ratio from availableSize, decides whether to trim row or column.
    aspectratio = availableSize.Width / availableSize.Height;

    // Now trim this square down to a rect, many times an entire row or column can be omitted.
    if (aspectratio > 1)
    {
        rowcount = maxrc;
        colcount = (maxrc > 2 && Children.Count <= maxrc * (maxrc - 1)) ? maxrc - 1 : maxrc;
    } 
    else 
    {
        rowcount = (maxrc > 2 && Children.Count <= maxrc * (maxrc - 1)) ? maxrc - 1 : maxrc;
        colcount = maxrc;
    }

    // Now that we have a column count, divide available horizontal, that's our cell width.
    cellwidth = (int)Math.Floor(availableSize.Width / colcount);
    // Next get a cell height, same logic of dividing available vertical by rowcount.
    cellheight = Double.IsInfinity(availableSize.Height) ? Double.PositiveInfinity : availableSize.Height / rowcount;
           
    foreach (UIElement child in Children)
    {
        child.Measure(new Size(cellwidth, cellheight));
        maxcellheight = (child.DesiredSize.Height > maxcellheight) ? child.DesiredSize.Height : maxcellheight;
    }
    return LimitUnboundedSize(availableSize);
}

MeasureOverride 実装に必要なパターンは、Panel.Children内の各要素をループすることです。 これらの各要素で常に Measure メソッドを呼び出します。 Measure には、Size型 パラメーターがあります。 ここで渡しているのは、パネルがその特定の子要素で使用できるようにコミットしているサイズです。 そのため、ループを実行して Measure の呼び出しを開始する前に、各セルが割り当てることができる領域の量を把握しておく必要があります。 MeasureOverride メソッド自体から、availableSize の値を取得できます。 パネルの親が Measureを呼び出した際に使用したサイズは、そもそもこの MeasureOverride を最初に呼び出すきっかけとなったものです。 したがって、一般的なロジックは、各子要素がパネルの全体的な availableSize の領域を分割するスキームを考案することです。 次に、サイズの各区分を各子要素の Measure に提供します。

サイズ BoxPanel がサイズを分割する方法は、非常に簡潔に説明できます。それは、項目の数によって主に制御されるスペースを多数のボックスに分けることです。 ボックスのサイズは、行と列の数と使用可能なサイズに基づいて調整されます。 正方形の一部の行や列が必要ない場合、それを省略することがあります。その結果、パネルは行と列の比率が正方形ではなく長方形になります。 このロジックがどのように到着したかの詳細については、「 BoxPanel のシナリオ」に進んでください。

では、その法案は何をするのでしょうか? Measure が呼び出された各要素の読み取り専用 DesiredSize プロパティ 値を設定します。 配置パスに到達したら、DesiredSize の値が重要です。なぜなら、DesiredSize は配置と最終レンダリング時のサイズを伝えるからです。 独自のロジックで DesiredSize を使用しない場合でも、システムには引き続き必要です。

availableSize の高さコンポーネント バインドされていない場合は、このパネルを使用できます。 それが本当であれば、パネルには分割するための既知の高さがありません。 この場合、測定パスの論理は、制約のある高さがまだないことを各子に知らせます。 これを行うには、子要素の Size.Height が無限の場合に、SizeMeasure 呼び出しに渡します。 これは有効です。 Measure が呼び出されると、DesiredSize は、Measureに渡された値または明示的に設定された HeightWidthなどの要素の自然なサイズと比較して最小のもので設定されます。

StackPanel の内部ロジックにも、この動作があります。StackPanel は、子要素に対して Measure を に渡し、無限の寸法値を伝えており、方向の寸法において子要素に制約がないことを示します。 StackPanel は、通常、そのディメンションで拡張されるスタック内のすべての子を収容するために、動的にサイズを設定します。

ただし、パネル自体は、MeasureOverrideから無限の値を持つ Size を返すことはできません。それはレイアウト中に例外をスローします。 そのため、ロジックの一部は、子が要求する最大の高さを調べるためであり、パネルの独自のサイズ制約から取得されていない場合は、その高さをセルの高さとして使用します。 前のコードで参照されたヘルパー関数 LimitUnboundedSize を次に示します。このヘルパー関数は、その最大セルの高さを受け取り、それを使用してパネルに返される有限の高さを与え、配置パスが開始される前に cellheight が有限の数値であることを保証します。

// This method limits the panel height when no limit is imposed by the panel's parent.
// That can happen to height if the panel is close to the root of main app window.
// In this case, base the height of a cell on the max height from desired size
// and base the height of the panel on that number times the #rows.
Size LimitUnboundedSize(Size input)
{
    if (Double.IsInfinity(input.Height))
    {
        input.Height = maxcellheight * colcount;
        cellheight = maxcellheight;
    }
    return input;
}

ArrangeOverride メソッド

protected override Size ArrangeOverride(Size finalSize)
{
     int count = 1;
     double x, y;
     foreach (UIElement child in Children)
     {
          x = (count - 1) % colcount * cellwidth;
          y = ((int)(count - 1) / colcount) * cellheight;
          Point anchorPoint = new Point(x, y);
          child.Arrange(new Rect(anchorPoint, child.DesiredSize));
          count++;
     }
     return finalSize;
}

ArrangeOverride 実装に必要なパターンは、Panel.Children の各要素をループ処理することです。 これらの各要素で必ず Arrange メソッドを呼び出します。

MeasureOverride ほど多くの計算がない点に注意してください。これは一般的です。 子のサイズは、パネル独自の MeasureOverride ロジックから、またはメジャー パス中に設定された各子セットの DesiredSize 値から既にわかっています。 ただし、各子が表示されるパネル内の場所を決定する必要があります。 一般的なパネルでは、各子は異なる位置にレンダリングする必要があります。 重複する要素を作成するパネルは、一般的なシナリオでは望ましくありません (ただし、実際に意図したシナリオである場合は、意図的な重複を持つパネルを作成することは問題ではありません)。

このパネルは、行と列の概念によって配置されます。 行と列の数は既に計算されています (測定に必要でした)。 したがって、行と列の形状と各セルの既知のサイズは、このパネルに含まれる各要素のレンダリング位置 ( anchorPoint) を定義するロジックに寄与します。 その ポイントは、計測により知られている サイズ と共に、レクトを作成する2つの要素として使用されます。 Rect、Arrange の入力の種類です。

パネルのコンテンツをクリップする必要がある場合があります。 その場合、クリップされたサイズは、DesiredSizeに存在するサイズです。これは、Measure ロジックが、Measureに渡された値の中で最小のもの、またはその他の自然なサイズ要因を基準として設定するためです。 したがって、通常は、配置中にクリッピングを特にチェックする必要。クリッピングは、DesiredSize を各 Arrange 呼び出しに渡すことに基づいて発生します。

レンダリング位置を定義するために必要なすべての情報が他の方法で認識されている場合は、ループを通過するときに必ずしもカウントが必要であるとは限りません。 たとえば、 Canvas レイアウト ロジックでは、 Children コレクション内の位置は関係ありません。 Canvas 内の各要素を配置するために必要なすべての情報は、配置ロジックの一部として、Canvas.LeftCanvas.Top の値を読み取ることで認識されます。 BoxPanelロジックでは、新しい行を開始して y 値をオフセットするタイミングがわかるように、colcount と比較するカウントが必要になります。

通常、finalSize 入力と、サイズArrangeOverride 実装から返される場合は同じです。 理由の詳細については、XAML カスタム パネルの概要の「ArrangeOverride」セクションを参照してください。

絞り込み: 行数と列数を制御する

このパネルは、今と同じようにコンパイルして使用できます。 ただし、もう 1 つの絞り込みを追加します。 先ほど示したコードでは、ロジックによって、縦横比が最も長い側に追加の行または列が配置されます。 ただし、セルの図形をより詳細に制御するには、パネルの独自の縦横比が "縦" であっても、3x4 ではなく 4 x 3 のセル セットを選択することが望ましい場合があります。そのため、パネル コンシューマーがその動作を制御するように設定できるオプションの依存関係プロパティを追加します。 非常に基本的な依存関係プロパティの定義を次に示します。

// Property
public Orientation Orientation
{
    get { return (Orientation)GetValue(OrientationProperty); }
    set { SetValue(OrientationProperty, value); }
}

// Dependency Property Registration
public static readonly DependencyProperty OrientationProperty =
        DependencyProperty.Register(nameof(Orientation), typeof(Orientation), typeof(BoxPanel), new PropertyMetadata(null, OnOrientationChanged));

// Changed callback so we invalidate our layout when the property changes.
private static void OnOrientationChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs args)
{
    if (dependencyObject is BoxPanel panel)
    {
        panel.InvalidateMeasure();
    }
}

以下で、Orientation を使用すると MeasureOverrideのメジャーロジックにどのように影響するかを示します。 実際に行っているのは、 rowcountcolcountmaxrc と真の縦横比から導き出される方法を変更することです。そのため、各セルに対応するサイズの違いがあります。 Orientation垂直 (既定値) の場合、縦長の四角形レイアウトの行数と列数に使用する前に、真の縦横比の値を反転します。

// Get an aspect ratio from availableSize, decides whether to trim row or column.
aspectratio = availableSize.Width / availableSize.Height;

// Transpose aspect ratio based on Orientation property.
if (Orientation == Orientation.Vertical) { aspectratio = 1 / aspectratio; }

BoxPanel のシナリオ

BoxPanelの特定のシナリオは、スペースを分割する方法の主な決定要因の 1 つは、子項目の数を把握し、パネルの既知の使用可能な領域を分割することです。 パネルは、本質的に四角形です。 多くのパネルは、その四角形のスペースをさらに四角形に分割することによって動作します。これは、 Grid がセルに対して行う処理です。 Gridの場合、セルのサイズは ColumnDefinitionRowDefinition の値によって設定され、要素は Grid.RowGrid.Column の添付プロパティを使って、どのセルに入るかを正確に宣言します。 グリッドから適切なレイアウトを取得するには、通常、十分なセルがあり、各子要素が独自のセルに収まるように添付プロパティを設定するために、事前に子要素の数を把握しておく必要があります。

しかし、子の数が動的な場合はどうでしょうか。 それは確かに可能です。アプリ コードは、UI を更新する価値があるほど重要であると考えられる動的な実行時条件に応じて、コレクションに項目を追加できます。 データ バインディングを使用してコレクション/ビジネス オブジェクトをバッキングする場合、このような更新の取得と UI の更新は自動的に処理されるため、多くの場合、推奨される手法です (詳細な データ バインディングを参照)。

ただし、すべてのアプリ シナリオがデータ バインディングに役立つわけではありません。 場合によっては、実行時に新しい UI 要素を作成して表示する必要があります。 BoxPanel は、このシナリオ用です。 子項目の数が変更されても、 BoxPanel は問題ありません。これは、計算に子項の数を使用し、既存の子要素と新しい子要素の両方をすべて合うように新しいレイアウトに調整するためです。

BoxPanelをさらに拡張するための高度なシナリオ (ここでは示されていません) では、動的な子に対応し、子の DesiredSize を個々のセルのサイズ設定のより強力な要素として使用できます。 このシナリオでは、さまざまな行または列のサイズ、またはグリッド以外の図形を使用して、"無駄な" 領域を減らすことができます。 これには、さまざまなサイズと縦横比の複数の四角形を、美学と最小サイズの両方を含む四角形に収まる方法の戦略が必要です。 BoxPanel それはしません。スペースを分割するためのより簡単な手法を使用しています。 BoxPanelの手法は、子カウントより大きい最小の平方数を決定する方法です。 たとえば、9 個の項目は 3 x 3 の正方形に収まります。 10 個のアイテムには 4 x 4 の正方形が必要です。 ただし、多くの場合、開始四角形の 1 つの行または列を削除しながら項目を収めてスペースを節約できます。 count=10 の例では、4x3 または 3x4 の四角形に収まります。

パネルが 10 個の項目に対して 5x2 を選択しないのはなぜか疑問に思うかもしれません。これは項目番号にきちんと合うためです。 ただし、実際には、パネルは特に強い向きの縦横比を持つことはほとんどない四角形として設計されています。 最小二乗法は、サイズ設定ロジックを典型的なレイアウトの形状に適合させ、セルの形状が不自然な縦横比にならないようにする方法です。

リファレンス

概念