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

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

このトピックは、次のセクションで構成されています。

  • 必要条件
  • 依存関係プロパティとは
  • 依存関係プロパティの例
  • 依存関係プロパティの実装が必要になるケース
  • 依存関係プロパティを定義する場合のチェックリスト
  • 読み取り専用の依存関係プロパティ
  • コレクション型依存関係プロパティ
  • 依存関係プロパティに関するセキュリティ上の考慮事項
  • 依存関係プロパティとクラス コンストラクター
  • 関連トピック

必要条件

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

依存関係プロパティとは

通常ならcommon language runtime (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 つ以上の WPF 機能がサポートされるようにするには、必ず依存関係プロパティとして実装する必要があります。

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

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

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

  • プロパティ値を要素ツリー内の親要素から自動的に継承する機能。 この場合、CLR アクセス用のプロパティ ラッパーを作成する場合でも、RegisterAttached メソッドで登録します。 詳細については、「プロパティ値の継承」を参照してください。

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

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

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

  • カスタム コントロールのプロパティが、[プロパティ] ウィンドウの編集など、Visual Studio 2008 WPF デザイナーのサポートを受ける機能。 詳細については、「コントロールの作成の概要」を参照してください。

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

依存関係プロパティを定義する場合のチェックリスト

依存関係プロパティの定義は、4 つの概念で構成されています。 これらの概念は、実装内では 1 行のコードで表されるものもあるため、必ずしも厳密な手順というわけではありません。

  • (オプション) 依存関係プロパティのプロパティ メタデータを作成します。

  • プロパティ システムにプロパティ名を登録します。その際、所有者型およびプロパティ値の型を指定します。 また、プロパティ メタデータを使用する場合は、併せて指定します。

  • 所有者型に DependencyProperty 識別子を public static readonly フィールドとして定義します。

  • 依存関係プロパティと同じ名前で、CLR の "ラッパー" プロパティを定義します。 CLR の "ラッパー" プロパティの get および set アクセサーを、基になる依存関係プロパティと連係するように実装します。

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

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

Public Shared ReadOnly AquariumGraphicProperty As DependencyProperty = DependencyProperty.Register("AquariumGraphic", GetType(Uri), GetType(AquariumObject), New FrameworkPropertyMetadata(Nothing, FrameworkPropertyMetadataOptions.AffectsRender, New PropertyChangedCallback(AddressOf OnUriChanged)))
public static readonly DependencyProperty AquariumGraphicProperty = DependencyProperty.Register(
  "AquariumGraphic",
  typeof(Uri),
  typeof(AquariumObject),
  new FrameworkPropertyMetadata(null,
      FrameworkPropertyMetadataOptions.AffectsRender, 
      new PropertyChangedCallback(OnUriChanged)
  )
);

依存関係プロパティの名前付け規則

依存関係プロパティに関しては確立された名前付け規則があり、例外的な状況を除いて必ずその規則に従う必要があります。

依存関係プロパティ自体の基本的な名前 (この例では "AquariumGraphic") を、Register の最初のパラメーターとして指定します。 その名前は、それぞれの登録型の中で一意である必要があります。 基本型を通じて継承される依存関係プロパティは、既に登録型の一部であると見なされ、継承されたプロパティの名前を再び登録することはできません。 ただし、その依存関係プロパティが継承されていない場合でも、依存関係プロパティの所有者として、クラスを追加する方法はあります。詳細については、「依存関係プロパティのメタデータ」を参照してください。

識別子フィールドを作成するときは、プロパティの登録名にサフィックス Property を加えた名前を付けます。 このフィールドは依存関係プロパティの識別子であり、ラッパー内で行う SetValueGetValue の呼び出しの入力として、また、独自のコード、許可した外部コード、プロパティ システム、および場合によっては XAML プロセッサによるプロパティへのその他のコード アクセスの入力として、後で使用されます。

メモメモ

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

"ラッパー" の実装

ラッパー実装では、get の実装で GetValue を呼び出し、set の実装で SetValue を呼び出します (ここでは、わかりやすくするために元の登録呼び出しとフィールドを示しています)。

例外的な状況を除いて、ラッパー実装は、GetValueSetValue の処理をそれぞれ実行します。 その理由については、「XAML 読み込みと依存関係プロパティ」を参照してください。

WPF のクラスに用意されている既存のパブリック依存関係プロパティは、この単純なラッパー実装モデルを使用します。依存関係プロパティのしくみの複雑さは、プロパティ システムの動作に内在するものか、プロパティ メタデータによる強制やプロパティ変更コールバックなどの他の概念に由来するものがほとんどです。


Public Shared ReadOnly AquariumGraphicProperty As DependencyProperty = DependencyProperty.Register("AquariumGraphic", GetType(Uri), GetType(AquariumObject), New FrameworkPropertyMetadata(Nothing, FrameworkPropertyMetadataOptions.AffectsRender, New PropertyChangedCallback(AddressOf OnUriChanged)))
Public Property AquariumGraphic() As Uri
    Get
        Return CType(GetValue(AquariumGraphicProperty), Uri)
    End Get
    Set(ByVal value As Uri)
        SetValue(AquariumGraphicProperty, value)
    End Set
End Property

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 の呼び出しの最初のパラメーターとして選択し、指定した名前と同じである必要があります。 この規則に従わないとプロパティが完全に使用できなくなるわけではありませんが、注意を要するいくつかの問題が発生します。

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

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

  • WPF XAML ローダーの現在の実装は、属性を処理するときにラッパーを完全にバイパスし、名前付け規則に依存します。 詳細については、「XAML 読み込みと依存関係プロパティ」を参照してください。

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

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

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

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

適切なメタデータ フラグの設定

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

参照

概念

依存関係プロパティの概要

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

コントロールの作成の概要

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

依存関係プロパティのセキュリティ

XAML 読み込みと依存関係プロパティ

DependencyObject の安全なコンストラクター パターン