Abhängigkeitseigenschaften vom Auflistungstyp

Dieses Thema enthält einen Leitfaden und empfohlene Muster zur Implementierung einer Abhängigkeitseigenschaft für Eigenschaften vom Auflistungstyp.

Implementieren einer Abhängigkeitseigenschaft vom Auflistungstyp

Für Abhängigkeitseigenschaften im Allgemeinen verwenden Sie das folgende Implementierungsmuster: Definieren Sie einen DependencyProperty-Eigenschaftenwrapper, bei dem diese Eigenschaft durch einen -Bezeichner gesichert ist anstatt durch ein Feld oder anderes Konstrukt. Folgen Sie diesem Muster auch bei der Implementierung einer Eigenschaft vom Auflistungstyp. Das Muster wird jedoch komplexer, wenn der in der Auflistung enthaltene Typ selbst ein DependencyObject oder Freezable abgeleitete Klasse ist.

Initialisieren der Auflistung für einen anderen Wert als den Standardwert

Beim Erstellen einer Abhängigkeitseigenschaft geben Sie nicht den Standardwert der Eigenschaft als Anfangsfeldwert an. Geben Sie den Standardwert stattdessen über die Metadaten für Abhängigkeitseigenschaften an. Handelt es sich bei der Eigenschaft um einen Verweistyp, ist der in den Metadaten für Abhängigkeitseigenschaften angegebene Standardwert kein Standardwert pro Instanz. Er stellt vielmehr einen Standardwert dar, der für alle Instanzen des Typs gilt. Achten Sie also darauf, dass Sie nicht die durch die Metadaten für Auflistungseigenschaften definierte, einzelne statische Auflistung als funktionierenden Standardwert für neu erstellte Instanzen des Typs verwenden. Vergewissern Sie sich stattdessen, dass Sie den Auflistungswert bewusst als eine eindeutige (Instanz-)Auflistung als Teil Ihrer Klassenkonstruktorlogik festlegen. Andernfalls erstellen Sie unbeabsichtigterweise eine Singleton-Klasse.

Betrachten Sie das folgende Beispiel. Im folgenden Abschnitt des Beispiels wird die Definition einer Klasse Aquarium dargestellt, die einen Fehler mit dem Standardwert enthält. Die Klasse definiert die Eigenschaft AquariumObjects Abhängigkeit vom Typ der Sammlung, die den generischen Typ List<T> mit einer Typbeschränkung FrameworkElement verwendet. Im Register(String, Type, Type, PropertyMetadata)-Aufruf der Abhängigkeitseigenschaft legt die Metadaten den Standardwert fest, der ein neuer generischer List<T>-Wert ist.

Warnung

Der folgende Code verhält sich nicht ordnungsgemäß.

public class Fish : FrameworkElement { }
public class Aquarium : DependencyObject {
    private static readonly DependencyPropertyKey AquariumContentsPropertyKey =
        DependencyProperty.RegisterReadOnly(
          "AquariumContents",
          typeof(List<FrameworkElement>),
          typeof(Aquarium),
          new FrameworkPropertyMetadata(new List<FrameworkElement>())
        );
    public static readonly DependencyProperty AquariumContentsProperty =
        AquariumContentsPropertyKey.DependencyProperty;

    public List<FrameworkElement> AquariumContents
    {
        get { return (List<FrameworkElement>)GetValue(AquariumContentsProperty); }
    }

    // ...
}
Public Class Fish
    Inherits FrameworkElement
End Class
Public Class Aquarium
    Inherits DependencyObject
    Private Shared ReadOnly AquariumContentsPropertyKey As DependencyPropertyKey = DependencyProperty.RegisterReadOnly("AquariumContents", GetType(List(Of FrameworkElement)), GetType(Aquarium), New FrameworkPropertyMetadata(New List(Of FrameworkElement)()))
    Public Shared ReadOnly AquariumContentsProperty As DependencyProperty = AquariumContentsPropertyKey.DependencyProperty

    Public ReadOnly Property AquariumContents() As List(Of FrameworkElement)
        Get
            Return CType(GetValue(AquariumContentsProperty), List(Of FrameworkElement))
        End Get
    End Property

    ' ...

End Class

Wenn Sie den Code unverändert lassen, wird dieser einzelne Listenstandardwert für alle Instanzen von Aquarium freigegeben. Beim Ausführen des folgenden Testcodes, der zeigen soll, wie zwei getrennte Aquarium-Instanzen instanziiert und jeder ein einzelner unterschiedlicher Fish hinzugefügt würde, käme es zu einem überraschenden Ergebnis:

Aquarium myAq1 = new Aquarium();
Aquarium myAq2 = new Aquarium();
Fish f1 = new Fish();
Fish f2 = new Fish();
myAq1.AquariumContents.Add(f1);
myAq2.AquariumContents.Add(f2);
MessageBox.Show("aq1 contains " + myAq1.AquariumContents.Count.ToString() + " things");
MessageBox.Show("aq2 contains " + myAq2.AquariumContents.Count.ToString() + " things");
Dim myAq1 As New Aquarium()
Dim myAq2 As New Aquarium()
Dim f1 As New Fish()
Dim f2 As New Fish()
myAq1.AquariumContents.Add(f1)
myAq2.AquariumContents.Add(f2)
MessageBox.Show("aq1 contains " & myAq1.AquariumContents.Count.ToString() & " things")
MessageBox.Show("aq2 contains " & myAq2.AquariumContents.Count.ToString() & " things")

Anstatt eines Eintrags besitzt jede Auflistung nun zwei! Das liegt daran, dass jedes Aquarium seinen Fish der Standardwertauflistung hinzugefügt hat, die das Ergebnis eines einzelnen Konstruktoraufrufs in den Metadaten ist und daher für alle Instanzen freigegeben ist. Diese Situation ist so gut wie nie erwünscht.

Zur Behebung des Problems müssen Sie den Abhängigkeitseigenschaftswert der Auflistung auf eine eindeutige Instanz als Teil des Klassenkonstruktoraufrufs zurücksetzen. Da es sich bei der Eigenschaft um eine schreibgeschützte Abhängigkeitseigenschaft handelt, setzen Sie sie mit der SetValue(DependencyPropertyKey, Object)-Methode mit der DependencyPropertyKey, die nur innerhalb der Klasse zugänglich ist.

public Aquarium() : base()
{
    SetValue(AquariumContentsPropertyKey, new List<FrameworkElement>());
}
Public Sub New()
    MyBase.New()
    SetValue(AquariumContentsPropertyKey, New List(Of FrameworkElement)())
End Sub

Bei einer erneuten Ausführung des Testcodes wären die Ergebnisse nun eher wie erwartet, da jedes Aquarium seine eigene eindeutige Auflistung unterstützen würde.

Dieses Muster wäre geringfügig abgewandelt, wenn Sie die Auflistungseigenschaft mit Lese-/Schreibzugriff wählen würden. In diesem Fall könnten Sie den öffentlichen Set-Accessor vom Konstruktor aus aufrufen, um die Initialisierung vorzunehmen. Dies wäre immer noch ein Aufruf der Nicht-Schlüssel-Signatur SetValue(DependencyProperty, Object) innerhalb Ihres Set-Wrappers unter Verwendung eines öffentlichen DependencyProperty-Bezeichners.

Melden von Bindungswertänderungen durch Auflistungseigenschaften

Eine Auflistungseigenschaft, die selbst eine Abhängigkeitseigenschaft ist, meldet nicht automatisch Änderungen an die ihr untergeordneten Eigenschaften. Wenn Sie in einer Auflistung Bindungen erstellen, kann möglicherweise das Melden von Änderungen durch die Bindung verhindert werden, wodurch einige Datenbindungsszenarios ungültig gemacht werden können. Bei Verwendung des FreezableCollection<T>-Auflistungstyps als Ihren Auflistungstyp werden jedoch Änderungen an untergeordneten Eigenschaften der in der Auflistung enthaltenen Elemente ordnungsgemäß gemeldet, und die Bindung funktioniert wie erwartet.

Zur Aktivierung der Bindung von untergeordneten Eigenschaften in einer Auflistung von Abhängigkeitsobjekten erstellen Sie die Auflistungseigenschaft als FreezableCollection<T>-Typ mit einer Typeinschränkung für diese Sammlung für jede von DependencyObject abgeleitete Klasse.

Weitere Informationen