次の方法で共有


XAML カスタム パネルの概要

パネルは、拡張アプリケーション マークアップ言語 (XAML) レイアウト システムが実行され、アプリ UI がレンダリングされるときに、そのパネルに含まれる子要素のレイアウト動作を提供するオブジェクトです。

重要な API: パネルArrangeOverrideMeasureOverride

PANEL クラスからカスタム クラスを派生させることで、XAML レイアウトのカスタム パネル を定義できます。 パネルの動作を提供するには、MeasureOverride をオーバーライドして、ArrangeOverrideをオーバーライドし、子要素を測定および配置するロジックを提供します。

Panel 基本クラス

カスタム パネル クラスを定義するには、 Panel クラスから直接派生するか、 GridStackPanel など、シールされていない実用的なパネル クラスの 1 つから派生させることができます。 既にレイアウト動作があるパネルの既存のレイアウト ロジックを回避するのは困難な場合があるため、 Panel から派生する方が簡単です。 また、動作を持つパネルには、パネルのレイアウト機能に関連しない既存のプロパティが含まれる場合があります。

Panel から、カスタム パネルは次の API を継承します。

  • Children プロパティです。
  • の背景ChildrenTransitionsIsItemsHost プロパティ、および依存関係プロパティ識別子。 これらのプロパティはいずれも仮想的なプロパティでないため、通常はオーバーライドしたり置き換えたりしません。 通常、これらのプロパティは、値の読み取り用ではなく、カスタム パネル シナリオでは必要ありません。
  • レイアウトは、MeasureOverride と ArrangeOverrideメソッドをオーバーライドします。 これらはもともと FrameworkElement によって定義されていました。 基本 Panel クラスはこれらをオーバーライドしませんが、 Grid のような実用的なパネルには、ネイティブ コードとして実装され、システムによって実行されるオーバーライド実装があります。 ArrangeOverrideMeasureOverride に新しい (または追加の) 実装を提供することは、カスタム パネルを定義するために必要な作業の大部分です。
  • FrameworkElementUIElementDependencyObjectの他のすべての API には、HeightVisibility などがあります。 レイアウトのオーバーライドでこれらのプロパティの値を参照する場合がありますが、これらは仮想的でないため、通常はオーバーライドしたり置き換えたりしません。

ここでは、XAML レイアウトの概念について説明します。これにより、カスタム パネルがレイアウトでどのように動作できるか、そしてどのように動作すべきかを考慮することができます。 すぐにアクセスしてカスタム パネルの実装例を確認する場合は、カスタム パネルの 例である BoxPanel を参照してください。

Children プロパティ

Children プロパティは、Panel から派生したすべてのクラス コレクションに含まれる子要素を格納する場所として Children プロパティを使用するため、カスタム パネルに関連します。 子要素 は、Panel クラスの XAML コンテンツ プロパティとして指定されており、Panel から派生したすべてのクラスは、その XAML コンテンツ プロパティの動作を継承することができます。 プロパティが XAML コンテンツ プロパティに指定されている場合は、マークアップでそのプロパティを指定するときに XAML マークアップでプロパティ要素を省略でき、値は即時マークアップの子 ("content") として設定されることを意味します。 たとえば、新しい動作を定義しない CustomPanel という名前のクラスを Panel から派生した場合でも、このマークアップを使用できます。

<local:CustomPanel>
  <Button Name="button1"/>
  <Button Name="button2"/>
</local:CustomPanel>

XAML パーサーがこのマークアップを読み取ると、Children はすべての Panel 派生型の XAML コンテンツ プロパティであることがわかっているので、パーサーは Children プロパティのUIElementCollection 値に 2 つの Button 要素を追加します。 XAML コンテンツ プロパティは、UI 定義の XAML マークアップにおいて、効率的な親子関係を促進します。 XAML コンテンツ プロパティの詳細と、XAML の解析時のコレクション プロパティの設定方法については、 XAML 構文ガイドを参照してください。

Children プロパティの値を保持しているコレクション型は、UIElementCollection クラスです。 UIElementCollection は、UIElement を適用するアイテム型として使用される厳密に型指定されたコレクションです。 UIElement は、何百もの実用的な UI 要素型によって継承される基本型であるため、ここでの型の強制は意図的に緩いです。 ただし、 ブラシパネルの直接の子として持つことができなかったことが強制されます。一般に、UI に表示され、レイアウトに参加することが期待される要素のみが Panel の子要素として検出されることを意味します。

XAML 定義によって、通常、カスタム パネルは、Children プロパティ as-isの特性を使用するだけで、UIElement 子要素を受け入れることができます。 高度なシナリオとして、レイアウトのオーバーライドでコレクションを繰り返し処理する際に、子要素のさらに詳細な型チェックをサポートできます。

オーバーライド内の Children コレクションをループするだけでなく、パネル ロジックも Children.Countの影響を受ける可能性があります。 必要なサイズや個々の項目の他の特性ではなく、少なくとも一部は項目の数に基づいて領域を割り当てるロジックがある場合があります。

レイアウト メソッドのオーバーライド

レイアウト オーバーライド メソッド (MeasureOverride および ArrangeOverride) の基本モデルは、すべての子を反復処理し、各子要素の特定のレイアウト メソッドを呼び出す必要があるということです。 最初のレイアウト サイクルは、XAML レイアウト システムがルート ウィンドウのビジュアルを設定したときに開始されます。 各親は子にレイアウトを呼び出すので、レイアウト メソッドの呼び出しが、レイアウトの一部であると想定されるすべての UI 要素に伝達されます。 XAML レイアウトには、まず測定し、その後配置するという 2 つの段階があります。

基本 Panel クラスから MeasureOverrideArrangeOverride の組み込みのレイアウト メソッドの動作は取得されません。 の項目は、XAML ビジュアル ツリーの一部として自動的にレンダリングされません。 MeasureOverride 内のレイアウト パスで Children で見つけた各項目にレイアウト メソッドを呼び出すこと、および ArrangeOverride 実装を行うことによって、レイアウト プロセスに項目を認識させるかどうかはあなた次第です。

独自の継承がない限り、レイアウトオーバーライドで基本実装を呼び出す理由はありません。 レイアウト動作のネイティブ メソッド (存在する場合) は関係なく実行され、オーバーライドから基本実装を呼び出さないと、ネイティブ動作は発生しません。

メジャー パス中に、レイアウト ロジックは、その子要素に対して Measure メソッドを呼び出すことによって、各子要素に対して目的のサイズを照会します。 Measure メソッドを呼び出すと、DesiredSize プロパティの値が確立されます。 MeasureOverride の戻り値は、パネル自体の希望するサイズです。

配置パス中に、子要素の位置とサイズが x-y 空間で決定され、レイアウト構成がレンダリング用に準備されます。 コードでは、要素がレイアウトに属していることをレイアウト システムが検出できるように、Children の各子要素に対して Arrange を呼び出す必要があります。 Arrange 呼び出しは、コンポジションとレンダリングの前駆物質です。これは、コンポジションがレンダリング用に送信されるときに、その要素が配置される場所をレイアウト システムに通知します。

多くのプロパティと値は、実行時のレイアウト ロジックの動作に影響します。 レイアウト プロセスについて考える方法は、子を持たない要素 (通常、UI で最も深く入れ子になった要素) が、最初に測定を最終処理できる要素であるということです。 彼らは、望ましいサイズに影響を与える子要素への依存関係がありません。 彼らは希望するサイズを持っているかもしれず、これらはレイアウトが実際に行われるまでのサイズ提案です。 次に、測定処理は、すべての測定値が確定し、ルート要素の測定値が確定するまで、ビジュアル ツリーを上の階層へとたどり続けます。

候補のレイアウトは、現在のアプリ ウィンドウ内に収まる必要があります。そうしないと、UI の一部がクリップされます。 多くの場合、パネルはクリッピング ロジックが決定される場所です。 パネル ロジックでは、 MeasureOverride 実装内から使用可能なサイズを決定できます。また、サイズ制限を子にプッシュし、すべてが可能な限り適合するように子間でスペースを分割する必要がある場合があります。 レイアウトの結果は、レイアウトのすべての部分のさまざまなプロパティを使用するものの、アプリ ウィンドウ内に収まるのが理想的です。 これには、パネルのレイアウト ロジックに適した実装と、そのパネルを使用して UI を構築するアプリ コードの部分での慎重な UI デザインの両方が必要です。 UI デザイン全体に、アプリに収まりきらないほど多くの子要素が含まれていると、どんなに良いパネルデザインでも適切に表示されません。

レイアウト システムを機能させる大きな部分は、 FrameworkElement に基づくすべての要素が、コンテナー内の子として動作する際に固有の動作の一部を既に持っていることです。 たとえば、レイアウトの動作を通知するか、レイアウトをまったく機能させるために必要な FrameworkElement の API がいくつかあります。 これらには次のものが含まれます。

MeasureOverride

MeasureOverride メソッドには、パネル自体の開始 DesiredSize としてレイアウト システムによって使用される戻り値があります。これは、Measure メソッドがレイアウトの親によってパネル上で呼び出されたときです。 メソッド内のロジックの選択は、返される内容と同じくらい重要であり、多くの場合、ロジックは返される値に影響します。

すべての MeasureOverride 実装は 、Children をループ処理し、各子要素に対して Measure メソッドを呼び出す必要があります。 Measure メソッドを呼び出すと、DesiredSize プロパティの値が確立されます。 これにより、パネル自体に必要な領域の量と、そのスペースが要素間でどのように分割されるか、または特定の子要素のサイズが示される場合があります。

MeasureOverride メソッドの非常に基本的なスケルトンを次に示します。

protected override Size MeasureOverride(Size availableSize)
{
    Size returnSize; //TODO might return availableSize, might do something else
     
    //loop through each Child, call Measure on each
    foreach (UIElement child in Children)
    {
        child.Measure(new Size()); // TODO determine how much space the panel allots for this child, that's what you pass to Measure
        Size childDesiredSize = child.DesiredSize; //TODO determine how the returned Size is influenced by each child's DesiredSize
        //TODO, logic if passed-in Size and net DesiredSize are different, does that matter?
    }
    return returnSize;
}

多くの場合、要素はレイアウトの準備ができるまでに自然なサイズになります。 メジャーが完了した後、DesiredSize は、Measure に渡した availableSize が小さい場合、その自然なサイズを示す可能性があります。 自然サイズがavailableSizeを超える場合、Measureに渡されたDesiredSizeavailableSizeまでに制約されます。 このように Measure の内部実装が動作し、レイアウトのオーバーライドでその動作を考慮する必要があります。

一部の要素は、高さと自動値を持っているため、自然なサイズを持っていません。 これらの要素は、availableSize全体を使用します。これは、自動 値が表すので、要素を使用可能な最大サイズにサイズ変更します。これは、即時レイアウトの親が availableSizeを使用して Measure を呼び出すことによって通信します。 実際には、UI のサイズが決まる基準が常に存在します(それが最上位レベルのウィンドウであってもです)。最終的に、メジャーパスはすべての 自動 値を親の制約条件に解決し、すべての 自動 値要素が実際の測定値を取得します(レイアウトが完了した後、ActualWidthActualHeightを確認することで取得できます)。

無限次元を 1 つ以上持つサイズを Measure に渡すことは合法であり、これによりパネルがコンテンツの測定値に合わせて自分のサイズを調整できることを示しています。 測定される各子要素は、その自然なサイズを使用して DesiredSize 値を設定します。 次に、整列パス中に、パネルは通常、そのサイズを使用して配置します。

TextBlock などのテキスト要素は、Height または Width 値が設定されていない場合でも、テキスト文字列とテキスト プロパティに基づいて計算された ActualWidthActualHeight を持ち、これらのディメンションはパネル ロジックで考慮する必要があります。 クリッピング テキストは、特に不適切な UI エクスペリエンスです。

実装で目的のサイズ測定が使用されていない場合でも、Measure メソッドを呼び出すことによりトリガーされる内部およびネイティブの振る舞いがあるため、各子要素でMeasure メソッドを呼び出すのが最善です。 要素がレイアウトに参加するためには、各子要素にメジャーパス中に Measure が呼び出され、さらに配置パス中に Arrange メソッドが呼び出される必要があります。 これらのメソッドを呼び出すと、オブジェクトに内部フラグが設定され、システムのレイアウト ロジックがビジュアル ツリーをビルドして UI をレンダリングするときに必要な値 ( DesiredSize プロパティなど) が設定されます。

MeasureOverride 戻り値は、measure が呼び出されたときに DesiredSize またはその他のサイズに関する考慮事項を解釈するパネルのロジックに基づいて 子要素が呼び出 されたときに されます。 子からの DesiredSize 値と、MeasureOverride の戻り値でそれらをどう使用するかは、各自のロジックの解釈に委ねられています。 通常、値をそのまま加算することはありません。なぜなら、MeasureOverride の入力値は、多くの場合、パネルの親によって提案される固定の利用可能なサイズであるからです。 このサイズを超えると、パネル自体がクリップされる可能性があります。 通常は、子の合計サイズをパネルの使用可能なサイズと比較し、必要に応じて調整します。

ヒントとガイダンス

  • カスタム パネルは、UI コンポジションの最初の真のビジュアルとして、理想的には Pageのすぐ下のレベル、または UserControl や XAML ページのルートである他の要素になるよう適している必要があります。 MeasureOverride 実装では、入力 Size を値を調べずに定期的に返さないでください。 戻り値 SizeInfinity 値が含まれている場合、ランタイムレイアウトロジックで例外を投げることができます。 Infinity 値はメイン アプリ ウィンドウから取得できます。これはスクロール可能であるため、最大の高さはありません。 その他のスクロール可能なコンテンツの動作は同じ場合があります。
  • MeasureOverride 実装 一般的なもう 1 つの間違いは、新しい既定の Size を返すことです (高さと幅の値は 0 です)。 その値から始めることもできます。また、どの子もレンダリングされないとパネルが判断した場合は、正しい値になる場合もあります。 ただし、既定の サイズ では、パネルのサイズがホストによって正しく調整されません。 UI にスペースを要求しないため、領域が取得されず、レンダリングされません。 それ以外の場合、すべてのパネル コードは正常に機能している可能性がありますが、高さ 0、幅 0 で構成されている場合、パネルまたはその内容は表示されません。
  • オーバーライド内では、子要素を FrameworkElement にキャストする誘惑を避け、特に ActualWidthActualHeightなど、レイアウトの結果として計算されるプロパティを使用するようにします。 ほとんどの一般的なシナリオでは、子の DesiredSize 値に基づいてロジックを作成できます。子要素の Height または Width 関連のプロパティは必要ありません。 要素の種類がわかっていて、画像ファイルの自然なサイズなどの追加情報がある特殊なケースでは、レイアウト システムによってアクティブに変更されている値ではないため、要素の特殊な情報を使用できます。 レイアウト計算プロパティをレイアウト ロジックの一部として含めると、意図しないレイアウト ループを定義するリスクが大幅に高まります。 これらのループは、有効なレイアウトを作成できず、ループが回復できない場合にシステムが LayoutCycleException を スローできる状態を引き起こします。
  • パネルは通常、使用可能なスペースを複数の子要素間で分割しますが、スペースの分割方法は異なります。 たとえば、Grid は、RowDefinition 値と ColumnDefinitionを使用してスペースを Grid セルに分割するレイアウト ロジックを実装し、スター サイズ設定とピクセル値の両方をサポートします。 ピクセル値の場合、各子で使用できるサイズは既にわかっているので、グリッド スタイルの メジャーの入力サイズとして渡されます。
  • パネル自体は、項目間の余白用のスペースを設けることができます。 これを行う場合は、の測定値を、 のマージンや のパディング プロパティとは異なる独自のプロパティとして明確にしてください。
  • 要素は、前のレイアウト パスに基づいて ActualWidth プロパティや ActualHeight プロパティに値を持つことがあります。 値が変更された場合、アプリ UI コードは、実行する特別なロジックがある場合に LayoutUpdated のハンドラーを要素に配置できますが、通常、パネル ロジックはイベント処理で変更を確認する必要はありません。 レイアウト関連のプロパティの値が変更され、適切な状況でパネルの MeasureOverride または ArrangeOverride が自動的に呼び出されるため、レイアウト システムはレイアウトを再実行するタイミングを決定しています。

ArrangeOverride メソッド

ArrangeOverride メソッドには、パネル自体をレンダリングするときにレイアウト システムによって使用される Size 戻り値があります。この戻り値は、レイアウト内の親によってパネルで Arrange メソッドが呼び出されたときに使用されます。 通常、入力された finalSizeArrangeOverride によって返される Size は一致します。 そうでない場合は、パネルがレイアウト要求の他の参加者が使用できるサイズとは異なるサイズにしようとしていることを意味します。 最終的なサイズは、以前にパネル コードを通じてレイアウトのメジャー パスを実行したことに基づいているため、異なるサイズを返すのが一般的ではありません。つまり、意図的にメジャー ロジックを無視していることを意味します。

SizeInfinity コンポーネントを含むものを返さないでください。 サイズ を使用しようとすると、内部レイアウトによって例外が発生します。

すべての ArrangeOverride 実装は、Children をループ処理し、各子要素に対して Arrange メソッドを呼び出す必要があります。 メジャーと同様に、アレンジ には戻り値がありません。 Measure とは異なり、計算プロパティは結果として設定されません (ただし、問題の要素は通常、LayoutUpdated イベントを発生します)。

ArrangeOverride メソッドの非常に基本的なスケルトンを次に示します。

protected override Size ArrangeOverride(Size finalSize)
{
    //loop through each Child, call Arrange on each
    foreach (UIElement child in Children)
    {
        Point anchorPoint = new Point(); //TODO more logic for topleft corner placement in your panel
       // for this child, and based on finalSize or other internal state of your panel
        child.Arrange(new Rect(anchorPoint, child.DesiredSize)); //OR, set a different Size 
    }
    return finalSize; //OR, return a different Size, but that's rare
}

レイアウトの配置パスは、測定パスが事前に行われないことがあるかもしれません。 ただし、これは、レイアウト システムが以前の測定値に影響を与えるプロパティが変更されていないと判断した場合にのみ発生します。 たとえば、配置が変更された場合、その配置の選択が変更されても DesiredSize は変更されないため、その特定の要素を再測定する必要はありません。 一方、レイアウト内の要素で ActualHeight が変更される場合は、新しい測定パスが必要です。 レイアウト システムは、真のメジャー変更を自動的に検出し、メジャー パスをもう一度呼び出してから、別の配置パスを実行します。

配置 の入力は、Rect 値を受け取ります。 この Rect を構築する最も一般的な方法は、 Point 入力と Size 入力を持つコンストラクターを使用することです。 Point は、要素の境界ボックスの左上隅を配置するポイントです。 Size は、その特定の要素のレンダリングに使用されるディメンションです。 レイアウトに関係するすべての要素に対して DesiredSize を確立することは、レイアウトのメジャーパスの目的であるため、その要素に対してしばしばこの Size 値として DesiredSize を使用します。 メジャーパスは、レイアウト システムが配置パスに到達した後に要素の配置を最適化できるように、要素の全体的なサイズを反復的に決定します。

通常、ArrangeOverride の実装間で異なるのは、パネルが各子要素の配置を決定するためのロジックにおける Point コンポーネントです。 Canvas などの絶対配置パネルでは、各要素から取得される明示的な配置情報が、Canvas.LeftCanvas.Top の値を通じて使用されます。 グリッドなどのスペース分割パネルには、使用可能なスペースをセルに分割する数学的演算があり、各セルには、コンテンツを配置して配置する場所に対する x-y 値が含まれます。 StackPanel などのアダプティブ パネルは、コンテンツを向き寸法に合わせて拡張している可能性があります。

配置に直接制御して渡す以外にも、レイアウト内の要素に対する追加の配置の影響があります。 これらは、すべての FrameworkElement 派生型に共通し、テキスト要素などの他の型によって拡張される Arrange の内部ネイティブ実装に由来します。 たとえば、要素は余白と配置を持ち、一部の要素はパディングを持つことができます。 多くの場合、これらのプロパティは相互作用します。 詳細については、「 配置、余白、パディング」を参照してください。

パネルとコントロール

代わりにカスタム コントロールとしてビルドする必要があるカスタム パネルに機能を配置しないでください。 パネルの役割は、自動的に発生するレイアウトの関数として、その中に存在するすべての子要素コンテンツを表示することです。 パネルは、コンテンツに装飾を追加したり ( 罫線 が表示される要素の周囲に罫線を追加する方法と同様)、パディングなどのレイアウト関連の調整を実行したりできます。 ですが、ビジュアル ツリーの出力を拡張する際に、子からの情報を報告したり使用したりすることを超えない範囲でとどめるべきです。

ユーザーがアクセスできる操作がある場合は、パネルではなくカスタム コントロールを記述する必要があります。 たとえば、スクロール バーやつまみなどが対話型のコントロール パーツであるため、目的がクリッピングを防ぐことであっても、パネルは表示されるコンテンツにスクロール ビューポートを追加しないでください。 (コンテンツには結局スクロール バーがある場合がありますが、これは子のロジックに任せる必要があります。レイアウト操作としてスクロールを追加して強制しないでください)。)コントロールを作成し、そのコントロールのビジュアル ツリーで重要な役割を果たすカスタム パネルを作成することもできます。これは、そのコントロールにコンテンツを表示する場合です。 ただし、コントロールとパネルは個別のコード オブジェクトである必要があります。

コントロールとパネルの区別が重要な理由の 1 つは、Microsoft UI オートメーションとアクセシビリティによるものです。 パネルは、論理的な動作ではなく、視覚的なレイアウト動作を提供します。 UI 要素が視覚的にどのように表示されるかは、アクセシビリティ シナリオにとって通常重要な UI の側面ではありません。 アクセシビリティとは、UI を理解するために論理的に重要なアプリの部分を公開することです。 操作が必要な場合、コントロールは UI オートメーション インフラストラクチャに対話の可能性を公開する必要があります。 詳細については、「カスタム オートメーション ピア の項を参照してください」。

その他のレイアウト API

レイアウト システムの一部ですが、 Panel で宣言されていない API は他にもあります。 これらは、パネルの実装や、パネルを使用するカスタム コントロールで使用できます。

  • UpdateLayoutInvalidateMeasureInvalidateArrange は、レイアウト パスを開始するメソッドです。 InvalidateArrange はメジャーパスをトリガーしない可能性がありますが、他の2つはトリガーされることがあります。 レイアウト メソッドのオーバーライド内からこれらのメソッドを呼び出すことはありません。これは、レイアウト ループが発生することがほぼ確実であるためです。 通常、コントロール コードはそれらを呼び出す必要はありません。 レイアウトのほとんどの側面は、フレームワークで定義されたレイアウト プロパティ ( Width など) の変更を検出することによって自動的にトリガーされます。
  • LayoutUpdated は、要素のレイアウトの一部の側面が変更されたときに発生するイベントです。 これはパネルに固有ではありません。イベントは FrameworkElement によって定義されます。
  • SizeChanged は、レイアウトパスが終了した後にのみ発生するイベントであり、その結果として ActualHeight または ActualWidth が変更されたことを示します。 これは別の FrameworkElement イベントです。 LayoutUpdated が発生する場合がありますが、SizeChanged は発生しないことがあります。 たとえば、内部コンテンツは再配置される可能性がありますが、要素のサイズは変更されませんでした。

リファレンス

概念