集合类型依赖属性 (WPF .NET)

本文提供了用于实现集合类型依赖属性的指南和建议模式。

重要

面向 .NET 7 和 .NET 6 的桌面指南文档正在撰写中。

先决条件

本文假定你对依赖属性有基本的了解,并且已阅读依赖属性概述。 若要理解本文中的示例,还应当熟悉 Extensible Application Markup Language (XAML) 并知道如何编写 WPF 应用程序。

实现集合类型依赖属性

通常,依赖属性的实现模式由 DependencyProperty 标识符(而非字段或其他构造)提供支持。 实现集合类型依赖属性时,可以按照此相同的模式。 如果集合元素类型是 DependencyObjectFreezable 派生类,则模式会更复杂。

初始化集合

创建依赖属性时,通常通过依赖属性元数据指定默认值,而不是指定初始属性值。 但是,如果属性值是引用类型,则应在注册依赖属性的类的构造函数中设置默认值。 依赖属性元数据不应包含默认引用类型值,因为该值将分配给该类的所有实例,从而创建单一实例类。

以下示例声明了一个 Aquarium 类,该类包含泛型 List<T> 中的 FrameworkElement 元素的集合。 传递给 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

以下测试代码实例化两个单独的 Aquarium 实例,并向每个集合添加不同的 Fish 项。 如果运行此代码,你将看到每个 Aquarium 实例都有一个集合项,正如预期的那样。

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 实例都会获取两个集合项! 这是因为两个 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 派生类的类型约束。

以下示例声明了一个 Aquarium 类,该类包含类型约束为 FrameworkElementFreezableCollection。 传递给 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

另请参阅