コレクション型依存関係プロパティ
更新 : 2007 年 11 月
ここでは、プロパティの型がコレクション型である場合に依存関係プロパティを実装する方法についての、ガイダンスと推奨されるパターンを示します。
このトピックには次のセクションが含まれています。
- コレクション型依存関係プロパティの実装
- 既定値を上回るコレクションの初期化
- コレクション プロパティからのバインディング値変更の報告
- 関連トピック
コレクション型依存関係プロパティの実装
一般に、依存関係プロパティの場合、使用する実装パターンは、CLR プロパティ ラッパーを定義するものです。この場合、そのプロパティは、フィールドや他の構造ではなく DependencyProperty 識別子によってサポートされます。コレクション型プロパティを実装するときは、これと同じパターンに従います。ただし、コレクションに含まれる型それ自体が DependencyObject または Freezable の派生クラスである場合は常に、コレクション型プロパティを使用することでパターンは複雑になります。
既定値を上回るコレクションの初期化
依存関係プロパティを作成するときは、初期フィールド値としてプロパティの既定値を指定しません。代わりに、依存関係プロパティのメタデータを使用して既定値を指定します。プロパティが参照型の場合、依存関係プロパティのメタデータで指定する既定値はインスタンスごとの既定値ではありません。その型のすべてのインスタンスに適用される既定値です。したがって、コレクション プロパティ メタデータによって定義される単一の静的コレクションを、その型の新しく作成されるインスタンスの作業既定値として使用しないように注意してください。代わりに、クラス コンストラクタのロジックの一部として、コレクションの値に一意 (インスタンス) のコレクションを意図的に設定する必要があります。それ以外の場合は、意図しないシングルトン クラスを作成することになります。
次に例を示します。この例は、Aquarium クラスの定義を示しています。このクラスで定義されているコレクション型依存関係プロパティ AquariumObjects は、FrameworkElement 型の制約でジェネリック List<T> 型を使用します。依存関係プロパティに対する Register(String, Type, Type, PropertyMetadata) の呼び出しでは、メタデータが、既定値を新しいジェネリック List<T> に設定します。
public class Fish : FrameworkElement { }
public class Aquarium : DependencyObject
{
private static readonly DependencyPropertyKey AquariumContentsPropertyKey =
DependencyProperty.RegisterReadOnly(
"AquariumContents",
typeof(List<FrameworkElement>),
typeof(Aquarium),
new FrameworkPropertyMetadata(new List<FrameworkElement>())
);
public static readonly DependencyProperty AquariumContentsProperty =
AquariumContentsPropertyKey.DependencyProperty;
public List<FrameworkElement> AquariumContents
{
get { return (List<FrameworkElement>)GetValue(AquariumContentsProperty); }
...
}
ただし、コードをこのままにすると、単一のリストの既定値が Aquarium のすべてのインスタンスで共有されます。次のテスト コードは、2 つの異なる Aquarium インスタンスをインスタンス化し、それぞれに単一の異なる Fish を追加する方法を示していますが、これを実行すると、想定外の結果になります。
Aquarium myAq1 = new Aquarium();
Aquarium myAq2 = new Aquarium();
Fish f1 = new Fish();
Fish f2 = new Fish();
myAq1.AquariumContents.Add(f1);
myAq2.AquariumContents.Add(f2);
MessageBox.Show("aq1 contains " + myAq1.AquariumContents.Count.ToString() + " things");
MessageBox.Show("aq2 contains " + myAq2.AquariumContents.Count.ToString() + " things");
各コレクションのカウントが 1 になるのではなく、いずれのコレクションもカウントが 2 になります。これは、各 Aquarium は Fish を既定値のコレクションに追加しますが、その基になっているのはメタデータでの単一のコンストラクタ呼び出しであり、したがってすべてのインスタンス間で共有されるためです。これは望ましい状況ではありません。
この問題を回避するには、クラス コンストラクタの呼び出しの一部として、コレクションの依存関係プロパティの値を一意のインスタンスにリセットする必要があります。プロパティは読み取り専用の依存関係プロパティであるため、クラス内だけでアクセスできる DependencyPropertyKey を使用し、SetValue(DependencyPropertyKey, Object) メソッドを使用して設定します。
public Aquarium() : base()
{
SetValue(AquariumContentsPropertyKey, new List<FrameworkElement>());
}
再びテスト コードを実行すると、結果は期待したものに近くなり、各 Aquarium が独自の一意のコレクションをサポートします。
コレクション プロパティを読み取り/書き込みにすると、このパターンには若干のバリエーションが発生します。この場合は、コンストラクタからパブリック set アクセサを呼び出して初期化を行うことができ、パブリック DependencyProperty 識別子を使用して、set ラッパー内でキーなしのシグネチャの SetValue(DependencyProperty, Object) を呼び出します。
コレクション プロパティからのバインディング値変更の報告
それ自体が依存関係プロパティであるコレクション プロパティは、変更をサブプロパティに自動的には報告しません。コレクションにバインディングを作成している場合は、これによってバインディングが変更を報告しないことがあり、一部のデータ バインディング シナリオが無効になります。ただし、コレクション型として FreezableCollection<T> を使用している場合は、コレクションに含まれる要素に対するサブプロパティの変更は正しく報告され、バインディングは意図したとおりに動作します。
依存関係オブジェクト コレクションでサブプロパティのバインディングを有効にするには、FreezableCollection<T> 型としてコレクション プロパティを作成し、そのコレクションの型制約を DependencyObject 派生クラスに指定します。