Proprietà di dipendenza di tipo raccolta (WPF .NET)

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

Importante

La documentazione di Desktop Guide per .NET 7 e .NET 6 è in fase di costruzione.

Prerequisiti

L'articolo presuppone una conoscenza di base delle proprietà di dipendenza e che si 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 DependencyProperty identificatore 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 è una DependencyObject classe derivata o 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 Aquarium classe che contiene una raccolta di FrameworkElement elementi in un oggetto generico List<T>. Un valore di raccolta predefinito non è incluso nell'oggetto PropertyMetadata passato al RegisterReadOnly(String, Type, Type, PropertyMetadata) metodo e viene invece usato il costruttore della classe per impostare il valore di raccolta predefinito su un nuovo oggetto generico 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

Il codice di test seguente crea un'istanza di due istanze separate Aquarium e aggiunge un elemento diverso Fish a ogni raccolta. Se si esegue il codice, si noterà che ogni Aquarium istanza 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 imposta come commento il costruttore della classe e si passa il valore di raccolta predefinito al PropertyMetadataRegisterReadOnly(String, Type, Type, PropertyMetadata) metodo , si noterà che ogni Aquarium istanza ottiene due elementi della raccolta. Ciò è dovuto al fatto che entrambe Fish le istanze vengono aggiunte allo stesso elenco, che è condiviso da tutte le istanze della classe Aquarium. Pertanto, quando la finalità è per ogni istanza dell'oggetto di avere un proprio elenco, 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 raccolta di lettura/scrittura nella Aquarium classe utilizzando i metodi Register(String, Type, Type) di firma non chiave 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 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 segnalare modifiche, 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 l'associazione funziona come previsto.

Per abilitare l'associazione di sottoproprietà in una raccolta di oggetti dipendenza, usare il tipo FreezableCollectiondi raccolta , con un vincolo di tipo di qualsiasi DependencyObject classe derivata.

Nell'esempio seguente viene dichiarata una Aquarium classe contenente un FreezableCollection oggetto con un vincolo di tipo .FrameworkElement Un valore di raccolta predefinito non è incluso nell'oggetto PropertyMetadata passato al RegisterReadOnly(String, Type, Type, PropertyMetadata) metodo e viene invece usato il costruttore della classe per impostare il valore di raccolta predefinito su un nuovo FreezableCollectionoggetto .

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

Vedi anche