Vlastnosti závislostí typu kolekce (WPF .NET)

Tento článek obsahuje pokyny a navrhované vzory pro implementaci vlastnosti závislosti, která je typem kolekce.

Důležité

Dokumentace k desktopové příručce pro .NET 7 a .NET 6 se právě připravuje.

Předpoklady

V článku se předpokládá základní znalost vlastností závislostí a že jste si přečetli přehled vlastností závislostí. Pokud chcete postupovat podle příkladů v tomto článku, pomůže vám to, pokud znáte jazyk XAML (Extensible Application Markup Language) a víte, jak psát aplikace WPF.

Implementace vlastnosti závislosti typu kolekce

Obecně platí, že model implementace vlastnosti závislosti je obálka vlastností CLR zálohovaná identifikátorem DependencyProperty místo pole nebo jiného konstruktoru. Při implementaci vlastnosti závislosti typu kolekce můžete postupovat stejným způsobem. Model je složitější, pokud je DependencyObject typ elementu kolekce nebo odvozená Freezable třída.

Inicializace kolekce

Při vytváření vlastnosti závislosti obvykle zadáte výchozí hodnotu prostřednictvím metadat vlastností závislostí místo zadání počáteční hodnoty vlastnosti. Pokud je však hodnota vlastnosti referenční typ, měla by být výchozí hodnota nastavena v konstruktoru třídy, která registruje vlastnost závislosti. Metadata vlastnosti závislosti by neměla obsahovat výchozí hodnotu typu odkazu, protože tato hodnota bude přiřazena ke všem instancím třídy a vytvoří jednu třídu.

Následující příklad deklaruje Aquarium třídu, která obsahuje kolekci FrameworkElement prvků v obecné List<T>. Výchozí hodnota kolekce není zahrnuta v PropertyMetadata předané RegisterReadOnly(String, Type, Type, PropertyMetadata) metodě a místo toho se konstruktor třídy používá k nastavení výchozí hodnoty kolekce na nový obecný 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

Následující testovací kód vytvoří instanci dvou samostatných Aquarium instancí a přidá do každé kolekce jinou Fish položku. Pokud kód spustíte, uvidíte, že každá Aquarium instance má jednu položku kolekce podle očekávání.

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

Pokud ale zakomentujete konstruktor třídy a předáte výchozí hodnotu kolekce metodě PropertyMetadataRegisterReadOnly(String, Type, Type, PropertyMetadata) , uvidíte, že každá Aquarium instance získá dvě položky kolekce! Je to proto, že obě Fish instance se přidají do stejného seznamu, který sdílí všechny instance třídy Aquarium. Takže pokud je záměr pro každou instanci objektu, aby měla svůj vlastní seznam, měla by být výchozí hodnota nastavena v konstruktoru třídy.

Inicializace kolekce pro čtení i zápis

Následující příklad deklaruje vlastnost závislost typu kolekce pro čtení i zápis ve Aquarium třídě pomocí metod Register(String, Type, Type) podpisu bez klíče a 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

FreezableCollection – vlastnosti závislosti

Vlastnost závislosti typu kolekce automaticky nehlásí změny v jejích dílčích obdobích. V důsledku toho platí, že pokud jste vázáni na kolekci, nemusí se vazba hlásit změny, což zneplatní některé scénáře datových vazeb. Pokud však použijete FreezableCollection<T> pro typ vlastnosti závislosti, změny vlastností elementů kolekce jsou správně hlášeny a vazba funguje podle očekávání.

Chcete-li povolit vazbu subproperty v kolekci závislostí objekty, použijte typ FreezableCollectionkolekce s omezením typu jakékoli DependencyObject odvozené třídy.

Následující příklad deklaruje Aquarium třídu, která obsahuje FreezableCollection s omezením FrameworkElementtypu . Výchozí hodnota kolekce není zahrnuta v PropertyMetadata předané RegisterReadOnly(String, Type, Type, PropertyMetadata) metodě a místo toho se konstruktor třídy používá k nastavení výchozí hodnoty kolekce na novou 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

Viz také