集合型別相依性屬性
本主題提供如何實作屬性型別為集合型別之相依性屬性的方針與建議模式。
這個主題包含下列章節。
- 實作集合型別相依性屬性
- 不使用預設值初始化集合
- 報告集合屬性中的繫結值變更
- 相關主題
實作集合型別相依性屬性
對於一般相依性屬性,您所遵循的實作模式是您會定義 CLR 屬性包裝函式,而其中該屬性是受 DependencyProperty 識別項支援,而非欄位或其他建構。 您在實作集合型別屬性時也會遵循相同的模式。 不過,每當集合內含的型別本身是 DependencyObject 或 Freezable 衍生類別 (Derived Class) 時,集合型別屬性都會增加此模式的些許複雜度。
不使用預設值初始化集合
建立相依性屬性時,您不會指定屬性預設值做為初始欄位值, 而會透過相依性屬性中繼資料指定預設值。 如果您的屬性是參考型別 (Reference Type),相依性屬性中繼資料中指定的預設值不是每個執行個體 一個預設值,而是一個預設值適用於該型別的所有執行個體。 因此,您必須小心,避免使用集合型別屬性中繼資料所定義的單一靜態集合,做為新建立之型別執行個體的工作預設值。 您必須刻意將集合值設定為唯一 (執行個體) 集合,做為類別建構函式邏輯的一部分, 否則將會建立非預期的單一類別。
參考下列範例: 下列範例區段顯示 Aquarium 類別的定義。 此類別定義集合型別相依性屬性 AquariumObjects,這個屬性使用具有 FrameworkElement 型別條件約束的泛型 List<T> 型別。 在相依性屬性的 Register(String, Type, Type, PropertyMetadata) 呼叫中,中繼資料會將預設值建立為新的泛型 List<T>。
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
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); }
...
}
不過,若您保留顯示的程式碼不變,Aquarium 的所有執行個體會共用單一清單預設值。 如果執行下列測試程式碼,您會看到意外的結果 (這個測試程式碼的目的是要顯示如何具現化 (Instantiate) 兩個個別 Aquarium 執行個體,並將不同的單一 Fish 加入至每個執行個體):
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 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");
每個集合擁有的計數不是一,而是二!! 這是因為每個 Aquarium 都會將其 Fish 加入至預設值集合,而這是中繼資料中單一建構函式呼叫所產生的結果,因此會在所有的執行個體間共用。 這種情況應該不是您想要的結果。
若要更正這個問題,您必須將集合相依性屬性值重設成唯一執行個體,做為類別建構函式邏輯呼叫的一部分。 因為屬性是唯讀的相依性屬性,所以您必須使用 SetValue(DependencyPropertyKey, Object) 方法,利用只能在類別內存取的 DependencyPropertyKey 來設定此屬性。
Public Sub New()
MyBase.New()
SetValue(AquariumContentsPropertyKey, New List(Of FrameworkElement)())
End Sub
public Aquarium() : base()
{
SetValue(AquariumContentsPropertyKey, new List<FrameworkElement>());
}
現在,如果重新執行相同的測試程式碼,您就可以看到預期的結果,也就是每個 Aquarium 都支援其本身的唯一集合。
如果您選擇讓集合屬性變成讀寫集合,這個模式可能稍有不同。 在這種情況下,您可以從建構函式呼叫公用 (Public) set 存取子 (Accessor) 來執行初始設定,而初始設定仍會使用公用 DependencyProperty 識別項,在您的 set 包裝函式內呼叫 SetValue(DependencyProperty, Object) 的非索引鍵簽章 (Signature)。
報告集合屬性中的繫結值變更
本身為相依性屬性的集合屬性並不會自動報告其子屬性的變更。 如果您要在集合中建立繫結,這可能會讓該繫結無法報告變更,因而導致某些資料繫結 (Data Binding) 案例失效。 不過,如果您使用集合型別 FreezableCollection<T> 做為集合型別,便可正確報告集合中內含項目的子屬性變更,而且繫結也可如預期般運作。
若要在相依性物件集合中啟用子屬性繫結,請使用該集合對任何 DependencyObject 衍生類別的型別條件約束,將集合屬性建立成型別 FreezableCollection<T>。