カスタム依存関係プロパティ

ここでは、Windows Presentation Foundation (WPF) アプリケーションの開発者とコンポーネント作成者に対して、カスタム依存関係プロパティの作成を推奨する理由を説明し、その実装手順と共に、プロパティのパフォーマンス、操作性、または汎用性の向上につながるいくつかの実装オプションを示します。

このトピックには次のセクションが含まれています。

  • 必要条件
  • 依存関係プロパティとは
  • 依存関係プロパティの例
  • 依存関係プロパティの実装が必要になるケース
  • プロパティ システムへのプロパティの登録
  • "ラッパー" の実装
  • 新しい依存関係プロパティのプロパティ メタデータ
  • 読み取り専用の依存関係プロパティ
  • コレクション型依存関係プロパティ
  • 依存関係プロパティに関するセキュリティ上の考慮事項
  • 依存関係プロパティとクラス コンストラクタ
  • 関連トピック

必要条件

このトピックでは、ユーザーが WPF クラスの既存の依存プロパティの使用という観点から、依存プロパティを理解し、「依存関係プロパティの概要」トピックを通読していることを前提としています。このトピックの例を理解するには、Extensible Application Markup Language (XAML) についての理解があり、WPF アプリケーションの記述方法を理解していることも必要です。

依存関係プロパティとは

通常なら 共通言語ランタイム (CLR) プロパティになるプロパティを依存関係プロパティとして実装すると、そのプロパティでスタイル設定、データ バインディング、継承、アニメーション、および既定値をサポートできるようになります。依存関係プロパティは、Register メソッド (または RegisterReadOnly) を呼び出すことによって WPF プロパティ システムに登録され、DependencyProperty 識別子フィールドによって補足されるプロパティです。依存関係プロパティを使用できるのは DependencyObject 型のみですが、DependencyObject は WPF クラス階層の中で非常に高い位置にあるため、WPF で使用できるクラスの大多数は依存関係プロパティをサポートすることができます。依存関係プロパティと、この SDK で依存関係プロパティの説明に使用している一部の用語と規則の詳細については、「依存関係プロパティの概要」を参照してください。

依存関係プロパティの例

WPF のクラスに実装されている依存関係プロパティは多数あり、その例として Background プロパティ、Width プロパティ、Text プロパティなどが挙げられます。クラスによって公開される依存関係プロパティにはそれぞれ DependencyProperty 型の public static フィールドが対応付けられ、その同じクラスで公開されます。これは、依存関係プロパティの識別子です。この識別子には、名前付け規則に従って、依存関係プロパティの名前に文字列 Property を加えた名前が付けられます。たとえば、Background プロパティに対応する DependencyProperty 識別子フィールドの名前は BackgroundProperty になります。識別子には依存関係プロパティに関する登録情報が格納され、後に依存関係プロパティ関連のその他の操作 (SetValue の呼び出しなど) に使用されます。

依存関係プロパティの概要」で説明しているとおり、"ラッパー" の実装のため、WPF の依存関係プロパティ (添付プロパティを除く) はいずれも CLR プロパティを兼ねます。したがって、他の CLR プロパティを使用するときと同様にラッパーを定義する CLR アクセサをコードから呼び出すことによって、依存関係プロパティを取得または設定することができます。通常、確立された依存関係プロパティのコンシューマは、基になるプロパティ システムへのコネクション ポイントである DependencyObjectGetValue メソッドや SetValue メソッドは使用しません。CLR プロパティの既存の実装により、識別子フィールドが適切に使用され、プロパティの get ラッパーと set ラッパーの実装内で GetValueSetValue があらかじめ呼び出されるようになっています。カスタム依存関係プロパティを自分で実装する場合は、同様の方法でラッパーを定義することになります。

依存関係プロパティの実装が必要になるケース

プロパティをクラスに実装する場合、対象となるクラスが DependencyObject から派生したものであれば、そのプロパティを DependencyProperty インスタンスで補足することによって依存関係プロパティにするという選択肢があります。プロパティを依存関係プロパティにする必要性や妥当性の有無は、それぞれのシナリオのニーズによって異なります。プロパティをプライベート フィールドで補足する通常の手法で十分な場合もあります。ただし、プロパティで次のいずれか 1 つ以上の機能がサポートされるようにするには、必ず依存関係プロパティとして実装する必要があります。

  • プロパティをスタイル内で設定する機能。詳細については、「スタイルとテンプレート」を参照してください。

  • プロパティをデータ バインディングする機能。依存関係プロパティのデータ バインディングの詳細については、「方法 : 2 つのコントロールのプロパティをバインドする」を参照してください。

  • プロパティを動的リソース参照で設定する機能。詳細については、「リソースの概要」を参照してください。

  • 要素ツリー内の親要素から値を取得する機能。

  • プロパティをアニメーション化する機能。詳細については、「アニメーションの概要」を参照してください。

  • プロパティの以前の値がプロパティ システムのアクション、環境、ユーザー、またはスタイルの読み込みや使用によって変更された場合に、プロパティ システムから報告を受ける機能。メタデータ設定を使用することによって、プロパティでコールバック関数を指定し、プロパティ システムがプロパティ値の決定的な変更を確認するたびに、その関数が呼び出されるようにすることができます。関連する概念として、強制があります。詳細については、「依存関係プロパティのコールバックと検証」を参照してください。

  • プロパティ値の変更に伴ってレイアウト システムで要素のビジュアルの再構成が必要になるかどうかの報告を受けるなど、確立されたメタデータ規則を使用する機能。または、メタデータのオーバーライドを使用して、メタデータに基づいた既定値などの特性を派生クラスで変更する機能。

これらのシナリオを検討するときは、新しいプロパティを実装するのではなく、既存の依存関係プロパティのメタデータをオーバーライドすることによって、自分のシナリオを実現できるかどうかも考慮する必要があります。メタデータのオーバーライドが実際的かどうかはシナリオに依存し、そのシナリオが WPF の既存の依存関係プロパティおよび要素とどの程度似ているかにもよります。既存のプロパティのメタデータをオーバーライドする方法の詳細については、「依存関係プロパティのメタデータ」を参照してください。

プロパティ システムへのプロパティの登録

プロパティを依存関係プロパティにするには、そのプロパティをプロパティ システムによって保持されるテーブルに登録し、以降のプロパティ システム操作で修飾子として使用される一意の識別子を付与する必要があります。これらの操作は内部操作の場合もあれば、独自のコードによるプロパティ システム API の呼び出しの場合もあります。プロパティを登録するには、クラスの本文 (クラス内のメンバ定義以外の部分) で Register メソッドを呼び出します。Register メソッドを呼び出すと、その戻り値として識別子フィールドも提供されます。Register の呼び出しを他のメンバ定義の外で行うのは、この戻り値を使用して、DependencyProperty 型の public static 読み取り専用フィールドをクラスの一部として割り当てて作成するためです。このフィールドは、依存関係プロパティの識別子になります。

public static readonly DependencyProperty AquariumGraphicProperty = DependencyProperty.Register(
  "AquariumGraphic",
  typeof(Uri),
  typeof(AquariumObject),
  new FrameworkPropertyMetadata(null,
      FrameworkPropertyMetadataOptions.AffectsRender, 
      new PropertyChangedCallback(OnUriChanged)
  )
);

依存関係プロパティに関しては確立された名前付け規則がいくつかあり、例外的な状況を除いて必ずその規則に従う必要があります。プロパティ自体の基本的な名前 (この例では "AquariumGraphic") を、Register の最初のパラメータとして指定します。その名前は、それぞれの登録型の中で一意である必要があります。基本型を通じて継承されたプロパティは、既に登録型の一部になっているものと見なされます。継承されたプロパティの名前を再登録することはできません (ただし、該当する依存関係プロパティが継承されていなくても、その所有者としてクラスを追加する手法があります。詳細については、「依存関係プロパティのメタデータ」を参照してください)。識別子フィールドを作成するときは、プロパティの登録名にサフィックス Property を加えた名前を付けます。繰り返しますが、このフィールドは依存関係プロパティの識別子であり、ラッパー内で行う SetValueGetValue の呼び出しの入力として、また、独自のコード、許可した外部コード、およびプロパティ システムによるプロパティへのその他のコード アクセスの入力として、後で使用されます。

メモメモ :

通常の実装ではクラスの本文で依存関係プロパティを定義しますが、クラスの静的コンストラクタで依存関係プロパティを定義することも可能です。依存関係プロパティを初期化するために複数のコード行が必要な場合は、この方法が理にかなっています。

"ラッパー" の実装

通常のラッパー実装では、get の実装で GetValue を呼び出し、set の実装で SetValue を呼び出します (ここでは、わかりやすくするために元の登録呼び出しとフィールドを示しています)。WPF のクラスに用意されている既存のパブリック依存関係プロパティは、この単純なラッパー実装モデルを使用します。依存関係プロパティのしくみの複雑さは、プロパティ システムの動作に内在するものか、プロパティ メタデータによる強制やプロパティ変更コールバックなどの他の概念に由来するものがほとんどです。

public static readonly DependencyProperty AquariumGraphicProperty = DependencyProperty.Register(
  "AquariumGraphic",
  typeof(Uri),
  typeof(AquariumObject),
  new FrameworkPropertyMetadata(null,
      FrameworkPropertyMetadataOptions.AffectsRender, 
      new PropertyChangedCallback(OnUriChanged)
  )
);
public Uri AquariumGraphic
{
  get { return (Uri)GetValue(AquariumGraphicProperty); }
  set { SetValue(AquariumGraphicProperty, value); }
}

繰り返しますが、ラッパー プロパティの名前は通常、プロパティを登録する際に Register の呼び出しの最初のパラメータとして選択し、指定した名前と同じである必要があります。この規則に従わないとプロパティが完全に使えなくなるわけではありませんが、2 つの注意すべき問題が発生します。

  • スタイルとテンプレートの一部が機能しません。

  • ほとんどのツールとデザイナでは、名前付け規則への準拠が XAML の適切なシリアル化やプロパティ単位のデザイナ環境支援の条件となっています。

新しい依存関係プロパティのプロパティ メタデータ

依存関係プロパティを登録すると、プロパティ システムを通じた登録によって、プロパティ特性を格納するメタデータ オブジェクトが作成されます。これらの特性の多くは、プロパティが Register の簡易署名で登録された場合に設定される既定値を持っています。Register のその他の署名を使用すると、必要なメタデータをプロパティの登録時に指定することができます。依存関係プロパティに対して指定する最も一般的なメタデータは、そのプロパティを使用する新しいインスタンスに適用する既定値を指定します。

FrameworkElement の派生クラスに存在する依存関係プロパティを作成する場合は、基本クラス PropertyMetadata ではなく、より特化したメタデータ クラス FrameworkPropertyMetadata を使用できます。FrameworkPropertyMetadata クラスのコンストラクタには、さまざまなメタデータ特性を組み合わせて指定できる署名がいくつか存在します。既定値のみを指定する場合は、Object 型の単一のパラメータを受け取る署名を使用します。そのオブジェクト パラメータをプロパティの型固有の既定値として渡します (指定する既定値の型は、Register の呼び出しで propertyType パラメータとして指定した型である必要があります)。

FrameworkPropertyMetadata の場合は、プロパティのメタデータ オプション フラグを指定することもできます。これらのフラグは、特定の条件をレイアウト エンジンなどの他のプロセスに伝達するために使用されます。

  • プロパティ (およびその値の変更) がユーザー インターフェイス (UI) に影響を及ぼす場合、特に、レイアウト システムが要素をページ内にレイアウトする際のサイズ設定と描画に影響する場合は、AffectsMeasureAffectsArrangeAffectsRender のいずれか 1 つ以上のフラグを設定します。

    • AffectsMeasure は、このプロパティの変更に伴って UI レンダリングの変更が必要になり、格納オブジェクトが親の内部に必要とする領域が増減する可能性があることを示します。たとえば、"Width" プロパティにはこのフラグを設定する必要があります。

    • AffectsArrange は、このプロパティの変更に伴って UI レンダリングの変更が必要になり、その変更が、通常は専用領域の変更は必要としないものの、領域内の配置変更を示唆することを示します。たとえば、"Alignment" プロパティにはこのフラグを設定する必要があります。

    • AffectsRender は、その他の何らかの変更が発生しており、レイアウトと測定値には影響しないものの、新たに描画する必要があることを示します。例としては、既存の要素の色を変更する "Background" などのプロパティが挙げられます。

    • 多くの場合、これらのフラグは、独自に行うプロパティ システムのオーバーライド実装やレイアウト コールバックのメタデータでプロトコルとして使用されます。OnPropertyChanged コールバックを例にとると、インスタンスのいずれかのプロパティで値の変更が報告され、そのメタデータで AffectsArrangetrue になっていることを条件として、InvalidateArrange を呼び出すことができます。

  • 一部のプロパティは、それが含まれている親要素のレンダリング特性に対し、前述した必要サイズの変更以上の影響を及ぼす可能性があります。その例としては、フロー ドキュメント モデルで使用される MinOrphanLines プロパティが挙げられます。このプロパティが変更されると、該当する段落を含むフロー ドキュメントの全体的なレンダリングに変更が生じる可能性があります。独自のプロパティで同様のケースを識別するには、AffectsParentArrange または AffectsParentMeasure を使用します。

  • 既定では、依存関係プロパティはデータ バインディングをサポートします。データ バインディングの現実的なシナリオがない場合や、大きなオブジェクトに対するデータ バインディングのパフォーマンスが問題として認識されている場合には、データ バインディングを意図的に無効にすることができます。

  • 既定では、依存関係プロパティのデータ バインディング Mode の既定値は OneWay になっています。バインディングは、バインディング インスタンスごとにいつでも TwoWay に変更できます。詳細については、「方法 : バインディングの方向を指定する」を参照してください。ただし、依存関係プロパティの作成者の判断で、プロパティの既定のバインディング モードを TwoWay にすることもできます。既存の依存関係プロパティに見られる例としては、次のようなものがあります。このプロパティについては、IsSubmenuOpen 設定ロジックと MenuItem の複合が既定のテーマ スタイルと対話するシナリオが想定されています。IsSubmenuOpen プロパティ ロジックは、データ バインディングをネイティブに使用し、他の状態プロパティおよびメソッド呼び出しに応じてプロパティの状態を保持します。既定で TwoWay のバインディングを行うプロパティのもう 1 つの例は TextBox.Text です。

  • Inherits フラグを設定することによって、カスタム依存関係プロパティでプロパティの継承を有効にすることもできます。プロパティの継承は、親要素と子要素に共通のプロパティがあるシナリオで役立ちます。子要素がその特定のプロパティ値を親と同じ値に設定することは理にかなっています。継承可能なプロパティの例としては、DataContext が挙げられます。このプロパティは、データの表示に関する重要なマスタ詳細シナリオを実現するためのバインディング操作に使用されます。DataContext を継承可能にすると、そのデータ コンテキストもすべての子要素に継承されます。プロパティ値の継承により、ページまたはアプリケーションのルートでデータ コンテキストを指定すれば、あらゆる子要素内のバインディングについて改めて指定しなくても済むようになります。DataContext は、継承によって既定値がオーバーライドされるものの、特定の子要素で常にローカルに設定できることを示す例としても適しています。詳細については、「方法 : 階層データでマスタ詳細パターンを使用する」を参照してください。プロパティ値の継承はパフォーマンスの低下につながる可能性があるため、多用は避けてください。詳細については、「プロパティ値の継承」を参照してください。

  • 依存関係プロパティがナビゲーション履歴サービスで検出または使用されるようにするかどうかを示すには、Journal フラグを設定します。例としては、SelectedIndex プロパティが挙げられます。選択コントロールで選択された項目は、ジャーナル履歴のナビゲーション時に保持する必要があります。

読み取り専用の依存関係プロパティ

読み取り専用の依存関係プロパティを定義することができます。ただし、プロパティを読み取り専用として定義する理由に関するシナリオは、これらをプロパティ システムに登録する手順同様に、少し異なります。詳細については、「読み取り専用の依存関係プロパティ」を参照してください。

コレクション型依存関係プロパティ

コレクション型依存関係プロパティには、別途考慮する必要のある実装上の問題がいくつかあります。詳細については、「コレクション型依存関係プロパティ」を参照してください。

依存関係プロパティに関するセキュリティ上の考慮事項

依存関係プロパティは、パブリック プロパティとして宣言する必要があります。依存関係プロパティの識別子フィールドは、public static フィールドとして宣言する必要があります。他のアクセス レベル (保護など) を宣言しようとしても、依存関係プロパティには、識別子とプロパティ システム API の組み合わせを使用して常にアクセスできます。LocalValueEnumerator など、プロパティ システムに組み込まれているメタデータ報告や値決定の API により、保護された識別子フィールドにもアクセスできる可能性があります。詳細については、「依存関係プロパティのセキュリティ」を参照してください。

依存関係プロパティとクラス コンストラクタ

マネージ コード プログラミングでは、(通例 FXCop などのコード分析ツールによって適用される) 一般的な方針として、クラス コンストラクタで仮想メソッドを呼び出すことは避ける必要があります。これは、コンストラクタは派生クラスのコンストラクタの基本の初期化として呼び出すことができ、コンストラクタを通じた仮想メソッドの入力は、構築中のオブジェクト インスタンスの初期化が不完全な状態で実行される可能性があるためです。DependencyObject の派生クラスからさらに派生させる場合は、仮想メソッドの呼び出しと公開をプロパティ システム自体が内部的に行う点に注意が必要です。これらの仮想メソッドは、WPF プロパティ システムのサービスの一部です。メソッドをオーバーライドすると、派生クラスが値の決定に参加できるようになります。ランタイムの初期化で問題が発生するのを避けるため、特定のコンストラクタ パターンに従わない限り、依存関係プロパティの値はクラスのコンストラクタ内で定義しないでください。詳細については、「DependencyObjects の安全なコンストラクタ パターン」を参照してください。

参照

概念

依存関係プロパティの概要
依存関係プロパティのメタデータ
コントロールの作成の概要
コレクション型依存関係プロパティ
依存関係プロパティのセキュリティ
DependencyObjects の安全なコンストラクタ パターン