Sdílet prostřednictvím


Závislostní vlastnosti typu kolekce

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

Požadavky

Článek 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.

Implementujte vlastnost závislosti typu kolekce

Obecně platí, že model implementace vlastnosti závislosti je obálka vlastností CLR zálohovaná identifikátorem DependencyProperty namí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 typ elementu kolekce je DependencyObject nebo Freezable odvozené třídy.

Inicializujte kolekci

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 referenčního typu, protože tato hodnota bude přiřazena ke všem instancím třídy, čímž se vytvoří jediná instance třídy.

Následující příklad deklaruje třídu Aquarium, která obsahuje kolekci FrameworkElement elementů v obecné List<T>. Výchozí hodnota kolekce není zahrnuta v PropertyMetadata předaném metodě RegisterReadOnly(String, Type, Type, PropertyMetadata), a místo toho konstruktor třídy nastavuje výchozí hodnotu 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ří dvě samostatné instance Aquarium a přidá do každé kolekce odlišnou položku Fish. Pokud kód spustíte, uvidíte, že každá instance Aquarium 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 jako PropertyMetadata metodě RegisterReadOnly(String, Type, Type, PropertyMetadata), uvidíte, že každá instance Aquarium získá dvě položky kolekce! Je to proto, že oba Fish instance jsou přidány do stejného seznamu, který je sdílen všemi instancemi 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.

Inicializujte kolekci pro čtení i zápis

Následující příklad deklaruje vlastnost závislostí typu kolekce pro čtení i zápis ve třídě Aquarium pomocí metod podpisu bez klíče Register(String, Type, Type) 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 ve svých dílčích vlastnostech. V důsledku toho, pokud se vážete na kolekci, vazba nemusí hlásit změny, čímž se zneplatní některé scénáře datových vazeb. Pokud však použijete FreezableCollection<T> pro typ vlastnosti závislosti, změny vlastností prvků kolekce jsou správně hlášeny a vazba funguje podle očekávání.

Chcete-li povolit vazbu na "subproperty" v kolekci závislostních objektů, použijte typ kolekce FreezableCollections omezením typu na jakoukoli třídu odvozenou z DependencyObject.

Následující příklad deklaruje třídu Aquarium, která obsahuje FreezableCollection s omezením typu FrameworkElement. Do PropertyMetadata předané metodě RegisterReadOnly(String, Type, Type, PropertyMetadata) není zahrnuta výchozí hodnota kolekce 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é