Свойства зависимостей типа коллекции (WPF .NET)

В этой статье приведены рекомендации и предлагаемые шаблоны для реализации свойства зависимостей, которое является типом коллекции.

Важно!

Документация по рабочему столу для .NET 7 и .NET 6 находится в стадии разработки.

Необходимые компоненты

Для понимания статьи нужно иметь базовые знания о свойствах зависимостей и прочитать Общие сведения о свойствах зависимостей. Чтобы понимать примеры в этой статье полезно познакомиться с языком XAML и узнать, как создавать приложения WPF.

Реализация свойства зависимости типа коллекции

Как правило, шаблон реализации для свойства зависимостей — это оболочка свойства CLR, поддерживаемая DependencyProperty идентификатором вместо поля или другой конструкции. При реализации свойства зависимостей типа коллекции можно следовать тому же шаблону. Шаблон более сложный, если тип элемента коллекции является 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

Но если вы закомментируете конструктор класса и передайте значение коллекции по умолчанию в PropertyMetadataRegisterReadOnly(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

См. также