Abhängigkeitseigenschaften des Sammlungstyps (WPF .NET)

Dieser Artikel enthält Anleitungen und vorgeschlagene Muster zum Implementieren einer Abhängigkeitseigenschaft, die ein Sammlungstyp ist.

Wichtig

Der Desktopleitfaden zu .NET 7 und .NET 6 ist in Bearbeitung.

Voraussetzungen

Im Artikel wird davon ausgegangen, dass sie grundlegende Kenntnisse über Abhängigkeitseigenschaften haben und eine Übersicht über Abhängigkeitseigenschaften gelesen haben. Um den Beispielen in diesem Artikel zu folgen, hilft es, wenn Sie mit Extensible Application Markup Language (XAML) vertraut sind und wissen, wie WPF-Anwendungen geschrieben werden.

Implementieren einer Auflistungstypabhängigkeitseigenschaft

Im Allgemeinen ist das Implementierungsmuster für eine Abhängigkeitseigenschaft ein CLR-Eigenschaftswrapper, der von einem DependencyProperty-Bezeichner anstelle eines Felds oder eines anderen Konstrukts unterstützt wird. Sie können das gleiche Muster verwenden, wenn Sie eine Abhängigkeitseigenschaft des Auflistungstyps implementieren. Das Muster ist komplexer, wenn der Auflistungselementtyp eine DependencyObject oder eine Freezable abgeleitete Klasse ist.

Initialisieren der Auflistung

Wenn Sie eine Abhängigkeitseigenschaft erstellen, geben Sie den Standardwert in der Regel über Metadaten der Abhängigkeitseigenschaft an, anstatt einen anfänglichen Eigenschaftswert anzugeben. Wenn ihr Eigenschaftswert jedoch ein Verweistyp ist, sollte der Standardwert im Konstruktor der Klasse festgelegt werden, die die Abhängigkeitseigenschaft registriert. Die Metadaten der Abhängigkeitseigenschaft sollten keinen Standardverweistypwert enthalten, da dieser Wert allen Instanzen der -Klasse zugewiesen wird, wodurch eine Singletonklasse erstellt wird.

Im folgenden Beispiel wird eine Aquarium-Klasse deklariert, die eine Auflistung von FrameworkElement-Elementen in einer generischen List<T> enthält. Ein Standardsammlungswert ist nicht in der PropertyMetadata enthalten, die an die Methode übergeben RegisterReadOnly(String, Type, Type, PropertyMetadata) wird, und stattdessen wird der Klassenkonstruktor verwendet, um den Standardsammlungswert auf einen neuen generischen List festzulegen.

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

Der folgende Testcode instanziiert zwei separate Aquarium-Instanzen und fügt jeder Auflistung ein anderes Fish-Element hinzu. Wenn Sie den Code ausführen, sehen Sie, dass jede Aquarium-Instanz über ein einzelnes Sammlungselement verfügt, wie erwartet.

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

Wenn Sie jedoch den Klassenkonstruktor kommentieren und den Standardsammlungswert an PropertyMetadata die RegisterReadOnly(String, Type, Type, PropertyMetadata)-Methode übergeben, sehen Sie, dass jede Aquarium-Instanz zwei Sammlungselemente abruft! Dies liegt daran, dass beide Fish-Instanzen derselben Liste hinzugefügt werden, die von allen Instanzen der Aquarium-Klasse geteilt wird. Wenn also die Absicht ist, dass jede Objektinstanz über eine eigene Liste verfügt, sollte der Standardwert im Klassenkonstruktor festgelegt werden.

Initialisieren einer Lese-/Schreibzugriffssammlung

Im folgenden Beispiel wird eine Read-Write-Auflistungsabhängigkeitseigenschaft in der Aquarium-Klasse deklariert, wobei die Nichtschlüsselsignaturmethoden und -methoden Register(String, Type, Type) und SetValue(DependencyProperty, Object) verwendet werden.

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-Abhängigkeitseigenschaften

Eine Abhängigkeitseigenschaft des Sammlungstyps meldet Änderungen in ihren Untereigenschaften nicht automatisch. Wenn Sie also eine Bindung an eine Sammlung vornehmen, meldet die Bindung möglicherweise keine Änderungen, wodurch einige Datenbindungsszenarien ungültig werden. Wenn Sie jedoch für den Abhängigkeitseigenschaftstyp verwenden FreezableCollection<T>, werden Änderungen an den Eigenschaften von Sammlungselementen ordnungsgemäß gemeldet und die Bindung funktioniert wie erwartet.

Verwenden Sie zum Aktivieren der Subproperty-Bindung in einer Auflistung von Abhängigkeitsobjekten den Auflistungstyp FreezableCollectionmit einer Typeinschränkung einer DependencyObject abgeleiteten Klasse.

Im folgenden Beispiel wird eine Aquarium-Klasse deklariert, die eine FreezableCollection mit einer Typeinschränkung von FrameworkElement enthält. Ein Standardsammlungswert ist nicht in der PropertyMetadata enthalten, die an die RegisterReadOnly(String, Type, Type, PropertyMetadata)-Methode übergeben wird, und stattdessen wird der Klassenkonstruktor verwendet, um den Standardsammlungswert auf einen neuen generischen FreezableCollection festzulegen.

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

Weitere Informationen