本文提供了用于实现集合类型的依赖属性的指导和建议模式。
先决条件
本文假设对依赖属性有一个基本的了解,并且你已阅读 依赖项属性概述。 若要遵循本文中的示例,如果熟悉可扩展应用程序标记语言(XAML),并且知道如何编写 WPF 应用程序,则很有帮助。
实现集合类型依赖属性
通常,依赖属性的实现模式是一个由 DependencyProperty 标识符支持的 CLR 属性包装器,而不是由字段或其他结构支持。 实现集合类型依赖属性时,可以遵循相同的模式。 如果集合元素类型是 DependencyObject 或 Freezable 派生类,则模式更为复杂。
初始化集合
创建依赖属性时,通常通过依赖属性元数据指定默认值,而不是指定初始属性值。 但是,如果属性值是引用类型,则应在注册依赖属性的类的构造函数中设置默认值。 依赖属性元数据不应包含默认引用类型值,因为该值将分配给该类的所有实例,从而创建单一实例类。
以下示例声明一个 Aquarium
类,该类包含泛型 FrameworkElement中的 List<T> 元素的集合。 传递给 PropertyMetadata 方法的 RegisterReadOnly(String, Type, Type, 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) 类中声明读写集合类型依赖属性。
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
类,该类包含类型约束为 FreezableCollection
的 FrameworkElement。 传递给 PropertyMetadata 方法的 RegisterReadOnly(String, Type, Type, 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