BoxPanel(カスタム パネルの例)
カスタム Panel クラスのコードを記述し、 ArrangeOverride メソッドと MeasureOverride メソッドを実装し、 Children プロパティを使用する方法について説明します。
重要な API: Panel、 ArrangeOverride、MeasureOverride
このコード例はカスタム パネルの実装を示していますが、さまざまなレイアウト シナリオに合わせてパネルをカスタマイズする方法に影響するレイアウトの概念について説明することに多くの時間を費やしていません。 これらのレイアウトの概念とその特定のレイアウト シナリオへの適用方法の詳細については、「 XAML カスタム パネルの概要を参照してください。
panelは、XAML レイアウト システムが実行され、アプリ UI がレンダリングされるときに、含まれる子要素のレイアウト動作を提供するオブジェクトです。 Panel クラスからカスタム クラスを派生させることで、XAML レイアウトのカスタム パネルを定義できます。 パネルの動作を提供するには、 ArrangeOverride メソッドと MeasureOverride メソッドをオーバーライドし、子要素を測定して配置するロジックを提供します。 この例は、 Panel から派生しています。 Panel から開始する場合、ArrangeOverride メソッドと MeasureOverride メソッドには開始動作がありません。 コードは、子要素が XAML レイアウト システムに認識され、UI にレンダリングされるゲートウェイを提供しています。 そのため、コードがすべての子要素を考慮し、レイアウト システムが期待するパターンに従うのが本当に重要です。
レイアウト シナリオ
カスタム パネルを定義するときは、レイアウト シナリオを定義します。
レイアウト シナリオは、次の方法で表されます。
- パネルに子要素がある場合の処理
- パネルに独自のスペースに制約がある場合
- パネルのロジックによって、最終的に子の UI レイアウトがレンダリングされるすべての測定、配置、位置、およびサイズを決定する方法
そのことを念頭に置いて、次に示す BoxPanel
は特定のシナリオを対象にしています。 この例で最も重要なコードを保持するために、シナリオについてはまだ詳しく説明しません。代わりに、必要な手順とコーディング パターンに集中します。 最初にシナリオの詳細を知りたい場合は、「BoxPanel
のシナリオ」スキップしてから、コードに戻ります。
最初に Panel から派生する
まず、 Panel からカスタム クラスを派生します。 これを行う最も簡単な方法は、Microsoft Visual Studio のソリューション エクスプローラーからプロジェクトの Add | New Item | Class コンテキスト メニュー オプションを使用して、このクラス用に別のコード ファイルを定義することです。 クラス (およびファイル) BoxPanel
に名前を付けます。
クラスのテンプレート ファイルは、特に Windows アプリ用ではないため、多くの using ステートメントで始まることはありません。 最初に、 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 として公開する必要はありません。 この例では、 maxrc
、 rowcount
、 colcount
、 cellwidth
、 cellheight
、 maxcellheight
、 aspectratio
という名前です。
これを完了すると、コード ファイル全体が次のようになります ( に関するコメントが削除されました、その理由がわかります)。
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
分割する方法は非常に簡単です。スペースは、項目の数によって主に制御される多数のボックスに分割されます。 ボックスのサイズは、行と列の数と使用可能なサイズに基づいて調整されます。 四角形から 1 つの行または列が必要ない場合があるため、ドロップされ、パネルは行の列比の観点から四角形ではなく四角形になります。 このロジックの到着方法の詳細については、「BoxPanel のシナリオ」に進んでください。
では、メジャー パスは何を行いますか? Measure が呼び出された各要素の読み取り専用DesiredSize プロパティの値を設定します。 DesiredSize値を持つことは、配置パスに到達した後に重要になる可能性があります。これは、DesiredSizeが配置時と最終レンダリング時に可能または必要なサイズを伝えるためです。 独自のロジックで DesiredSize を使用しない場合でも、システムには引き続き必要です。
このパネルは、 availableSize の高さコンポーネントが無制限の場合に使用できます。 これが true の場合、パネルには分割する既知の高さがありません。 この場合、メジャー パスのロジックは、境界付きの高さがまだないことを各子に通知します。 そのためには、 Size を Measure 呼び出し Size.Height が無限である子を呼び出します。 これは有効です。 Measureが呼び出されると、DesiredSizeが最小値として設定されます。Measureに渡されたもの、または明示的に設定されたHeightやWidthなどの要素の自然なサイズ。
Note
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
) を定義するロジックに寄与します。 その Pointは、メジャーから既に知られている Size と共に、 Rectを構築する 2 つのコンポーネントとして使用されます。 Rect は、 Arrange の入力型です。
パネルのコンテンツをクリップする必要がある場合があります。 その場合、クリップされたサイズは DesiredSize に存在するサイズです。これは、 Measure ロジックによって、 Measure またはその他の自然なサイズ要因に渡された最小値として設定されるためです。 そのため、通常は、 Arrange 中にクリッピングを特にチェックする必要はありません。クリッピングは、 DesiredSize 各 Arrange 呼び出しに渡すことに基づいて行われます。
レンダリング位置を定義するために必要なすべての情報が他の方法で認識されている場合は、ループを通過するときに必ずしもカウントが必要であるとは限りません。 たとえば、 Canvas レイアウト ロジックでは、 Children コレクション内の位置は関係ありません。 各要素を Canvas に配置するために必要なすべての情報は Canvas.Left を読み取り、配置ロジックの一部として子の値を Canvas.Top します。 BoxPanel
ロジックでは、colcountと比較するためにカウントが必要になるため、新しい行を開始してy値をオフセットするタイミングが分かるようにします。
通常、入力 finalSize と Size ArrangeOverride から返す実装は同じです。 理由の詳細については、XAML カスタム パネルの概要の「ArrangeOverride」セクション 参照。
絞り込み: 行数と列数を制御する
このパネルは、今と同じようにコンパイルして使用できます。 ただし、もう 1 つの絞り込みを追加します。 先ほど示したコードでは、ロジックによって、縦横比が最も長い側に追加の行または列が配置されます。 ただし、セルの形状をさらに制御するには、パネル自体の縦横比が "縦長" であっても、3 x 4 ではなく、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
のメジャー ロジックに与える影響を示します。 実際に行っているのは、 rowcount
と colcount
が maxrc
と真の縦横比から導き出される方法を変更することです。そのため、各セルに対応するサイズの違いがあります。 Orientation
が Vertical (既定値) の場合、"縦" 四角形レイアウトの行数と列数に使用する前に、真の縦横比の値を反転します。
// 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の場合、セルのサイズはColumnDefinitionとRowDefinition値によって設定され、要素は、Grid.RowおよびGrid.Column添付プロパティで移動する正確なセルを宣言します。 Gridから適切なレイアウトを取得するには通常、十分なセルがあり、各子要素が独自のセルに収まるように添付プロパティを設定するために、事前に子要素の数を知る必要があります。
しかし、子の数が動的な場合はどうでしょうか。 それは確かに可能です。アプリ コードは、UI を更新する価値があるほど重要であると考えられる動的な実行時条件に応じて、コレクションに項目を追加できます。 データ バインディングを使用してコレクションやビジネス オブジェクトをバッキングする場合、このような更新の取得と UI の更新は自動的に処理されるため、多くの場合、推奨される手法です (詳細については、「 Data バインディングの詳細を参照してください)。
ただし、すべてのアプリ シナリオがデータ バインディングに役立つわけではありません。 場合によっては、実行時に新しい UI 要素を作成して表示する必要があります。 BoxPanel
は、このシナリオ用です。 子項目の数の変更は、計算で子数を使用し、既存の子要素と新しい子要素の両方を新しいレイアウトに調整するため、 BoxPanel
では問題ありません。
BoxPanel
をさらに拡張するための高度なシナリオ (ここでは示されていません) では、動的な子に対応し、子の DesiredSize を個々のセルのサイズ設定のより強力な要素として使用できます。 このシナリオでは、さまざまな行または列のサイズ、またはグリッド以外の図形を使用して、"無駄な" 領域を減らすことができます。 これには、さまざまなサイズと縦横比の複数の四角形を、美学と最小サイズの両方を含む四角形に収まる方法の戦略が必要です。 BoxPanel
それはしません。スペースを分割するためのより簡単な手法を使用しています。 BoxPanel
の手法は、子カウントより大きい最小二乗数を決定する方法です。 たとえば、9 個の項目は 3 x 3 の正方形に収まります。 10 個のアイテムには 4 x 4 の正方形が必要です。 ただし、多くの場合、開始四角形の 1 つの行または列を削除しながら項目を収めてスペースを節約できます。 count=10 の例では、4x3 または 3x4 の四角形に収まります。
パネルが 10 個の項目に対して 5x2 を選択しないのはなぜか疑問に思うかもしれません。これは項目番号にきちんと合うためです。 ただし、実際には、パネルは、強い方向の縦横比を持つことはほとんどない四角形としてサイズ設定されます。 最小二乗法は、サイズ設定ロジックをバイアスして、一般的なレイアウト図形とうまく連携し、セルの図形が奇数の縦横比を取得するサイズ設定を促進しない方法です。
関連トピック
リファレンス
概念
Windows developer