次の方法で共有


WPF アーキテクチャ

このトピックでは、Windows Presentation Foundation (WPF) クラス階層のガイド付きツアーを提供します。 WPF の主要なサブシステムの大部分について説明し、それらの相互作用について説明します。 また、WPF のアーキテクトが行った選択の一部についても詳しく説明します。

System.Object

プライマリ WPF プログラミング モデルは、マネージド コードを通じて公開されます。 WPF の設計フェーズの早い段階では、システムのマネージド コンポーネントとアンマネージド コンポーネントの間で線を描画する場所について多くの議論がありました。 CLR には、開発の生産性と堅牢性を向上させる多くの機能 (メモリ管理、エラー処理、共通型システムなど) が用意されていますが、コストがかかります。

WPF の主要なコンポーネントを次の図に示します。 図の赤いセクション (PresentationFramework、PresentationCore、および milcore) は、WPF の主要なコード部分です。 これらの中で、アンマネージド コンポーネントである milcore は 1 つだけです。 Milcore は、DirectX との緊密な統合を可能にするためにアンマネージド コードで記述されています。 WPF のすべての表示は DirectX エンジンを介して行われ、効率的なハードウェアとソフトウェアのレンダリングが可能になります。 WPF では、メモリと実行を細かく制御する必要もあります。 milcore のコンポジション エンジンはパフォーマンスに非常に敏感であり、パフォーマンスを得るために CLR の多くの利点を与える必要があります。

.NET Framework 内での WPF の位置。

WPF のマネージド部分とアンマネージド部分の間の通信については、このトピックの後半で説明します。 マネージド プログラミング モデルの残りの部分については、以下で説明します。

System.Threading.DispatcherObject

WPF のほとんどのオブジェクトは、コンカレンシーとスレッド処理を処理するための基本的なコンストラクトを提供する DispatcherObjectから派生します。 WPF は、ディスパッチャーによって実装されるメッセージング システムに基づいています。 これは、使い慣れた Win32 メッセージ ポンプとよく似ています。実際、WPF ディスパッチャーは、クロス スレッド呼び出しを実行するために User32 メッセージを使用します。

WPF でのコンカレンシーについて説明する際に理解する必要がある主要な概念は、ディスパッチャーとスレッド アフィニティの 2 つあります。

WPF の設計フェーズでは、目標は単一の実行スレッドに移行することですが、スレッド以外の "アフィニティ化" モデルです。 スレッド アフィニティは、コンポーネントが実行中のスレッドの ID を使用して何らかの種類の状態を格納するときに発生します。 この最も一般的な形式は、スレッド ローカル ストア (TLS) を使用して状態を格納することです。 スレッド アフィニティを使用するには、実行の各論理スレッドがオペレーティング システム内の 1 つの物理スレッドによってのみ所有されている必要があります。このスレッドは、メモリを集中的に消費する可能性があります。 最後に、WPF のスレッド モデルは、スレッド アフィニティを持つシングル スレッド実行の既存の User32 スレッド モデルと同期されました。 その主な理由は相互運用性です。OLE 2.0、クリップボード、Internet Explorer などのシステムはすべて、単一スレッド アフィニティ (STA) の実行を必要とします。

STA スレッドを含むオブジェクトがある場合は、スレッド間で通信し、正しいスレッド上にあることを検証する方法が必要です。 ここにディスパッチャーの役割があります。 ディスパッチャーは、優先順位付けされたキューが複数ある基本的なメッセージ ディスパッチ システムです。 メッセージの例としては、生の入力通知 (マウス移動)、フレームワーク関数 (レイアウト)、ユーザー コマンド (このメソッドを実行) などがあります。 DispatcherObjectから派生すると、STA 動作を持つ CLR オブジェクトが作成され、作成時にディスパッチャーへのポインターが与えられます。

System.Windows.DependencyObject

WPF の構築に使用されるアーキテクチャの主要な哲学の 1 つは、メソッドまたはイベントよりもプロパティの優先でした。 プロパティは宣言型であり、アクションの代わりに意図をより簡単に指定できます。 また、ユーザー インターフェイスコンテンツを表示するためのモデル駆動型 (データ駆動型) システムもサポートされました。 この理念は、アプリケーションの動作をより適切に制御するために、バインドできるプロパティを増やすという意図された効果を持っていました。

プロパティによって駆動されるシステムの数を増やすには、CLR が提供するシステムよりも豊富なプロパティ システムが必要でした。 この豊かさの簡単な例として、変更通知があります。 双方向バインディングを有効にするには、変更通知をサポートするためにバインドの両側が必要です。 プロパティ値に関連付けられた動作を行うには、プロパティ値が変更されたときに通知を受け取る必要があります。 Microsoft .NET Framework には、INotifyPropertyChangeインターフェイスがあります。これにより、オブジェクトは変更通知を発行できますが、これは省略可能です。

WPF は、DependencyObject 型から派生した、より豊富なプロパティ システムを提供します。 プロパティ システムは、プロパティ式間の依存関係を追跡し、依存関係が変更されたときにプロパティ値を自動的に再検証するという点で、本当に "依存関係" プロパティ システムです。 たとえば、(FontSizeなど) 継承するプロパティがある場合、値を継承する要素の親でプロパティが変更されると、システムは自動的に更新されます。

WPF プロパティ システムの基礎は、プロパティ式の概念です。 WPF のこの最初のリリースでは、プロパティ式システムが閉じられ、式はすべてフレームワークの一部として提供されます。 式の存在によって、プロパティシステムにデータバインディングやスタイル設定、継承がハードコーディングされておらず、代わりにフレームワーク内の後のレイヤーによって提供されているのです。

プロパティ システムでは、プロパティ値のスパース ストレージも提供されます。 オブジェクトには数十個の (数百ではないにしても) プロパティを持つ可能性があり、ほとんどの値は既定の状態 (継承、スタイルによって設定など) であるため、オブジェクトのすべてのインスタンスで定義されているすべてのプロパティの重みを完全に持つ必要はありません。

プロパティ システムの最後の新機能は、添付プロパティの概念です。 WPF 要素は、コンポジションとコンポーネントの再利用の原則に基づいて構築されています。 多くの場合、要素を含む要素 (Grid レイアウト要素など) では、子要素の動作 (Row/Column 情報など) を制御するために追加のデータが必要になります。 これらのすべてのプロパティをすべての要素に関連付ける代わりに、任意のオブジェクトが他のオブジェクトのプロパティ定義を提供できます。 これは、JavaScript の "expando" 機能に似ています。

System.Windows.Media.Visual

システムが定義されている場合、次の手順では、画面に描画されるピクセルを取得します。 Visual クラスは、ビジュアル オブジェクトのツリーを構築するために提供されます。各オブジェクトには、必要に応じて描画命令と、それらの命令をレンダリングする方法 (クリッピング、変換など) に関するメタデータが含まれています。 Visual は非常に軽量で柔軟に設計されているため、ほとんどの機能はパブリック API の公開がなく、保護されたコールバック関数に大きく依存します。

Visual は、実際には WPF コンポジション システムへのエントリ ポイントです。 Visual は、これら 2 つのサブシステム (マネージド API とアンマネージド milcore) 間の接続ポイントです。

WPF は、milcore によって管理されるアンマネージ データ構造を走査することによってデータを表示します。 コンポジション ノードと呼ばれるこれらの構造は、各ノードのレンダリング命令を含む階層表示ツリーを表します。 次の図の右側に示されているこのツリーは、メッセージング プロトコルを介してのみアクセスできます。

WPF をプログラミングするときは、Visual 要素と派生型を作成します。この型は、このメッセージング プロトコルを介してコンポジション ツリーと内部的に通信します。 WPF の各 Visual は、1 つ、なし、または複数のコンポジション ノードを作成できます。

Windows Presentation Foundation ビジュアル ツリー Windows Presentation Foundation のビジュアル ツリー。

ここで注目すべき非常に重要なアーキテクチャの詳細があります。ビジュアルと描画命令のツリー全体がキャッシュされます。 グラフィックスの用語では、WPF は保持されたレンダリング システムを使用します。 これにより、構成システムがユーザー コードへのコールバックをブロックすることなく、高いリフレッシュ レートでシステムを再描画できます。 これは、応答しないアプリケーションの外観を防ぐのに役立ちます。

図であまり目立たないもう 1 つの重要な詳細は、システムが実際に構成を実行する方法です。

User32 および GDI では、システムは即時モードクリッピングシステムで動作します。 コンポーネントをレンダリングする必要がある場合、システムは、コンポーネントがピクセルに触れることが許可されていない領域境界を確立し、そのボックス内のピクセルを描画するように求められます。 このシステムは、メモリ制約のあるシステムで非常にうまく機能します。何らかの変更が発生した場合、影響を受けるコンポーネントに触れるだけで済むため、2 つのコンポーネントが 1 つのピクセルの色に影響を与えることはありません。

WPF では、"ペインターのアルゴリズム" 描画モデルが使用されます。 つまり、各コンポーネントをクリッピングするのではなく、各コンポーネントをディスプレイの背面から前面にレンダリングするように求められます。 これにより、各コンポーネントを前のコンポーネントのディスプレイ上に描画できます。 このモデルの利点は、複雑で部分的に透明な図形を作成できることです。 今日の最新のグラフィックス ハードウェアでは、このモデルは比較的高速です (User32/GDI が作成された場合はそうではありませんでした)。

前述のように、WPF の中核となる理念は、プログラミングのより宣言型の "プロパティ中心" モデルに移行することです。 ビジュアル システムでは、これはいくつかの興味深い場所に表示されます。

まず、リテインモードグラフィックシステムについて考えると、これは命令型のDrawLine/DrawLineといったモデルから、データ指向のnew Line()/new Line()といったモデルへ移行していることを意味します。 このデータ ドリブン レンダリングへの移動により、描画命令に対する複雑な操作をプロパティを使用して表現できます。 Drawing から派生する型は、実質的にレンダリング用のオブジェクト モデルです。

次に、アニメーション システムを評価すると、ほぼ完全に宣言型であることがわかります。 開発者が次の場所または次の色を計算する代わりに、アニメーションオブジェクトのプロパティのセットとしてアニメーションを表現できます。 これらのアニメーションは、開発者またはデザイナーの意図を表すことができます (このボタンをここから 5 秒で移動します)、システムはこれを実現する最も効率的な方法を決定できます。

System.Windows.UIElement

UIElement は、レイアウト、入力、イベントなどのコア サブシステムを定義します。

レイアウトは WPF の主要な概念です。 多くのシステムでは、固定レイアウト モデルのセット (HTML はレイアウト用に 3 つのモデルをサポートしています。フロー、絶対、テーブル) またはレイアウト用のモデルはありません (User32 は実際には絶対配置のみをサポートします)。 WPF は、開発者とデザイナーが、命令型ロジックではなくプロパティ値によって駆動される柔軟で拡張可能なレイアウト モデルを望んでいるという前提から始まりました。 UIElement レベルでは、レイアウトの基本コントラクトが導入されます。これは、MeasureArrange パスを含む 2 フェーズ モデルです。

Measure を使用すると、コンポーネントは必要なサイズを決定できます。 これは Arrange とは別のフェーズです。これは、親要素が子に対して、最適な位置とサイズを決定するために複数回測定するように求める状況が多いためです。 親要素が子要素に測定を求めるという事実は、WPF のもう 1 つの重要な理念である「コンテンツに基づいたサイズ調整」を示しています。 WPF のすべてのコントロールは、コンテンツの自然なサイズにサイズを設定する機能をサポートしています。 これにより、ローカライズがはるかに簡単になり、要素のサイズを変更する際に動的なレイアウトが可能になります。 Arrange フェーズでは、親が各子の最終的なサイズを配置して決定できます。

多くの場合、WPF の出力側 (Visual および関連オブジェクト) について話し合うのに多くの時間が費やされます。 しかし、入力側にも膨大なイノベーションがあります。 WPF の入力モデルの最も基本的な変更は、入力イベントがシステムを介してルーティングされる一貫したモデルである可能性があります。

入力はカーネル モードのデバイス ドライバーのシグナルとして生成され、Windows カーネルと User32 を含む複雑なプロセスを通じて正しいプロセスとスレッドにルーティングされます。 入力に対応する User32 メッセージが WPF にルーティングされると、WPF 生入力メッセージに変換され、ディスパッチャーに送信されます。 WPF を使用すると、生の入力イベントを複数の実際のイベントに変換できるため、"MouseEnter" などの機能を、配信が保証された低レベルのシステムで実装できます。

各入力イベントは、少なくとも 2 つのイベント ("プレビュー" イベントと実際のイベント) に変換されます。 WPF のすべてのイベントには、要素ツリーを介したルーティングという概念があります。 イベントがターゲットからツリーを上方にルートまで移動する場合は "バブル" と呼ばれ、ルートから開始してターゲットまで下方に移動する場合は "トンネル" と呼ばれます。 入力プレビュー イベント トンネル。ツリー内の任意の要素を有効にして、イベントに対してフィルター処理またはアクションを実行できます。 その場合、通常の (プレビューではない) イベントが、ターゲットからルートまでバブルされます。

トンネルとバブル フェーズの間でこの分割により、キーボード アクセラレータなどの機能の実装は、複合世界で一貫した方法で動作します。 User32 では、サポートするすべてのアクセラレータを含む 1 つのグローバル テーブルを持つことで、キーボード アクセラレータを実装します (Ctrl + N は "New" にマッピングします)。 アプリケーションのディスパッチャーで、TranslateAccelerator を呼び出します。これによって、User32 の入力メッセージがスニッフィングされ、登録済みのアクセラレータと一致したかどうかが判断されます。 WPF では、システムは完全に "構成可能" であるため、これは機能しません。どの要素でも、任意のキーボード アクセラレータを処理して使用できます。 この入力用の 2 フェーズ モデルを使用すると、コンポーネントは独自の "TranslateAccelerator" を実装できます。

これをさらに一歩進めるために、UIElement では CommandBindings の概念も紹介します。 WPF コマンド システムを使用すると、開発者はコマンド エンドポイント (ICommandを実装するもの) の観点から機能を定義できます。 コマンド バインドを使用すると、要素で入力ジェスチャ (Ctrl + N) とコマンド (新規) の間のマッピングを定義できます。 入力ジェスチャとコマンド定義はどちらも拡張可能であり、使用時に一緒にワイヤードすることができます。 これにより、たとえば、エンド ユーザーがアプリケーション内で使用するキー バインドをカスタマイズすることが簡単になります。

このトピックのこの時点では、WPF の "コア" 機能 (PresentationCore アセンブリに実装されている機能) が重点的に取り上げられます。 WPF を構築する場合、基本的な部分 (MeasureArrangeを使用したレイアウトのコントラクトなど) とフレームワーク部分 (Gridなどの特定のレイアウトの実装など) を完全に分離することが望ましい結果でした。 目標は、外部開発者が必要に応じて独自のフレームワークを作成できるようにする、スタック内の拡張性ポイントを低くすることでした。

System.Windows.FrameworkElement

FrameworkElement は、2 つの異なる方法で見ることができます。 ここでは、WPF の下位レイヤーで導入されたサブシステムに一連のポリシーとカスタマイズを導入します。 また、一連の新しいサブシステムも導入されています。

FrameworkElement によって導入される主なポリシーは、アプリケーションのレイアウトに関するポリシーです。 FrameworkElement は、UIElement によって導入された基本的なレイアウト コントラクトに基づいており、レイアウト作成者が一貫したプロパティ駆動型レイアウト セマンティクスのセットを持つのを容易にするレイアウト "スロット" の概念を追加します。 HorizontalAlignmentVerticalAlignmentMinWidth、および Margin などのプロパティは、FrameworkElement に由来するすべてのコンポーネントにレイアウト コンテナー内で一貫性のある動作を提供します。

FrameworkElement では、WPF のコア レイヤーにある多くの機能に対する API の公開も容易になります。 たとえば、FrameworkElement では、BeginStoryboard メソッドを使用してアニメーションに直接アクセスできます。 Storyboard は、一連のプロパティに対して複数のアニメーションをスクリプト化する方法を提供します。

FrameworkElement 導入される最も重要な 2 つの点は、データ バインディングとスタイルです。

WPF のデータ バインディング サブシステムは、アプリケーション ユーザー インターフェイス (UI) を作成するために Windows フォームまたは ASP.NET を使用したユーザーにとって比較的よく知られている必要があります。 これらの各システムでは、特定の要素の 1 つ以上のプロパティをデータにバインドする簡単な方法があります。 WPF では、プロパティ バインド、変換、およびリスト バインドが完全にサポートされています。

WPF のデータ バインディングの最も興味深い機能の 1 つは、データ テンプレートの導入です。 データ テンプレートを使用すると、データの視覚化方法を宣言によって指定できます。 データにバインドできるカスタム ユーザー インターフェイスを作成する代わりに、問題を回避し、作成される表示をデータに決定させることができます。

スタイル設定は、実際にはデータ バインディングの軽量な形式です。 スタイルを使用すると、共有定義から要素の 1 つ以上のインスタンスに一連のプロパティをバインドできます。 スタイルは、明示的な参照 (Style プロパティの設定) または要素の CLR 型にスタイルを関連付けることによって暗黙的に要素に適用されます。

System.Windows.Controls.Control

コントロールの最も重要な機能はテンプレートです。 WPF のコンポジション システムを保持モードのレンダリング システムと考えると、テンプレートを使用すると、コントロールはパラメーター化された宣言型の方法でレンダリングを記述できます。 ControlTemplate は、コントロールによって提供されるプロパティへのバインドを使用して、子要素のセットを作成するスクリプトに過ぎないのです。

Control は、ストック プロパティのセット (ForegroundBackgroundPaddingなど) を提供し、テンプレート作成者はこれを使ってコントロールの表示をカスタマイズできます。 コントロールの実装は、データ モデルと相互作用モデルを提供します。 対話モデルでは、一連のコマンド (ウィンドウの閉じるなど) と入力ジェスチャへのバインド (ウィンドウの右上隅にある赤い X のクリックなど) を定義します。 データ モデルには、対話モデルをカスタマイズするか、表示をカスタマイズするための一連のプロパティが用意されています (テンプレートによって決定されます)。

これにより、データ モデル (プロパティ)、相互作用モデル (コマンドとイベント)、表示モデル (テンプレート) が分割され、コントロールの外観と動作を完全にカスタマイズできます。

コントロールのデータ モデルの一般的な側面は、コンテンツ モデルです。 Buttonのようなコントロールを見ると、Object型の "Content" という名前のプロパティがあることがわかります。 Windows フォームと ASP.NET では、通常、このプロパティは文字列になりますが、ボタンに配置できるコンテンツの種類が制限されます。 ボタンのコンテンツには、単純な文字列、複雑なデータ オブジェクト、または要素ツリー全体を指定できます。 データ オブジェクトの場合、データ テンプレートを使用して表示を構築します。

概要

WPF は、動的なデータ ドリブンプレゼンテーション システムを作成できるように設計されています。 システムのすべての部分は、動作を駆動するプロパティ セットを使用してオブジェクトを作成するように設計されています。 データ バインディングはシステムの基本的な部分であり、すべてのレイヤーに統合されます。

従来のアプリケーションでは、ディスプレイが作成され、一部のデータにバインドされます。 WPF では、コントロールに関するすべての要素が、何らかの種類のデータ バインディングによって生成されます。 ボタン内で見つかったテキストは、ボタン内に構成されたコントロールを作成し、その表示をボタンのコンテンツ プロパティにバインドすることによって表示されます。

WPF ベースのアプリケーションの開発を開始すると、非常に使い慣れていると感じるはずです。 プロパティの設定、オブジェクトの使用、およびデータ バインドは、Windows フォームまたは ASP.NET を使用するのとほぼ同じ方法で行うことができます。 WPF のアーキテクチャについて詳しく調べれば、データを基本的にアプリケーションのコア ドライバーとして扱う、より豊富なアプリケーションを作成する可能性があることがわかります。

関連項目