Windows Presentation Foundation (WPF) アプリケーション開発者とコンポーネント作成者は、カスタム依存関係プロパティを作成して、プロパティの機能を拡張できます。 共通言語ランタイム (CLR) プロパティとは異なり、依存関係プロパティでは、スタイル設定、データ バインディング、継承、アニメーション、既定値のサポートが追加されます。 Background、 Width、および Text は、WPF クラスの既存の依存関係プロパティの例です。 この記事では、カスタム依存関係プロパティを実装する方法について説明し、パフォーマンス、使いやすさ、汎用性を向上させるオプションについて説明します。
[前提条件]
この記事では、依存関係プロパティの基本的な知識と、依存関係プロパティの概要
依存関係プロパティ識別子
依存関係プロパティは、 Register または RegisterReadOnly 呼び出しによって WPF プロパティ システムに登録されるプロパティです。
Register
メソッドは、依存関係プロパティの登録済みの名前と特性を保持するDependencyProperty インスタンスを返します。
DependencyProperty
インスタンスを、依存関係プロパティ識別子と呼ばれる静的な読み取り専用フィールドに割り当てます。これは、規則によって <property name>Property
という名前になります。 たとえば、 Background プロパティの識別子フィールドは常に BackgroundProperty。
依存関係プロパティ識別子は、プロパティをプライベート フィールドでバッキングする標準的なパターンではなく、プロパティ値を取得または設定するためのバッキング フィールドとして使用されます。 プロパティ システムでは識別子が使用されるだけでなく、XAML プロセッサで識別子が使用される可能性があり、コード (および場合によっては外部コード) は識別子を介して依存関係プロパティにアクセスできます。
依存関係プロパティは、 DependencyObject 型から派生したクラスにのみ適用できます。 は WPF クラス階層のルートに近いため、ほとんどの WPF クラスは依存関係プロパティをサポートしています。 依存関係プロパティの詳細、および依存関係プロパティの説明に使用される用語と規則については、「 依存関係プロパティの概要」を参照してください。
依存プロパティのラッパー
プロパティがアタッチされていない WPF 依存関係プロパティは、 get
アクセサーと set
アクセサーを実装する CLR ラッパーによって公開されます。 プロパティ ラッパーを使用すると、依存関係プロパティのコンシューマーは、他の CLR プロパティと同様に、依存関係プロパティの値を取得または設定できます。
get
アクセサーとset
アクセサーは、DependencyObject.GetValue呼び出しとDependencyObject.SetValue呼び出しを通じて基になるプロパティ システムと対話し、依存関係プロパティ識別子をパラメーターとして渡します。 依存関係プロパティのコンシューマーは、通常、 GetValue
や SetValue
を直接呼び出しませんが、カスタム依存関係プロパティを実装する場合は、ラッパーでこれらのメソッドを使用します。
依存関係プロパティを実装するタイミング
DependencyObjectから派生したクラスにプロパティを実装する場合は、DependencyProperty識別子を使用してプロパティをバッキングして依存関係プロパティにします。 依存関係プロパティを作成することが有益かどうかは、シナリオによって異なります。 プライベート フィールドを使用してプロパティをバッキングすることは一部のシナリオでは十分ですが、プロパティで次の WPF 機能の 1 つ以上をサポートする場合は、依存関係プロパティの実装を検討してください。
スタイル内で設定できるプロパティ。 詳細については、「 スタイルとテンプレート」を参照してください。
データ バインディングをサポートするプロパティ。 データ バインディングの依存関係プロパティの詳細については、「2 つのコントロールのプロパティをバインドする」を参照してください。
動的リソース参照によって設定できるプロパティ。 詳細については、「 XAML リソース」を参照してください。
要素ツリー内の親要素から値を自動的に継承するプロパティ。 このためには、CLR アクセス用のプロパティ ラッパーも作成した場合でも、 RegisterAttachedを使用して登録する必要があります。 詳細については、「プロパティ値の継承
」を参照してください。 アニメーション化可能なプロパティ。 詳細については、「アニメーションの概要」を参照してください。
プロパティ値が変更されたときの WPF プロパティ システムによる通知。 変更は、プロパティ システム、環境、ユーザー、またはスタイルによるアクションが原因である可能性があります。 プロパティは、プロパティの値が変更されたとプロパティ システムが判断するたびに呼び出される、プロパティ メタデータ内のコールバック メソッドを指定できます。 関連する概念は、プロパティ値強制型変換です。 詳細については、「 依存関係プロパティのコールバックと検証」を参照してください。
依存関係プロパティ メタデータへのアクセス。WPF プロセスによって読み取られます。 たとえば、プロパティ メタデータを使用して次のことができます。
変更された依存関係プロパティの値で、レイアウト システムが要素のビジュアルを再計算するかどうかを指定します。
派生クラスのメタデータをオーバーライドして、依存関係プロパティの既定値を設定します。
Visual Studio WPF デザイナーでは、[ プロパティ ] ウィンドウでカスタム コントロールのプロパティを編集するなど、サポートされます。 詳細については、「コントロールの作成の概要」を参照してください。
一部のシナリオでは、新しい依存関係プロパティを実装するよりも、既存の依存関係プロパティのメタデータをオーバーライドすることをお勧めします。 メタデータのオーバーライドが実際的かどうかは、シナリオによって異なります。また、そのシナリオが既存の WPF 依存関係プロパティとクラスの実装にどの程度近いかによって異なります。 既存の依存関係プロパティのメタデータのオーバーライドの詳細については、「 依存関係プロパティのメタデータ」を参照してください。
依存関係プロパティを作成するためのチェックリスト
依存関係プロパティを作成するには、次の手順に従います。 一部の手順は、1 行のコードで組み合わせて実装できます。
(省略可能)依存関係プロパティのメタデータを作成します。
プロパティ名、所有者の型、プロパティ値の型、および必要に応じてプロパティ メタデータを指定して、依存関係プロパティをプロパティ システムに登録します。
所有者型のDependencyProperty フィールドとして
public static readonly
識別子を定義します。 識別子フィールド名は、プロパティ名にサフィックスProperty
を付加したものです。依存関係プロパティ名と同じ名前の CLR ラッパー プロパティを定義します。 CLR ラッパーで、ラッパーをバックする依存関係プロパティに接続する
get
アクセサーとset
アクセサーを実装します。
プロパティの登録
プロパティを依存関係プロパティにするには、プロパティ システムに登録する必要があります。 プロパティを登録するには、クラスの本体内から、メンバー定義の外部から Register メソッドを呼び出します。
Register
メソッドは、プロパティ システム API を呼び出すときに使用する一意の依存関係プロパティ識別子を返します。
Register
呼び出しがメンバー定義の外部で行われるのは、戻り値をpublic static readonly
型のDependencyPropertyフィールドに割り当てたためです。 クラスで作成するこのフィールドは、依存関係プロパティの識別子です。 次の例では、 Register
の最初の引数で依存関係プロパティに AquariumGraphic
名前を付けます。
// Register a dependency property with the specified property name,
// property type, owner type, and property metadata. Store the dependency
// property identifier as a public static readonly member of the class.
public static readonly DependencyProperty AquariumGraphicProperty =
DependencyProperty.Register(
name: "AquariumGraphic",
propertyType: typeof(Uri),
ownerType: typeof(Aquarium),
typeMetadata: new FrameworkPropertyMetadata(
defaultValue: new Uri("http://www.contoso.com/aquarium-graphic.jpg"),
flags: FrameworkPropertyMetadataOptions.AffectsRender,
propertyChangedCallback: new PropertyChangedCallback(OnUriChanged))
);
' Register a dependency property with the specified property name,
' property type, owner type, and property metadata. Store the dependency
' property identifier as a public static readonly member of the class.
Public Shared ReadOnly AquariumGraphicProperty As DependencyProperty =
DependencyProperty.Register(
name:="AquariumGraphic",
propertyType:=GetType(Uri),
ownerType:=GetType(Aquarium),
typeMetadata:=New FrameworkPropertyMetadata(
defaultValue:=New Uri("http://www.contoso.com/aquarium-graphic.jpg"),
flags:=FrameworkPropertyMetadataOptions.AffectsRender,
propertyChangedCallback:=New PropertyChangedCallback(AddressOf OnUriChanged)))
注
クラス本体で依存関係プロパティを定義することは一般的な実装ですが、クラスの静的コンストラクターで依存関係プロパティを定義することもできます。 依存関係プロパティを初期化するために複数のコード行が必要な場合は、この方法が適しています。
依存関係プロパティの名前付け
プロパティ システムの通常の動作では、依存関係プロパティの確立された名前付け規則が必須です。 作成する識別子フィールドの名前は、サフィックスが Property
プロパティの登録済み名前である必要があります。
依存関係プロパティ名は、登録クラス内で一意である必要があります。 基本型を通じて継承される依存関係プロパティは既に登録されており、派生型では登録できません。 ただし、依存関係プロパティの所有者としてクラスを追加することで、クラスが継承していない型であっても、別の型によって登録された依存関係プロパティを使用できます。 所有者としてクラスを追加する方法の詳細については、「 依存関係プロパティのメタデータ」を参照してください。
プロパティ ラッパーの実装
規則により、ラッパー プロパティの名前は、依存関係プロパティ名である Register 呼び出しの最初のパラメーターと同じである必要があります。 ラッパー実装では、GetValue アクセサーでget
を呼び出し、SetValue アクセサーでset
します (読み取り/書き込みプロパティの場合)。 次の例は、登録呼び出しと識別子フィールド宣言に続くラッパーを示しています。 WPF クラスのすべてのパブリック依存関係プロパティでは、同様のラッパー モデルが使用されます。
// Register a dependency property with the specified property name,
// property type, owner type, and property metadata. Store the dependency
// property identifier as a public static readonly member of the class.
public static readonly DependencyProperty AquariumGraphicProperty =
DependencyProperty.Register(
name: "AquariumGraphic",
propertyType: typeof(Uri),
ownerType: typeof(Aquarium),
typeMetadata: new FrameworkPropertyMetadata(
defaultValue: new Uri("http://www.contoso.com/aquarium-graphic.jpg"),
flags: FrameworkPropertyMetadataOptions.AffectsRender,
propertyChangedCallback: new PropertyChangedCallback(OnUriChanged))
);
// Declare a read-write property wrapper.
public Uri AquariumGraphic
{
get => (Uri)GetValue(AquariumGraphicProperty);
set => SetValue(AquariumGraphicProperty, value);
}
' Register a dependency property with the specified property name,
' property type, owner type, and property metadata. Store the dependency
' property identifier as a public static readonly member of the class.
Public Shared ReadOnly AquariumGraphicProperty As DependencyProperty =
DependencyProperty.Register(
name:="AquariumGraphic",
propertyType:=GetType(Uri),
ownerType:=GetType(Aquarium),
typeMetadata:=New FrameworkPropertyMetadata(
defaultValue:=New Uri("http://www.contoso.com/aquarium-graphic.jpg"),
flags:=FrameworkPropertyMetadataOptions.AffectsRender,
propertyChangedCallback:=New PropertyChangedCallback(AddressOf OnUriChanged)))
' Declare a read-write property wrapper.
Public Property AquariumGraphic As Uri
Get
Return CType(GetValue(AquariumGraphicProperty), Uri)
End Get
Set
SetValue(AquariumGraphicProperty, Value)
End Set
End Property
まれなケースを除き、ラッパー実装には GetValue と SetValue コードのみを含める必要があります。 その理由については、「 カスタム依存関係プロパティの影響」を参照してください。
プロパティが確立された名前付け規則に従っていない場合は、次の問題が発生する可能性があります。
スタイルとテンプレートの一部の側面は機能しません。
ほとんどのツールとデザイナーは、XAML を適切にシリアル化し、プロパティごとのレベルでデザイナー環境の支援を提供するために名前付け規則に依存しています。
WPF XAML ローダーの現在の実装では、ラッパーが完全にバイパスされ、属性値を処理するために名前付け規則に依存しています。 詳細については、「 XAML の読み込みと依存関係のプロパティ」を参照してください。
依存関係プロパティのメタデータ
依存関係プロパティを登録すると、プロパティ システムによって、プロパティの特性を格納するメタデータ オブジェクトが作成されます。 Register メソッドのオーバーロードを使用すると、登録時にプロパティ メタデータを指定できます (たとえば、Register(String, Type, Type, PropertyMetadata))。 プロパティ メタデータの一般的な用途は、依存関係プロパティを使用する新しいインスタンスにカスタムの既定値を適用することです。 プロパティ メタデータを指定しない場合、プロパティ システムは多くの依存関係プロパティ特性に既定値を割り当てます。
FrameworkElementから派生したクラスに依存関係プロパティを作成する場合は、基底クラスのFrameworkPropertyMetadataではなく、より特殊なメタデータ クラスPropertyMetadataを使用できます。 いくつかの FrameworkPropertyMetadata コンストラクターシグネチャを使用すると、メタデータ特性のさまざまな組み合わせを指定できます。 既定値を指定するだけの場合は、 FrameworkPropertyMetadata(Object) を使用し、既定値を Object
パラメーターに渡します。 値の型が、propertyType
呼び出しで指定されたRegister
と一致していることを確認します。
一部の FrameworkPropertyMetadata オーバーロードでは、プロパティの メタデータ オプション フラグ を指定できます。 プロパティ システムは、これらのフラグを個別のプロパティに変換し、レイアウト エンジンなどの WPF プロセスによってフラグ値が使用されます。
メタデータ フラグの設定
メタデータ フラグを設定するときは、次の点を考慮してください。
プロパティ値 (またはそれに対する変更) がレイアウト システムによる UI 要素のレンダリング方法に影響する場合は、次のフラグを 1 つ以上設定します。
AffectsMeasureは、プロパティ値の変更に UI レンダリングの変更が必要であることを示します。具体的には、その親内のオブジェクトによって占有される領域です。 たとえば、
Width
プロパティに対してこのメタデータ フラグを設定します。AffectsArrangeは、プロパティ値の変更に UI レンダリングの変更が必要であることを示します。具体的には、その親内でのオブジェクトの位置です。 通常、オブジェクトのサイズも変更されません。 たとえば、
Alignment
プロパティに対してこのメタデータ フラグを設定します。AffectsRenderは、レイアウトとメジャーには影響しないが、別のレンダリングが必要な変更が発生したことを示します。 たとえば、
Background
プロパティ、または要素の色に影響を与えるその他のプロパティに対して、このフラグを設定します。
これらのフラグは、プロパティ システム (またはレイアウト) コールバックのオーバーライド実装への入力としても使用できます。 たとえば、インスタンスのプロパティが値の変化を報告し、メタデータにOnPropertyChangedが設定されている場合に、InvalidateArrangeコールバックを使用してAffectsArrangeを呼び出すことができます。
一部のプロパティは、他の方法で親要素のレンダリング特性に影響します。 たとえば、 MinOrphanLines プロパティを変更すると、フロー ドキュメントの全体的なレンダリングが変更される可能性があります。 AffectsParentArrangeまたはAffectsParentMeasureを使用して、独自のプロパティで親アクションを通知します。
既定では、依存関係プロパティはデータ バインディングをサポートします。 ただし、 IsDataBindingAllowed を使用して、現実的なシナリオがない場合や、大規模なオブジェクトなど、データ バインディングのパフォーマンスに問題がある場合は、データ バインディングを無効にすることができます。
依存関係プロパティの既定のデータ バインディング モード は OneWayされていますが、特定のバインドのバインド モードを TwoWayに変更できます。 詳細については、バインド方向を参照してください。 依存関係プロパティの作成者は、双方向バインディングを既定のモードにすることもできます。 双方向データ バインディングを使用する既存の依存関係プロパティの例として、他のプロパティとメソッド呼び出しに基づく状態を持つ MenuItem.IsSubmenuOpenがあります。
IsSubmenuOpen
のシナリオは、その設定ロジックとMenuItemの合成が既定のテーマ スタイルと対話することです。 TextBox.Text は、既定で双方向バインディングを使用する別の WPF 依存関係プロパティです。依存関係プロパティのプロパティの継承を有効にするには、 Inherits フラグを設定します。 プロパティの継承は、親要素と子要素に共通のプロパティがあり、子要素が共通プロパティの親値を継承するのが理にかなっているシナリオに役立ちます。 継承可能なプロパティの例として、DataContextを使用するバインド操作をサポートするがあります。
Journal フラグを設定して、依存関係プロパティを検出するか、ナビゲーション ジャーナリング サービスで使用する必要があることを示します。 たとえば、 SelectedIndex プロパティは、
Journal
フラグを設定して、アプリケーションが選択した項目の履歴を履歴に保持することを推奨します。
読み取り専用の依存関係プロパティ
読み取り専用の依存関係プロパティを定義できます。 一般的なシナリオは、内部状態を格納する依存関係プロパティです。 たとえば、 IsMouseOver の状態はマウス入力によってのみ決定されるため、読み取り専用です。 詳細については、「 読み取り専用の依存関係プロパティ」を参照してください。
コレクション型の依存関係プロパティ
コレクション型の依存関係プロパティには、参照型の既定値の設定やコレクション要素のデータ バインディングのサポートなど、考慮すべき追加の実装の問題があります。 詳細については、「 コレクション型の依存関係プロパティ」を参照してください。
依存関係プロパティのセキュリティ
通常は、依存関係プロパティをDependencyPropertyフィールドとして宣言し、識別子フィールドをpublic static readonly
フィールドとしてパブリックにします。
protected
など、より制限の厳しいアクセス レベルを指定した場合でも、依存関係プロパティは、プロパティ システム API と組み合わせて識別子を介してアクセスできます。 保護された識別子フィールドでも、WPF メタデータ レポートや値決定 API ( LocalValueEnumerator など) を介してアクセスできる可能性があります。 詳細については、「 依存関係プロパティのセキュリティ」を参照してください。
読み取り専用の依存関係プロパティの場合、RegisterReadOnlyから返される値はDependencyPropertyKeyされ、通常はDependencyPropertyKey
をクラスのpublic
メンバーにしません。 WPF プロパティ システムはコードの外部に DependencyPropertyKey
を伝達しないため、読み取り専用の依存関係プロパティは、読み取り/書き込み依存関係プロパティよりも set
セキュリティが優れています。
依存関係プロパティとクラス コンストラクター
マネージド コード プログラミングには一般的な原則があり、多くの場合、コード分析ツールによって適用されます。クラス コンストラクターは仮想メソッドを呼び出すべきではありません。 これは、派生クラス コンストラクターの初期化中に基本コンストラクターを呼び出すことができます。また、基底コンストラクターによって呼び出された仮想メソッドが、派生クラスの初期化を完了する前に実行される可能性があるためです。 DependencyObjectから既に派生しているクラスから派生すると、プロパティ システム自体が仮想メソッドを内部的に呼び出して公開します。 これらの仮想メソッドは、WPF プロパティ システム サービスの一部です。 メソッドをオーバーライドすると、派生クラスが値決定に参加できるようになります。 ランタイム初期化に関する潜在的な問題を回避するには、特定のコンストラクター パターンに従わない限り、クラスのコンストラクター内で依存関係プロパティの値を設定しないでください。 詳細については、「 DependencyObjects の安全なコンストラクター パターン」を参照してください。
こちらも参照ください
.NET Desktop feedback