次の方法で共有


XAML カスタム パネルの概要

panel は、Extensible Application Markup Language (XAML) レイアウト システムが実行され、アプリ UI がレンダリングされるときに、含まれる子要素のレイアウト動作を提供するオブジェクトです。

重要な API: PanelArrangeOverrideMeasureOverride

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

Panel 基底クラス

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

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

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

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

Children プロパティ

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

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

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

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

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

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

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

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

基本の Panel クラスから、MeasureOverride および ArrangeOverride の組み込みのレイアウト メソッドの動作は取得されません。 Children 内の項目は、XAML ビジュアル ツリーの一部として自動的にレンダリングされません。 Children で見つけた各項目に対してレイアウト メソッドを呼び出しMeasureOverride および 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の内部実装が動作し、レイアウトのオーバーライドでその動作を考慮する必要があります。

一部の要素には、HeightWidthAuto 値があるため、自然なサイズがありません。 これらの要素は完全な availableSize を使用します。これは、Auto 値が表すので、要素のサイズを使用可能な最大サイズに設定します。これは、即時レイアウトの親が availableSizeMeasure を呼び出すことによって通信します。 実際には、UI のサイズが (最上位レベルのウィンドウであっても) 常にいくつかの測定値があります。最終的に、メジャー パスはすべての Auto 値を親制約に解決し、すべての Auto 値要素は実際の測定値を取得します (レイアウトの完了後に ActualWidth および ActualHeight をチェックすることで取得できます)。

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

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

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

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

ヒントとガイダンス

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

ArrangeOverride

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

Infinity コンポーネントを持つ Size を返さないでください。 このような Size を使用しようとすると、内部レイアウトから例外がスローされます。

すべての ArrangeOverride 実装は、 Children をループ処理し、各子要素に対して Arrange メソッドを呼び出す必要があります。 Measure と同様に、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 が変更された場合は、新しいメジャー パスが必要です。 レイアウト システムは、真のメジャー変更を自動的に検出し、メジャー パスをもう一度呼び出してから、別の配置パスを実行します。

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

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

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

パネルとコントロール

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

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

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

その他のレイアウト API

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

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

リファレンス

概念