共用方式為


集合類型的相依性屬性

本主題提供如何實作屬性類型為集合類型之相依性屬性的指引和建議模式。

實作集合類型相依性屬性

若為一般的相依性屬性,您要遵循的實作模式,是定義 CLR 屬性包裝函式,其屬性由 DependencyProperty 識別碼所倒轉,不是由欄位或其他建構所倒轉。 當您實作集合類型屬性時,您可以遵循此相同的模式。 不過,每當集合內包含的類型本身是 DependencyObjectFreezable 衍生類別時,集合類型屬性會略為增添模式的複雜性。

初始化超過預設值的集合

建立相依性屬性時,未指定屬性預設值為初始欄位值。 而是透過相依性屬性中繼資料指定預設值。 如果屬性是參考類型,則在相依性屬性中繼資料中指定的預設值不是每個執行個體的預設值,而是適用於所有類型執行個體的預設值。 因此您必須小心不要使用集合屬性中繼資料所定義的單一靜態集合,當成您類型新建執行個體的工作預設值。 您必須改確定刻意將唯一 (執行個體) 集合的集合值設定為類別建構函式邏輯的一部分。 否則會建立意外的單一類別。

請思考一下下列範例。 以下範例區段為類別 Aquarium 的定義,其中談到預設值的瑕疵。 該類別定義了集合類型相依性屬性 AquariumObjects,該屬性使用具有 FrameworkElement 類型限制的泛 List<T> 類型。 Register(String, Type, Type, PropertyMetadata) 呼叫相依性屬性時,中繼資料會建立預設值,作為新的 List<T> 泛型。

警告

以下程式碼無法正確運作。

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

不過,如果程式碼保持與範例一致,則單一清單預設值會供 Aquarium 的所有執行個體共用。 如果執行下列測試程式碼,它原是要示範如何具現化兩個不同的 Aquarium 執行個體,並為它們都新增單一不同的 Fish,您會看到令人驚訝的結果︰

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")

不是每個集合都有一個計數,而是每個集合都有兩個計數! 這是因為每個 Aquarium 都將自己的 Fish 新增至預設值集合,由中繼資料中的單一建構函式呼叫所致,因此在所有執行個體之間共用。 這種情況根本就不是您想要的。

若要修正這個問題,您必須將集合相依性屬性值重設為唯一的執行個體,當成類別建構函式呼叫的一部分。 該屬性是唯讀相依性屬性,您可以使用 SetValue(DependencyPropertyKey, Object) 方法加以設定,方法為使用只能在該類別存取的 DependencyPropertyKey

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

現在,如果再次執行相同的測試程式碼,您會看到更符合預期的結果,其中每個 Aquarium 都支援自己的唯一集合。

如果選擇讓您的集合屬性為可讀寫,則這個模式可能要略加修改。 在這種情況下,您可以使用建構函式呼叫公用集合存取子來執行初始化,這麼做的呼叫對象仍然是您集合包裝函式內的 SetValue(DependencyProperty, Object) 非索引鍵簽章,用的是公用 DependencyProperty 識別碼。

報告集合屬性中的繫結值變更

本身為相依性屬性的集合屬性不會自動報告其子屬性變更。 如果您要建立集合繫結,這可以防止繫結報告變更,因而使某些資料繫結案例失效。 不過,如果您使用集合類型 FreezableCollection<T> 作為集合類型,則會正確報告集合中所含項目的子屬性變更,且繫結如預期正常運作。

若要在相依性物件集合中啟用子屬性繫結,集合屬性請建立為 FreezableCollection<T> 類型,且將該集合的類型條件限制為任何 DependencyObject 衍生類別。

另請參閱