WPF のツリー
多くのテクノロジでは、要素およびコンポーネントはツリー構造で編成され、開発者はこのツリー内のオブジェクト ノードを直接操作してアプリケーションのレンダリングまたは動作に影響を与えます。 Windows Presentation Foundation (WPF) でも各種のツリー構造というメタファを使用してプログラム要素間のリレーションシップを定義します。 ほとんどの場合、WPF 開発者は、オブジェクト ツリー メタファについて概念的に考えながらアプリケーションをコードで作成したりアプリケーションの一部を XAML で定義したりすることができますが、そのためには、XML DOM で使用するような一般的なオブジェクト ツリー操作 API ではなく特定の API を呼び出したり特定のマークアップを使用したりします。 WPF では、ツリー メタファ ビューを提供する LogicalTreeHelper と VisualTreeHelper の 2 つのヘルパー クラスを公開しています。 ビジュアル ツリーおよび論理ツリーという用語も WPF のドキュメントで使用されます。これらの同じツリーは、特定の主要な WPF 機能の動作を理解するうえで役立つためです。 ここでは、ビジュアル ツリーおよび論理ツリーが表す項目を定義し、これらのツリーがオブジェクト ツリーの全体的な概念とどのように関連するかについて説明し、LogicalTreeHelper および VisualTreeHelper について説明します。
このトピックは、次のセクションで構成されています。
- WPF のツリー
- 論理ツリー
- ビジュアル ツリー
- ツリー、コンテンツ要素、およびコンテンツ ホスト
- ツリーの走査
- "ツリー" としてのルーティング イベントのルート
- リソース ディクショナリとツリー
- 関連トピック
WPF のツリー
WPF の最も完全なツリー構造はオブジェクト ツリーです。 XAML でアプリケーション ページを定義し、その XAML を読み込むと、マークアップ内の要素の入れ子のリレーションシップに基づいてツリー構造が作成されます。 コードでアプリケーションまたはアプリケーションの一部を定義した場合、ツリー構造は、指定したオブジェクトのコンテンツ モデルを実装するプロパティにプロパティ値を割り当てる方法に基づいて作成されます。 Windows Presentation Foundation (WPF) には、完全なオブジェクト ツリーを概念化し、パブリック API にレポートできるようにする方法として、論理ツリーとビジュアル ツリーの 2 つが用意されています。 論理ツリーとビジュアル ツリーの違いは必ずしも重要とは限りませんが、特定の WPF サブシステムでは問題が生じることがあり、マークアップやコード内での選択に影響することがあります。
論理ツリーやビジュアル ツリーを直接操作するとは限らない場合でも、ツリーの操作方法の概念を理解することは、WPF をテクノロジとして理解するうえで役立ちます。 WPF でのプロパティの継承やイベント ルーティングを理解するためには、WPF をある種のツリー メタファとして考えることも重要です。
メモ |
---|
オブジェクト ツリーは実際の API というよりは概念なので、この概念について考える別の方法としてオブジェクト グラフがあります。実際には、ツリー メタファが分割される実行時のオブジェクト間にリレーションシップがあります。ただし、特に XAML で定義された UI では、ツリー メタファは十分に関連性があるため、ほとんどの WPF のドキュメントではこの一般的な概念を指す場合にオブジェクト ツリーという用語が使用されます。 |
論理ツリー
WPF では、要素を支援するオブジェクトのプロパティを設定することで UI 要素にコンテンツを追加します。 たとえば、ListBox コントロールに項目を追加するには、Items プロパティを操作します。 これにより、Items プロパティの値である ItemCollection に項目を配置することになります。 同様に、DockPanel にオブジェクトを追加するには、Children プロパティ値を操作します。 この場合は、UIElementCollection にオブジェクトを追加することになります。 コード例については、「方法 : 要素を動的に追加する」を参照してください。
Extensible Application Markup Language (XAML) では、次の例に示すように、ListBox にリスト項目を配置する場合、あるいは DockPanel にコントロールまたは他の UI 要素を配置する場合は、Items プロパティと Children プロパティも明示的または暗黙的に使用します。
<DockPanel
Name="ParentElement"
xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
>
<!--implicit: <DockPanel.Children>-->
<ListBox DockPanel.Dock="Top">
<!--implicit: <ListBox.Items>-->
<ListBoxItem>
<TextBlock>Dog</TextBlock>
</ListBoxItem>
<ListBoxItem>
<TextBlock>Cat</TextBlock>
</ListBoxItem>
<ListBoxItem>
<TextBlock>Fish</TextBlock>
</ListBoxItem>
<!--implicit: </ListBox.Items>-->
</ListBox>
<Button Height="20" Width="100" DockPanel.Dock="Top">Buy a Pet</Button>
<!--implicit: </DockPanel.Children>-->
</DockPanel>
この XAML を XML としてドキュメント オブジェクト モデルで処理する場合、"implicit" としてコメント アウトされているタグを含めたとすると (これも有効な形式です)、結果として得られる XML DOM ツリーには、<ListBox.Items> および他の暗黙的な項目に対応する要素が含まれると予想されます。 しかし、マークアップを読み込んでオブジェクトに出力する時点では、XAML はそのようには処理されず、結果のオブジェクト グラフには指定したとおりの ListBox.Items は含まれません。 ただし、ItemCollection を格納する Items という名前の ListBox プロパティは含まれます。その ItemCollection は、ListBox の XAML の処理時に初期化されますが空です。 その後、パーサーによる ItemCollection.Add の呼び出しによって、ListBox のコンテンツとして存在する各子オブジェクト要素が ItemCollection に追加されます。 このオブジェクト ツリーへの XAML の処理例は、これまでのところ、作成されたオブジェクト ツリーが基本的に論理ツリーである例のように見えます。
ただし、論理ツリーは、XAML の暗黙的な構文項目が除外されている場合でも、実行時のアプリケーション UI に対して存在するオブジェクト グラフ全体ではありません。 この主な理由は、ビジュアルとテンプレートです。 たとえば Button について考えます。 論理ツリーでは、Button オブジェクトがレポートされ、文字列 Content もレポートされます。 ただし、ランタイム オブジェクト ツリーにはこのボタン以上のものが表示されます。 特に、特定の Button コントロール テンプレートが適用されたので、ボタンは画面上にその状態でのみ表示されます。 実行時に論理ツリーを参照している場合でも (表示される UI からの入力イベントを処理して論理ツリーを読み取るなど)、適用されたテンプレートから取得されたビジュアル (ビジュアル ボタンを囲むテンプレートで定義された濃い灰色の Border など) は論理ツリーではレポートされません。 テンプレートのビジュアルを確認するには、ビジュアル ツリーを調べる必要があります。
作成されるオブジェクト グラフおよび XAML の暗黙的な構文に XAML 構文が対応付けられる方法の詳細については、「XAML 構文の詳細」または「XAML の概要 (WPF)」を参照してください。
論理ツリーの目的
論理ツリーは、コンテンツ モデルが、使用可能な子オブジェクトを簡単に反復処理できること、およびコンテンツ モデルの拡張を目的としています。 論理ツリーは、論理ツリー内のすべてのオブジェクトがいつ読み込まれるかなど、特定の通知のフレームワークも提供します。 基本的に、論理ツリーは、ビジュアルを除外したフレームワーク レベルではランタイム オブジェクト グラフに近いと言えますが、独自のランタイム アプリケーションのコンポジションに対する多くのクエリ操作では論理ツリーで十分です。
また、静的リソース参照と動的リソース参照はどちらも、最初は要求元のオブジェクト、次は論理ツリーの上方で Resources コレクションの論理ツリーを介した上方への検索を行って、ResourceDictionary を格納し、場合によってはそのキーを格納する別の Resources 値の各 FrameworkElement (または FrameworkContentElement) をチェックすることによって解決されます。 論理ツリーとビジュアル ツリーの両方が存在する場合は、論理ツリーがリソースの検索に使用されます。 リソース ディクショナリおよび検索の詳細については、「リソースの概要」を参照してください。
論理ツリーのコンポジション
論理ツリーは、WPF フレームワーク レベルで定義されます。つまり、論理ツリーの操作に最も関連する WPF 基本要素は FrameworkElement または FrameworkContentElement です。 ただし、実際に LogicalTreeHelper API を使用するとわかるように、論理ツリーには FrameworkElement でも FrameworkContentElement でもないノードが含まれる場合があります。 たとえば、論理ツリーでは、文字列である TextBlock の Text 値がレポートされます。
論理ツリーのオーバーライド
高度なコントロールを作成するときには、論理ツリーをオーバーライドできます。そのためには、一般的なオブジェクトやコンテンツ モデルで論理ツリー内のオブジェクトを追加または削除する方法を定義している、いくつかの APIs をオーバーライドします。 論理ツリーのオーバーライド例については、「方法: 論理ツリーをオーバーライドする」を参照してください。
プロパティ値の継承
プロパティ値の継承は、ハイブリッド ツリーを通じて行われます。 プロパティの継承を有効化する Inherits プロパティが含まれる実際のメタデータは、WPF フレームワーク レベルの FrameworkPropertyMetadata クラスです。 このため、元の値を保持する親とその値を継承する子オブジェクトの両方が FrameworkElement または FrameworkContentElement であり、両方が、ある論理ツリーに含まれている必要があります。 ただし、プロパティの継承をサポートする既存の WPF プロパティでは、プロパティ値の継承は、論理ツリーにない、介在するオブジェクトを通じて存続できます。 これは主に、template 宣言されたインスタンスまたはさらに上位レベルのページ レベル コンポジション (論理ツリー内の上位) で設定されている継承されたプロパティ値をテンプレート要素で使用することに関連します。 プロパティ値の継承が、このような境界を越えて変わらず動作するためには、継承するプロパティを添付プロパティとして登録する必要があります。また、プロパティの継承動作を使用してカスタム依存関係プロパティを定義する場合は、このパターンに従う必要があります。 プロパティの継承で使用する正確なツリーを、実行時であっても、ヘルパー クラス ユーティリティ メソッドで完全に予測することができません。 詳細については、「プロパティ値の継承」を参照してください。
ビジュアル ツリー
WPF では、論理ツリーの概念に加えて、ビジュアル ツリーの概念も存在します。 ビジュアル ツリーは、Visual 基本クラスによって表されるビジュアル オブジェクトの構造を示します。 コントロールのテンプレートを作成する場合は、そのコントロールに適用するビジュアル ツリーを定義または再定義します。 ビジュアル ツリーは、パフォーマンスの向上および最適化のために、描画に対して低レベルの制御が必要な開発者にとっても非常に有用です。 従来の WPF アプリケーションのプログラミングの一環としてビジュアル ツリーについて公開されていることの 1 つは、ルーティング イベントのイベント ルートは、通常、論理ツリーではなくビジュアル ツリーをたどるということです。 ルーティング イベントの動作の細部は、コントロールの作成者でない限り、すぐにはわからない場合があります。 ビジュアル ツリーを介したルーティング イベントにより、ビジュアルのレベルで構成を実装するコントロールによってイベントを処理したり、イベント setter を作成したりできるようになります。
ツリー、コンテンツ要素、およびコンテンツ ホスト
コンテンツ要素 (ContentElement から派生するクラス) はビジュアル ツリーには含まれません。これらは Visual を継承しておらず、視覚的表現を持ちません。 UI に表示するには、Visual であり、論理ツリーの参加要素でもあるコンテンツ ホスト内で、ContentElement をホストする必要があります。 通常、このようなオブジェクトは FrameworkElement です。 コンテンツ ホストは、コンテンツにとって "ブラウザー" のようなものであり、ホストが制御する画面領域内でそのコンテンツの表示方法を選択するものと考えることができます。 コンテンツがホストされると、このコンテンツは、ビジュアル ツリーに通常関連付けられている特定のツリー プロセスで 1 つの要因になることができます。 一般に、FrameworkElement ホスト クラスには、ホストされるコンテンツが実際にはビジュアル ツリーに含まれない場合でも、コンテンツの論理ツリーのサブノードを介して、ホストされる ContentElement をイベント ルートに追加する実装コードが含まれます。 これは、ContentElement が、自身以外の要素にルーティングするルーティング イベントを調達できるようにするために必要です。
ツリーの走査
LogicalTreeHelper クラスには、論理ツリーの走査用に GetChildren、GetParent、および FindLogicalNode の各メソッドが用意されています。 これらのコントロールはほとんど常に論理上の子要素を専用のコレクション プロパティとして公開し、Add、インデクサーなどのコレクション アクセスをサポートしているため、多くの場合、既存のコントロールの論理ツリーを移動する必要はありません。 ツリーの移動が使用される主なシナリオは、コントロール作成者が、コレクション プロパティが定義済みの ItemsControl や Panel など、対象とするコントロール パターンから派生することを選択しない場合や、コレクション プロパティを独自にサポートする場合です。
ビジュアル ツリーも、ビジュアル ツリーの走査に使用できるヘルパー クラス VisualTreeHelper をサポートしています。 ビジュアル ツリーでは、コントロール固有のプロパティを使用した便利な移動方法は公開されていないため、プログラミング シナリオで必要な場合には、ビジュアル ツリーを移動する方法として VisualTreeHelper クラスが推奨されます。 詳細については、「WPF グラフィックス レンダリングの概要」を参照してください。
メモ |
---|
場合によっては、適用されたテンプレートのビジュアル ツリーを調べる必要が生じることがあります。この方法を使用するときには注意が必要です。テンプレートを定義するコントロールのビジュアル ツリーを走査する場合でも、コントロールのコンシューマーは、インスタンスで Template プロパティを設定することで常にテンプレートを変更でき、エンド ユーザーでさえシステム テーマを変更することで適用されたテンプレートに影響を与えることができます。 |
"ツリー" としてのルーティング イベントのルート
前述のように、特定のルーティング イベントのルートは、ビジュアル ツリーと論理ツリーの表現を組み合わせたツリーの単一の定義済みパスをたどります。 イベント ルートは、トンネル ルーティング イベントかバブル ルーティング イベントかに応じて、ツリーを上方向または下方向へ移動できます。 イベント ルートの概念では、実際にルーティングするイベントの発生とは無関係にイベント ルートを "移動する" のに使用される、ヘルパー クラスを直接にはサポートしていません。 ルートを表す EventRoute クラスはありますが、そのクラスのメソッドは、通常、内部でのみ使用されています。
リソース ディクショナリとツリー
ページで定義されているすべての Resources に対するリソース ディクショナリ検索では、基本的に、論理ツリーが走査されます。 論理ツリーにないオブジェクトもキーを持つリソースを参照できますが、リソース検索シーケンスは、そのオブジェクトが論理ツリーに接続している場所から開始されます。 WPF では、論理ツリー ノードのみが ResourceDictionary を含む Resources プロパティを使用できるため、ResourceDictionary からのキーを持つリソースの検索時にビジュアル ツリーを走査しても利点はありません。
ただし、リソース検索も、直接の論理ツリーを超えて拡張できます。 アプリケーション マークアップの場合、アプリケーション レベルのリソース ディクショナリ、テーマ サポート、および静的プロパティまたはキーとして参照されるシステム値へとリソース検索が続きます。 リソース参照が動的な場合は、テーマ自体も、テーマ論理ツリーの外部にあるシステム値を参照できます。 リソース ディクショナリおよび検索ロジックの詳細については、「リソースの概要」を参照してください。