コレクション型の依存関係プロパティ (WPF .NET)

この記事では、コレクション型である依存関係プロパティを実装するためのガイダンスと推奨されるパターンを示します。

重要

.NET 7 と .NET 6 用のデスクトップ ガイド ドキュメントは作成中です。

必須コンポーネント

この記事では、依存関係プロパティの基本的な知識と、依存関係プロパティの概要に関する記事を参照済みであることを前提としています。 この記事の例について理解するには、Extensible Application Markup Language (XAML) を使い慣れていて、WPF アプリケーションの記述方法を理解していると役に立ちます。

コレクション型の依存関係プロパティを実装する

一般に、依存関係プロパティの実装パターンは、フィールドやその他のコンストラクトではなく、DependencyProperty 識別子によってサポートされる CLR プロパティ ラッパーとなります。 コレクション型の依存関係プロパティを実装する場合も、同じパターンとすることができます。 このパターンは、コレクション要素の型が DependencyObject または Freezable 派生クラスである場合、より複雑になります。

コレクションを初期化する

依存関係プロパティを作成する場合は、通常、初期プロパティ値を指定するのでなく、依存関係プロパティのメタデータを使用して既定値を指定します。 ただし、ご利用のプロパティ値が参照型である場合は、依存関係プロパティを登録するクラスのコンストラクターで既定値を設定する必要があります。 依存関係プロパティのメタデータには、既定の参照型の値を含めてはなりません。その値はクラスのすべてのインスタンスに割り当てられ、それによってシングルトン クラスが作成されるからです。

次の例では、ジェネリック List<T> 内に FrameworkElement 要素のコレクションを含む Aquarium クラスを宣言しています。 既定のコレクション値は、RegisterReadOnly(String, Type, Type, PropertyMetadata) メソッドに渡される PropertyMetadata には含められていません。代わりに、クラス コンストラクターを使用して、既定のコレクション値を新しい汎用的な List に設定しています。

public class Aquarium : DependencyObject
{
    // Register a dependency property with the specified property name,
    // property type, owner type, and property metadata.
    private static readonly DependencyPropertyKey s_aquariumContentsPropertyKey =
        DependencyProperty.RegisterReadOnly(
          name: "AquariumContents",
          propertyType: typeof(List<FrameworkElement>),
          ownerType: typeof(Aquarium),
          typeMetadata: new FrameworkPropertyMetadata()
          //typeMetadata: new FrameworkPropertyMetadata(new List<FrameworkElement>())
        );

    // Set the default collection value in a class constructor.
    public Aquarium() => SetValue(s_aquariumContentsPropertyKey, new List<FrameworkElement>());

    // Declare a public get accessor.
    public List<FrameworkElement> AquariumContents =>
        (List<FrameworkElement>)GetValue(s_aquariumContentsPropertyKey.DependencyProperty);
}

public class Fish : FrameworkElement { }
Public Class Aquarium
    Inherits DependencyObject

    ' Register a dependency property with the specified property name,
    ' property type, owner type, and property metadata.
    Private Shared ReadOnly s_aquariumContentsPropertyKey As DependencyPropertyKey =
        DependencyProperty.RegisterReadOnly(
            name:="AquariumContents",
            propertyType:=GetType(List(Of FrameworkElement)),
            ownerType:=GetType(Aquarium),
            typeMetadata:=New FrameworkPropertyMetadata())
            'typeMetadata:=New FrameworkPropertyMetadata(New List(Of FrameworkElement)))

    ' Set the default collection value in a class constructor.
    Public Sub New()
        SetValue(s_aquariumContentsPropertyKey, New List(Of FrameworkElement)())
    End Sub

    ' Declare a public get accessor.
    Public ReadOnly Property AquariumContents As List(Of FrameworkElement)
        Get
            Return CType(GetValue(s_aquariumContentsPropertyKey.DependencyProperty), List(Of FrameworkElement))
        End Get
    End Property
End Class

Public Class Fish
    Inherits FrameworkElement
End Class

次のテスト コードでは、2 つの個別の Aquarium インスタンスをインスタンス化し、各コレクションにそれぞれ異なる Fish 項目を追加します。 コードを実行すると、予想どおり、各 Aquarium インスタンスにそれぞれ 1 つのコレクション項目が含まれているのがわかります。

private void InitializeAquariums(object sender, RoutedEventArgs e)
{
    Aquarium aquarium1 = new();
    Aquarium aquarium2 = new();
    aquarium1.AquariumContents.Add(new Fish());
    aquarium2.AquariumContents.Add(new Fish());
    MessageBox.Show(
        $"aquarium1 contains {aquarium1.AquariumContents.Count} fish\r\n" +
        $"aquarium2 contains {aquarium2.AquariumContents.Count} fish");
}
Private Sub InitializeAquariums(sender As Object, e As RoutedEventArgs)
    Dim aquarium1 As New Aquarium()
    Dim aquarium2 As New Aquarium()
    aquarium1.AquariumContents.Add(New Fish())
    aquarium2.AquariumContents.Add(New Fish())
    MessageBox.Show($"aquarium1 contains {aquarium1.AquariumContents.Count} fish{Environment.NewLine}" +
                    $"aquarium2 contains {aquarium2.AquariumContents.Count} fish")
End Sub

ただし、クラス コンストラクターをコメント アウトし、既定のコレクション値を PropertyMetadata として RegisterReadOnly(String, Type, Type, PropertyMetadata) メソッドに渡した場合、各 Aquarium インスタンスには 2 つのコレクション項目が含まれているのがわかります。 これは、両方の Fish インスタンスが同じリストに追加され、これが Aquarium クラス内のすべてのインスタンスによって共有されるためです。 そのため、オブジェクト インスタンスに独自のリストを持たせることを意図する場合は、既定値をクラス コンストラクターで設定する必要があります。

読み取り/書き込みコレクションを初期化する

次の例では、非キー署名メソッド Register(String, Type, Type) および SetValue(DependencyProperty, Object) を使用して、Aquarium クラス内で読み取り/書き込みコレクション型の依存関係プロパティを宣言します。

public class Aquarium : DependencyObject
{
    // Register a dependency property with the specified property name,
    // property type, and owner type. Store the dependency property
    // identifier as a public static readonly member of the class.
    public static readonly DependencyProperty AquariumContentsProperty =
        DependencyProperty.Register(
          name: "AquariumContents",
          propertyType: typeof(List<FrameworkElement>),
          ownerType: typeof(Aquarium)
        );

    // Set the default collection value in a class constructor.
    public Aquarium() => SetValue(AquariumContentsProperty, new List<FrameworkElement>());

    // Declare public get and set accessors.
    public List<FrameworkElement> AquariumContents
    {
        get => (List<FrameworkElement>)GetValue(AquariumContentsProperty);
        set => SetValue(AquariumContentsProperty, value);
    }
}
Public Class Aquarium
    Inherits DependencyObject

    ' Register a dependency property with the specified property name,
    ' property type, and owner type. Store the dependency property
    ' identifier as a static member of the class.
    Public Shared ReadOnly AquariumContentsProperty As DependencyProperty =
        DependencyProperty.Register(
            name:="AquariumContents",
            propertyType:=GetType(List(Of FrameworkElement)),
            ownerType:=GetType(Aquarium))

    ' Set the default collection value in a class constructor.
    Public Sub New()
        SetValue(AquariumContentsProperty, New List(Of FrameworkElement)())
    End Sub

    ' Declare public get and set accessors.
    Public Property AquariumContents As List(Of FrameworkElement)
        Get
            Return CType(GetValue(AquariumContentsProperty), List(Of FrameworkElement))
        End Get
        Set
            SetValue(AquariumContentsProperty, Value)
        End Set
    End Property
End Class

FreezableCollection 依存関係プロパティ

コレクション型の依存関係プロパティによって、そのサブプロパティ内の変更が自動的に報告されることはありません。 その結果、コレクションにバインドしている場合、そのバインディングから変更が報告されず、一部のデータ バインディング シナリオが無効になる可能性があります。 しかし、依存関係プロパティ型に FreezableCollection<T> 使用すると、コレクション要素のプロパティに対する変更が適切に報告され、バインディングは期待どおりに動作します。

依存関係オブジェクトのコレクション内のサブプロパティ バインドを有効にするには、コレクション型 FreezableCollection を使用し、DependencyObject 派生クラスの型制約を指定します。

次の例では、型制約を FrameworkElement とした FreezableCollection を含む Aquarium クラスを宣言します。 既定のコレクション値は、RegisterReadOnly(String, Type, Type, PropertyMetadata) メソッドに渡される PropertyMetadata には含められていません。代わりに、クラス コンストラクターを使用して、既定のコレクション値を新しい FreezableCollection に設定しています。

public class Aquarium : DependencyObject
{
    // Register a dependency property with the specified property name,
    // property type, and owner type.
    private static readonly DependencyPropertyKey s_aquariumContentsPropertyKey =
        DependencyProperty.RegisterReadOnly(
          name: "AquariumContents",
          propertyType: typeof(FreezableCollection<FrameworkElement>),
          ownerType: typeof(Aquarium),
          typeMetadata: new FrameworkPropertyMetadata()
        );

    // Store the dependency property identifier as a static member of the class.
    public static readonly DependencyProperty AquariumContentsProperty =
        s_aquariumContentsPropertyKey.DependencyProperty;

    // Set the default collection value in a class constructor.
    public Aquarium() => SetValue(s_aquariumContentsPropertyKey, new FreezableCollection<FrameworkElement>());

    // Declare a public get accessor.
    public FreezableCollection<FrameworkElement> AquariumContents =>
        (FreezableCollection<FrameworkElement>)GetValue(AquariumContentsProperty);
}
Public Class Aquarium
    Inherits DependencyObject

    ' Register a dependency property with the specified property name,
    ' property type, and owner type.
    Private Shared ReadOnly s_aquariumContentsPropertyKey As DependencyPropertyKey =
        DependencyProperty.RegisterReadOnly(
            name:="AquariumContents",
            propertyType:=GetType(FreezableCollection(Of FrameworkElement)),
            ownerType:=GetType(Aquarium),
            typeMetadata:=New FrameworkPropertyMetadata())

    ' Set the default collection value in a class constructor.
    Public Sub New()
        SetValue(s_aquariumContentsPropertyKey, New FreezableCollection(Of FrameworkElement)())
    End Sub

    ' Declare a public get accessor.
    Public ReadOnly Property AquariumContents As FreezableCollection(Of FrameworkElement)
        Get
            Return CType(GetValue(s_aquariumContentsPropertyKey.DependencyProperty), FreezableCollection(Of FrameworkElement))
        End Get
    End Property
End Class

関連項目