次の方法で共有


WPF のツリー

多くのテクノロジでは、要素とコンポーネントがツリー構造に編成され、開発者はツリー内のオブジェクト ノードを直接操作して、アプリケーションのレンダリングや動作に影響を与えます。 Windows Presentation Foundation (WPF) では、複数のツリー構造のメタファーを使用して、プログラム要素間のリレーションシップを定義します。 ほとんどの WPF 開発者は、オブジェクト ツリーのメタファーについて概念的に考えながら、コードでアプリケーションを作成したり、XAML でアプリケーションの一部を定義したりすることができますが、XML DOM で使用するような一般的なオブジェクト ツリー操作 API ではなく、特定の API を呼び出すか、特定のマークアップを使用して行います。 WPF では、ツリーメタファー ビューを提供する 2 つのヘルパー クラス (LogicalTreeHelperVisualTreeHelper) が公開されています。 これらの同じツリーは特定の主要な WPF 機能の動作を理解するのに役立つため、WPF ドキュメントでもビジュアル ツリーと論理ツリーという用語が使用されます。 このトピックでは、ビジュアル ツリーと論理ツリーが何を表すのかを定義し、そのようなツリーがオブジェクト ツリーの概念全体とどのように関連しているかを説明し、LogicalTreeHelperVisualTreeHelperについて説明します。

WPF のツリー

WPF で最も完全なツリー構造はオブジェクト ツリーです。 XAML でアプリケーション ページを定義し、XAML を読み込む場合、マークアップ内の要素の入れ子のリレーションシップに基づいてツリー構造が作成されます。 アプリケーションまたはアプリケーションの一部をコードで定義すると、特定のオブジェクトのコンテンツ モデルを実装するプロパティのプロパティ値を割り当てる方法に基づいてツリー構造が作成されます。 WPF では、完全なオブジェクト ツリーを概念化し、そのパブリック API に報告する方法として、論理ツリーとビジュアル ツリーの 2 つの方法があります。 論理ツリーとビジュアル ツリーの違いは必ずしも重要であるとは限りませんが、特定の WPF サブシステムで問題が発生したり、マークアップやコードで行う選択に影響を与えたりすることがあります。

論理ツリーまたはビジュアル ツリーを直接操作するとは限りませんが、ツリーの相互作用の概念を理解することは、テクノロジとしての WPF を理解するのに役立ちます。 WPF をある種のツリーメタファーと考えることも、WPF でのプロパティの継承とイベント ルーティングのしくみを理解するうえで重要です。

手記

オブジェクト ツリーは実際の API よりも概念であるため、概念を考えるもう 1 つの方法はオブジェクト グラフです。 実行時には、ツリーメタファーが崩れるオブジェクト間のリレーションシップがあります。 ただし、特に XAML で定義された UI では、ツリーメタファーは、この一般的な概念を参照するときに、ほとんどの WPF ドキュメントでオブジェクト ツリーという用語を使用するのに十分な関連性があります。

論理ツリー

WPF では、それらの要素をバックするオブジェクトのプロパティを設定して、UI 要素にコンテンツを追加します。 たとえば、Items プロパティを操作して、ListBox コントロールに項目を追加します。 これにより、Items プロパティ値である ItemCollection に項目を配置します。 同様に、DockPanelにオブジェクトを追加するには、その Children プロパティ値を操作します。 ここでは、UIElementCollectionにオブジェクトを追加します。 コード例については、「方法: 要素を動的に追加する」を参照してください。

拡張アプリケーション マークアップ言語 (XAML) では、DockPanel内の ListBox またはコントロールまたはその他の UI 要素にリストアイテムを配置する場合、次の例のように、明示的または暗黙的に Items プロパティと Children プロパティも使用します。

<DockPanel
  Name="ParentElement"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://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 として処理し、暗黙的としてコメント アウトされたタグを含めた場合 (これは有効でした)、結果の XML DOM ツリーには、<ListBox.Items> とその他の暗黙的な項目の要素が含まれます。 ただし、XAML では、マークアップを読み取ってオブジェクトに書き込むときにそのように処理されることはありません。結果のオブジェクト グラフには、文字どおり ListBox.Itemsが含まれません。 ただし、ItemCollectionを含む Items という名前の ListBox プロパティがあり、ItemCollection は初期化されますが、ListBox XAML の処理時には空です。 次に、ListBox のコンテンツとして存在する各子オブジェクト要素は、ItemCollection.Addへのパーサー呼び出しによって ItemCollection に追加されます。 XAML をオブジェクト ツリーに処理するこの例は、これまでは、作成されたオブジェクト ツリーが基本的に論理ツリーである一見の例です。

ただし、XAML の暗黙的な構文項目が除外されている場合でも、論理ツリーは実行時にアプリケーション UI に対して存在するオブジェクト グラフ全体ではありません。その主な理由は、ビジュアルとテンプレートです。 たとえば、Buttonについて考えてみましょう。 論理ツリーは、Button オブジェクトとその文字列 Contentを報告します。 ただし、ランタイム オブジェクト ツリーには、このボタンにさらに多くがあります。 特に、ボタンは、特定の Button コントロール テンプレートが適用されたため、画面にのみ表示されます。 適用されたテンプレートから取得されたビジュアル (ビジュアル ボタンの周囲の濃い灰色のテンプレート定義 Border など) は、実行時に論理ツリーを見ている場合 (表示されている UI からの入力イベントの処理や論理ツリーの読み取りなど) でも、論理ツリーでは報告されません。 テンプレート ビジュアルを見つけるには、代わりにビジュアル ツリーを調べる必要があります。

XAML 構文が作成されたオブジェクト グラフにどのようにマップされるか、また XAML の暗黙の構文について詳しくは、「XAML 構文の詳細 」または「WPF における XAML 」を参照してください。

論理ツリーの目的

論理ツリーが存在するため、コンテンツ モデルは、可能な子オブジェクトを簡単に反復処理でき、コンテンツ モデルを拡張可能にすることができます。 また、論理ツリーには、論理ツリー内のすべてのオブジェクトが読み込まれる場合など、特定の通知用のフレームワークが用意されています。 基本的に、論理ツリーはフレームワーク レベルでの実行時オブジェクト グラフの近似値であり、ビジュアルは除外されますが、独自のランタイム アプリケーションの構成に対する多くのクエリ操作には十分です。

さらに、静的リソース参照と動的リソース参照の両方を解決するために、最初の要求元オブジェクトから始めて、論理ツリーを上方にたどりながら Resources コレクションを調べます。その後、引き続き論理ツリーを上に進み、各 FrameworkElement (または FrameworkContentElement) を確認し、該当するキーを含む可能性のある ResourceDictionaryを含む別の Resources の値を探します。 論理ツリーとビジュアル ツリーの両方が存在する場合、論理ツリーはリソース参照に使用されます。 リソース ディクショナリと参照の詳細については、「XAML リソース」を参照してください。

論理ツリーの構成

論理ツリーは WPF フレームワーク レベルで定義されます。これは、論理ツリー操作に最も関連する WPF 基本要素が FrameworkElement または FrameworkContentElementであることを意味します。 ただし、LogicalTreeHelper API を実際に使用しているかどうかを確認できるように、論理ツリーには、FrameworkElement または FrameworkContentElementではないノードが含まれている場合があります。 たとえば、論理ツリーは、文字列である TextBlockText 値を報告します。

論理ツリーのオーバーライド

高度なコントロール作成者は、一般的なオブジェクトまたはコンテンツ モデルが論理ツリー内のオブジェクトを追加または削除する方法を定義する複数の API をオーバーライドすることで、論理ツリーをオーバーライドできます。 論理ツリーをオーバーライドする方法の例については、「論理ツリーをオーバーライドする を参照してください。

プロパティ値の継承

プロパティ値の継承は、ハイブリッド ツリーを介して動作します。 プロパティの継承を有効にする Inherits プロパティを含む実際のメタデータは、WPF フレームワーク レベルの FrameworkPropertyMetadata クラスです。 したがって、元の値を保持する親と、その値を継承する子オブジェクトの両方が FrameworkElement または FrameworkContentElementである必要があり、両方とも論理ツリーの一部である必要があります。 ただし、プロパティの継承をサポートする既存の WPF プロパティの場合、プロパティ値の継承は、論理ツリーにない中間オブジェクトを通じて永続化できます。 主に、テンプレート要素が、テンプレート化されたインスタンスまたはさらに高いレベルで設定された継承されたプロパティ値を使用する場合、あるいはページレベルのコンポジションやそれ以上の論理ツリーの上位に関連する場合に関係します。 プロパティ値の継承がこのような境界を越えて一貫して機能するためには、継承プロパティを添付プロパティとして登録する必要があります。プロパティの継承動作を使用してカスタム依存関係プロパティを定義する場合は、このパターンに従う必要があります。 プロパティの継承に使用される正確なツリーは、実行時であっても、ヘルパー クラス ユーティリティ メソッドでは完全には予測できません。 詳細については、プロパティ値の継承を参照してください。

ビジュアル ツリー

論理ツリーの概念に加えて、WPF のビジュアル ツリーの概念もあります。 ビジュアル ツリーは、Visual 基底クラスによって表されるビジュアル オブジェクトの構造を表します。 コントロールのテンプレートを作成するときは、そのコントロールに適用されるビジュアル ツリーを定義または再定義します。 ビジュアル ツリーは、パフォーマンスと最適化の理由から描画を下位レベルで制御する必要がある開発者にも重要です。 従来の WPF アプリケーション プログラミングの一部としてのビジュアル ツリーの公開の 1 つは、ルーティング イベントのイベント ルートは、主に論理ツリーではなくビジュアル ツリーに沿って移動することです。 この微妙なルーティング イベントの動作は、コントロールの作成者でない限り、すぐには明らかにならない場合があります。 ビジュアル ツリーを介してイベントをルーティングすると、ビジュアル レベルでコンポジションを実装するコントロールで、イベントを処理したり、イベント セッターを作成したりできます。

ツリー、コンテンツ要素、およびコンテンツ ホスト

コンテンツ要素 (ContentElementから派生するクラス) はビジュアル ツリーの一部ではありません。これらは Visual から継承されず、視覚的表現も持っていません。 UI に表示するには、ContentElement を、Visual と論理ツリーの両方の参加要素であるコンテンツ ホストでホストする必要があります。 通常、このようなオブジェクトは FrameworkElementです。 コンテンツ ホストがコンテンツの "ブラウザー" に似ているという概念を理解し、ホストが制御する画面領域内でそのコンテンツを表示する方法を選択できます。 コンテンツがホストされると、通常はビジュアル ツリーに関連付けられている特定のツリー プロセスにコンテンツを参加させることができます。 一般に、FrameworkElement ホスト クラスには、ホストされたコンテンツが真のビジュアル ツリーの一部ではない場合でも、コンテンツ論理ツリーのサブノードを介してイベント ルートにホストされた ContentElement を追加する実装コードが含まれています。 これは、ContentElement がそれ以外の要素にルーティングするルーティング イベントをソースにできるようにするために必要です。

ツリー トラバーサル

LogicalTreeHelper クラスは、論理ツリー トラバーサルの GetChildrenGetParent、および FindLogicalNode メソッドを提供します。 ほとんどの場合、既存のコントロールの論理ツリーを走査する必要はありません。これらのコントロールは、ほとんどの場合、その論理子要素を、Addやインデクサーなどのコレクション アクセスをサポートする専用のコレクション プロパティとして公開するためです。 ツリー トラバーサルは、主に、コレクション プロパティが既に定義されている ItemsControlPanel などの目的のコントロール パターンから派生しないことを選択し、独自のコレクション プロパティのサポートを提供するコントロール作成者によって使用されるシナリオです。

ビジュアル ツリーは、ビジュアル ツリー トラバーサル用のヘルパー クラスVisualTreeHelperもサポートしています。 ビジュアル ツリーはコントロール固有のプロパティを使用して便利に公開されないため、プログラミング シナリオで必要な場合は、VisualTreeHelper クラスを使用してビジュアル ツリーを走査することをお勧めします。 詳細については、「WPF グラフィックス レンダリングの概要」を参照してください。

手記

場合によっては、適用されたテンプレートのビジュアル ツリーを調べる必要があります。 この手法を使用する場合は注意が必要です。 テンプレートを定義するコントロールのビジュアル ツリーを走査している場合でも、コントロールのコンシューマーはインスタンスに Template プロパティを設定することでテンプレートを常に変更でき、エンド ユーザーもシステム テーマを変更することで適用されたテンプレートに影響を与えることができます。

ルーティングされたイベントのルートを「ツリー」として

前述のように、特定のルーティング イベントのルートは、ビジュアルツリーと論理ツリー表現のハイブリッドであるツリーの単一の所定のパスに沿って移動します。 イベント ルートは、トンネリングルーティング イベントかバブル ルーティング イベントかに応じて、ツリー内の上下の方向に移動できます。 イベント ルートの概念には、実際にルーティングされるイベントを発生させるのとは別に、イベント ルートを "ウォーク" するために使用できる直接サポート ヘルパー クラスはありません。 ルート EventRouteを表すクラスがありますが、そのクラスのメソッドは一般に内部でのみ使用されます。

リソース ディクショナリとツリー

ページで定義されているすべての Resources のリソース ディクショナリ検索は、基本的に論理ツリーを走査します。 論理ツリーにないオブジェクトはキー付きリソースを参照できますが、リソース参照シーケンスは、そのオブジェクトが論理ツリーに接続されている時点から開始されます。 WPF では、論理ツリー ノードのみが ResourceDictionaryを含む Resources プロパティを持つことができます。したがって、ResourceDictionaryからキー付きリソースを探してビジュアル ツリーを走査してもメリットはありません。

ただし、リソース参照は、即時論理ツリーを超えて拡張することもできます。 アプリケーション マークアップの場合、リソース参照は、アプリケーション レベルのリソース ディクショナリに進み、静的プロパティまたはキーとして参照されるテーマのサポートとシステム値に進むことができます。 また、リソース参照が動的な場合は、テーマ自体がテーマ論理ツリーの外部でシステム値を参照することもできます。 リソース ディクショナリと参照ロジックの詳細については、「XAML リソース」を参照してください。

参照