Propriedades de dependência de tipo de coleção (WPF .NET)

Este artigo fornece orientação e padrões sugeridos para implementar uma propriedade de dependência que é um tipo de coleção.

Importante

A documentação do Guia da Área de Trabalho para .NET 7 e .NET 6 está em construção.

Pré-requisitos

O artigo pressupõe um conhecimento básico das propriedades de dependência e que você leu Visão geral das propriedades de dependência. Para seguir os exemplos neste artigo, é útil se você estiver familiarizado com XAML (Extensible Application Markup Language) e souber como escrever aplicativos WPF.

Implementar uma propriedade de dependência de tipo de coleção

Em geral, o padrão de implementação para uma propriedade de dependência é um wrapper de propriedade CLR apoiado por um identificador em vez de um DependencyProperty campo ou outra construção. Você pode seguir o mesmo padrão ao implementar uma propriedade de dependência de tipo de coleção. O padrão será mais complexo se o tipo de elemento de coleção for uma DependencyObject classe derivada ou uma Freezable classe.

Inicializar a coleção

Ao criar uma propriedade de dependência, você normalmente especifica o valor padrão por meio de metadados de propriedade de dependência em vez de especificar um valor de propriedade inicial. No entanto, se o valor da propriedade for um tipo de referência, o valor padrão deverá ser definido no construtor da classe que registra a propriedade de dependência. Os metadados da propriedade de dependência não devem incluir um valor de tipo de referência padrão porque esse valor será atribuído a todas as instâncias da classe, criando uma classe singleton.

O exemplo a seguir declara uma classe que contém uma Aquarium coleção de FrameworkElement elementos em um genérico List<T>. Um valor de coleção padrão não é incluído no passado para o método e, em PropertyMetadata vez disso, o construtor de classe é usado para definir o RegisterReadOnly(String, Type, Type, PropertyMetadata) valor de coleção padrão para um novo genérico 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

O código de teste a seguir instancia duas instâncias separadas Aquarium e adiciona um item diferente Fish a cada coleção. Se você executar o código, verá que cada Aquarium instância tem um único item de coleção, conforme o esperado.

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

Mas, se você comentar o construtor de classe e passar o valor de coleção padrão quanto PropertyMetadata ao RegisterReadOnly(String, Type, Type, PropertyMetadata) método, verá que cada Aquarium instância obtém dois itens de coleção! Isso ocorre porque ambas as Fish instâncias são adicionadas à mesma lista, que é compartilhada por todas as instâncias da classe Aquarium. Assim, quando a intenção é que cada instância de objeto tenha sua própria lista, o valor padrão deve ser definido no construtor de classe.

Inicializar uma coleção de leitura-gravação

O exemplo a seguir declara uma propriedade de dependência de tipo de coleção de leitura-gravação na Aquarium classe, usando os métodos Register(String, Type, Type) de assinatura não-chave e 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

Propriedades de dependência FreezableCollection

Uma propriedade de dependência de tipo de coleção não relata automaticamente alterações em suas subpropriedades. Como resultado, se você estiver vinculando a uma coleção, a associação pode não relatar alterações, invalidando alguns cenários de vinculação de dados. Mas, se você usar FreezableCollection<T> para o tipo de propriedade de dependência, as alterações nas propriedades dos elementos da coleção serão relatadas corretamente e a vinculação funcionará conforme o esperado.

Para habilitar a vinculação de subpropriedade em uma coleção de objetos de dependência, use o tipo de coleção , com uma restrição de tipo FreezableCollectionde qualquer DependencyObject classe derivada.

O exemplo a seguir declara uma classe que contém uma AquariumFreezableCollection restrição de tipo com FrameworkElementum . Um valor de coleção padrão não é incluído no passado para o método e, em PropertyMetadata vez disso, o construtor de classe é usado para definir o RegisterReadOnly(String, Type, Type, PropertyMetadata) valor de coleção padrão para um novo 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

Confira também