Condividi tramite


Proprietà di dipendenza di tipo collezione

Questo articolo fornisce indicazioni e modelli suggeriti per l'implementazione di una proprietà di dipendenza che è un tipo di raccolta.

Prerequisiti

L'articolo presuppone una conoscenza di base delle proprietà di dipendenza e che tu abbia letto "Panoramica delle proprietà di dipendenza". Per seguire gli esempi in questo articolo, è utile se si ha familiarità con Extensible Application Markup Language (XAML) e si sa come scrivere applicazioni WPF.

Implementare una proprietà di dipendenza di tipo raccolta

In generale, il modello di implementazione per una proprietà di dipendenza è un wrapper di proprietà CLR supportato da un identificatore DependencyProperty anziché da un campo o da un altro costrutto. È possibile seguire lo stesso modello quando si implementa una proprietà di dipendenza di tipo raccolta. Il modello è più complesso se il tipo di elemento della raccolta è un DependencyObject o una classe derivata Freezable.

Inizializzare la raccolta

Quando si crea una proprietà di dipendenza, in genere si specifica il valore predefinito tramite i metadati della proprietà di dipendenza anziché specificare un valore iniziale della proprietà. Tuttavia, se il valore della proprietà è un tipo riferimento, il valore predefinito deve essere impostato nel costruttore della classe che registra la proprietà di dipendenza. I metadati della proprietà di dipendenza non devono includere un valore di tipo riferimento predefinito perché tale valore verrà assegnato a tutte le istanze della classe, creando una classe singleton.

Nell'esempio seguente viene dichiarata una classe Aquarium che contiene una raccolta di elementi FrameworkElement in un List<T>generico . Un valore di raccolta predefinito non è incluso nel PropertyMetadata passato al metodo RegisterReadOnly(String, Type, Type, PropertyMetadata); invece, si utilizza il costruttore della classe per impostare il valore di raccolta predefinito su un nuovo Listgenerico.

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

Il codice di test seguente crea due istanze separate di Aquarium e aggiunge un elemento Fish diverso a ogni raccolta. Se esegui il codice, vedrai che ogni istanza di Aquarium ha un singolo elemento della raccolta, come previsto.

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

Tuttavia, se si commenta il costruttore della classe e si passa il valore di raccolta predefinito come PropertyMetadata al metodo RegisterReadOnly(String, Type, Type, PropertyMetadata), si noterà che ogni istanza di Aquarium ottiene due elementi della raccolta. Questo perché entrambe le istanze di Fish vengono aggiunte allo stesso elenco, che è condiviso da tutte le istanze della classe Aquarium. Pertanto, quando l'intento è che ciascuna istanza dell'oggetto abbia la propria lista, il valore predefinito deve essere impostato nel costruttore della classe.

Inizializzare una raccolta di lettura/scrittura

Nell'esempio seguente viene dichiarata una proprietà di dipendenza di tipo lettura/scrittura della raccolta nella classe Aquarium utilizzando i metodi di firma non chiave Register(String, Type, Type) 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

Proprietà di dipendenza della FreezableCollection

Una proprietà di dipendenza di tipo raccolta non segnala automaticamente le modifiche nelle relative proprietà secondarie. Di conseguenza, se si esegue il binding a una raccolta, l'associazione potrebbe non registrare cambiamenti, invalidando alcuni scenari di data binding. Tuttavia, se si usa FreezableCollection<T> per il tipo di proprietà di dipendenza, le modifiche apportate alle proprietà degli elementi della raccolta vengono segnalate correttamente e il binding funziona come previsto.

Per abilitare l'associazione di sotto-proprietà in una raccolta di oggetti di dipendenza, utilizzare il tipo di raccolta FreezableCollection, con un vincolo di tipo su qualsiasi classe derivata da DependencyObject.

Nell'esempio seguente viene dichiarata una classe Aquarium che contiene un FreezableCollection con un vincolo di tipo FrameworkElement. Un valore di raccolta predefinito non è incluso nel PropertyMetadata passato al metodo RegisterReadOnly(String, Type, Type, PropertyMetadata); al contrario, viene utilizzato il costruttore della classe per assegnare al valore di raccolta predefinito un nuovo 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

Vedere anche