依賴關係屬性和通知

依賴關係屬性和通知
http://www.21tx.com 2009年02月09日msdn

  

依賴關係屬性和通知目錄

  依賴關係屬性基礎知識

  綁定源與目標

  使用通知自定義集合

  依賴關係屬性和事件

  動態綁定技術

  Freezable 差異

  使用 DependencyPropertyDescriptor

  DependencyObjects 集合

  這些天來,對象似乎已經忙得暈頭轉向了。每個人都希望它們做這做那。Windows® Presentation Foundation (WPF) 應用程序中的典型對象會接到各種各樣不同的請求:有要求綁定到數據的、有要求更改樣式的、有要求從可見父項繼承的,甚至還有要求來點動畫讓大家高興一下的。

  對象怎麼才能建立起邊界和優先級呢?WPF 的回答是一種稱爲依賴關係屬性的功能。通過爲 WPF 類提供結構化方法來響應由數據綁定、樣式、繼承和其他來源更改帶來的變化,依賴關係屬性已變得十分重要,其程度不亞於事件和事件處理對早期.net 程序員的重要性。

  當然,依賴關係屬性不是萬能的,它可能無法爲某些傳統任務提供您所需要的所有功能。假設您可以訪問具有依賴關係屬性的對象,並且希望在其中某個屬性發生變化時得到通知。這對於事件處理程序來說好像並不是什麼難事——但您要知道實際上根本不存在這樣的事件!

  當您使用對象集合時,這個問題更加突出。在某些情況下,您可能希望當集合中對象的某些特定依賴關係屬性發生更改時得到通知。現有唯一的解決方案是 FreezableCollection<T>,它能夠告訴您何時發生變化——但不能告訴您是什麼發生了變化。

  換句話說,依賴關係屬性並不總能與其他各方良好協作。本專欄主要講述的就是如何彌補它們在通知方面的不足。

  依賴關係屬性基礎知識

  假設您正在設計名爲 PopArt 的 WPF 類,並且希望定義類型爲 Brush 的屬性 SwirlyBrush。如果 PopArt 繼承自 DependencyObject,您就可以將 SwirlyBrush 定義爲 DependencyProperty。第一步是公共靜態只讀字段:

public static readonly DependencyProperty SwirlyBrushProperty;

  依賴關係屬性與該屬性具有相同的名稱,但附加了 "Property" 字樣。它是字段聲明或靜態構造函數的一部分,您可以隨後註冊依賴關係屬性:

SwirlyBrushProperty = DependencyProperty.ReGISter("SwirlyBrush",
 typeof(Brush), typeof(PopArt),
 new PropertyMetadata(OnSwirlyBrushChanged));

  您還需要能夠提供對該屬性進行正常訪問的傳統屬性定義(有時稱爲 CLR 屬性):

public Brush SwirlyBrush {
 set { SetValue(SwirlyBrushProperty, value); }
 get { return (Brush) GetValue(SwirlyBrushProperty); }
}

  SetValue 和 GetValue 方法由 DependencyObject 定義,這就是所有定義依賴關係屬性的類必需從該類派生的原因。除調用這兩種方法外,CLR 屬性不應當包含任何其他代碼。CLR 屬性通常被認爲是由依賴關係屬性支持的。

  依賴關係屬性註冊中引用的 OnSwirlyBrushChanged 方法是一種回調方法,任何時候只要 SwirlyBrush 屬性發生變化便會調用該方法。因爲該方法與靜態字段關聯,所以它自身也必須是靜態方法:

static void OnSwirlyBrushChanged(DependencyObject obj,
 DependencyPropertyChangedEventArgs args) {
 ...
}

  第一個參數是屬性發生改變的類的特定實例。如果已經在名爲 PopArt 的類中定義過此依賴關係屬性,則第一個參數始終是類型爲 PopArt 的對象。我喜歡使用與靜態方法相同的名稱定義實例方法。靜態方法隨後按照如下方式調用實例方法:

static void OnSwirlyBrushChanged(DependencyObject obj,
 DependencyPropertyChangedEventArgs args) {
 (obj as PopArt).OnSwirlyBrushChanged(args);
}
void OnSwirlyBrushChanged(DependencyPropertyChangedEventArgs args) {
 ...
}

  該實例方法中包含用於應對 SwirlyBrush 值更改所需的所有項目。

  在代碼中,您可以採用正常方式在 PopArt 的實例中設置 SwirlyBrush 屬性的值:

popart.SwirlyBrush = new SolidColorBrush(Colors.AliceBlue);

  但是,還需要注意 PopArt 類中存在名爲 PopArt.SwirlyBrushProperty 的公共靜態字段。該字段是類型爲 DependencyProperty 的有效對象,獨立於所有類型爲 PopArt 的對象存在。這使您能夠在創建具有該屬性的對象之前引用類的特定屬性。

  由 DependencyObject 定義的 SetValue 方法也是公共的,因此您可以採用如下方式設置 SwirlyBrush 屬性:

popart.SetValue(PopArt.SwirlyBrushProperty,
 new SolidColorBrush(Colors.AliceBlue));

  此外,如果具有如圖 1 所示的三個對象,您可以使用以下這段完全通用的代碼設置屬性:

target.SetValue(property, value);

依賴關係屬性和通知圖 1 對象

對象 說明
目標 DependencyObject 派生類的實例。
屬性 DependencyProperty 類型的對象。
包含正確類型的依賴關係屬性的對象。

  如果值類型與 DependencyProperty 關聯的類型(本例中爲 Brush)不一致,則會拋出異常。但 DependencyProperty 定義的 IsValidType 方法可以幫助避免此類問題。

  使用 DependencyProperty 對象與 SetValue 和 GetValue 方法引用和設置屬性的過程,比早期通過 "SwirlyBrush" 等字符串引用屬性更爲簡單明瞭。採用這種方法指定的屬性需要反射來實際設置屬性。

  綁定源和目標

  依賴關係屬性在 WPF 中的廣泛使用在設置 XAML 形式的數據綁定、樣式和動畫時並不明顯,但當您在代碼中完成這些任務時卻是顯而易見的。BindingOperations 和FrameworkElement 定義的 SetBinding 方法需要類型爲 DependencyProperty 的對象作爲綁定目標。WPF 樣式中使用的 Setter 類需要 DependencyProperty 對象。BeginAnimation 方法還需要使用 DependencyProperty 對象作爲動畫目標。

  這些目標必須都是 DependencyProperty 對象才能使 WPF 施加合適的優先權規則。例如,動畫設置的屬性值比樣式設置的屬性值優先權高。(如果需要了解有哪些源負責特定的屬性值,您可以使用 DependencyPropertyHelper 類。)

  儘管數據綁定目標必須是依賴關係屬性,但綁定源可以不必是依賴關係屬性。很顯然,如果綁定需要能夠成功監控源中的變化,則綁定源必須採納某種通知機制,而 WPF 實際上允許三種不同類型的源通知。並且這幾種類型是互斥的。

  第一種(同時也是首選)方法是對源和目標均使用依賴關係屬性。如相同的屬性在不同上下文中分別擔當綁定源和目標兩種角色,那麼這是一種不錯的解決方案。

  第二種方法包括根據屬性名稱定義事件。舉例來說,如果某個類定義了名爲 Flavor 的屬性,則它還將定義名爲 FlavorChanged 的事件。顧名思義,該類將在 Flavor 的值發生變化時觸發此事件。這種類型的通知在Windows 窗體中廣泛使用(例如,由 Control 定義的 Enabled 屬性具有相應的 EnabledChanged 事件)。但應該避免在 WPF 代碼中使用這種方法,原因在此不再贅述。

  最後一種可行的方法是實現 INotifyPropertyChanged 接口,它要求類根據 PropertyChangedEventHandler 委託定義名爲 PropertyChanged 的事件。相關聯的 PropertyChangedEventArgs 對象具有名爲 PropertyName 的屬性,它代表發生更改的屬性的名稱字符串。

  例如,如果類具有名爲 Flavor 的屬性和名爲 flavor 的私有字段,則該屬性的 set 訪問器如下所示:

flavor = value;
if (PropertyChanged != null)
 PropertyChanged(this, new PropertyChangedEventArgs("Flavor");

  對 Flavor 屬性變化感興趣的類只需訂閱 PropertyChanged 事件,即可在該屬性(或該類中任何其他屬性)發生變化時得到通知。

  INotifyPropertyChanged 接口並不能做到萬無一失。該接口所能控制的僅是名爲 PropertyChanged 的事件。並且不能保證該類一定會觸發此事件。在許多情況下,類會爲某些公共屬性觸發該事件,但並不一定會爲所有屬性觸發該事件。如果您無法訪問源代碼,那沒有什麼很好的方法能夠事先了解哪些屬性會觸發 PropertyChanged,而哪些屬性不會觸發該事件。

  無論如何,INotifyPropertyChanged 對於那些不會成爲 WPF 數據綁定、樣式或動畫目標的屬性來說仍然是一種優秀、簡單的解決方案,但它必須提供對其他類的更改通知。

  使用通知自定義集合

  有時可能需要使用對象集合,並且需要在其中某個對象的屬性發生變化時得到通知。最有價值的通知能夠準確告訴您集合中哪一個項目發生了變化,並且同時還能指出該項中哪個屬性發生了變化。

  如集合中的所有對象都實現了 INotifyPropertyChanged 接口且集合本身實現了 InotifyCollectionChanged,這項任務相對就比較簡單。當新項目添加到集合,或者刪除現有項目時,實現該接口的集合會觸發 CollectionChanged 事件。CollectionChanged 事件可以提供這些添加或刪除項目的列表。或許泛型 ObservableCollection<T> 類是實現了 INotifyCollectionChanged 的最流行的集合對象。

  讓我們創建一個派生自 ObservableCollection<T> 的自定義集合類,並實現名爲 ItemPropertyChanged 的新事件。集合中任何項目的屬性發生變化均會觸發該事件,並且伴隨該事件的參數還包括項和發生變化的屬性。圖 2 顯示派生自 PropertyChangedEventArgs 的 ItemPropertyChangedEventArgs 類,它包含類型爲對象、名稱爲 Item 的新屬性。圖 2 還顯示了 ItemPropertyChangedEventHandler 委託。

依賴關係屬性和通知圖 2 ItemPropertyChangedEventArgs

public class ItemPropertyChangedEventArgs : PropertyChangedEventArgs {
 object item;
 public ItemPropertyChangedEventArgs(object item,
  string propertyName) : base(propertyName) {
  this.item = item;
 }
 public object Item {
  get { return item; }
 }
}
public delegate void ItemPropertyChangedEventHandler(object sender,
 ItemPropertyChangedEventArgs args);

  ObservableNotifiableCollection<T> 派生自 ObservableCollection<T>,並且定義瞭如圖 3 所示的 ItemPropertyChanged 事件。請注意,將類型參數限制爲 INotifyPropertyChanged 類型的對象。在 OnCollectionChanged 重寫過程中,該類將爲每個添加到集合的項目註冊一個 PropertyChanged 事件,並在從集合中刪除項目時刪除該事件。在項目的 PropertyChanged 事件中,集合將觸發 ItemPropertyChanged 事件。PropertyChanged 事件的調用方對象(即屬性發生變化的項目)將成爲 ItemPropertyChangedEventArgs 的 Item 屬性。

依賴關係屬性和通知圖 3 ObservableNotifiableCollection<T> 類

class ObservableNotifiableCollection<T> :
 ObservableCollection<T> where T : INotifyPropertyChanged {
 public ItemPropertyChangedEventHandler ItemPropertyChanged;
 protected override void OnCollectionChanged(
  NotifyCollectionChangedEventArgs args) {
  base.OnCollectionChanged(args);
  if (args.NewItems != null)
   foreach (INotifyPropertyChanged item in args.NewItems)
    item.PropertyChanged += OnItemPropertyChanged;
  if (args.OldItems != null)
   foreach (INotifyPropertyChanged item in args.OldItems)
    item.PropertyChanged -= OnItemPropertyChanged;
 }
 void OnItemPropertyChanged(object sender,
  PropertyChangedEventArgs args) {
  if (ItemPropertyChanged != null)
   ItemPropertyChanged(this,
    new ItemPropertyChangedEventArgs(sender,
    args.PropertyName));
 }
}

  圖 2 和 3 中所示的代碼是項目 ObservableNotifiableCollectionDemo 的一部分,本專欄的可下載代碼中包含該項目。它還包括一個小型演示程序。

  依賴關係屬性和事件

  儘管依賴關係屬性能夠很好地產生綁定源,但它不能以公共事件方式提供常規通知機制。第一次搜索該事件時,這項令人不快的事實常常會讓您感到有點震驚。

  但是,DependencyObject 確實定義了受保護的 OnPropertyChanged 方法。該方法是 DependencyObject 工作方式的基礎。根據我的經驗判斷,OnPropertyChanged 作爲調用 SetValue 的結果而被調用,並且調用與單個依賴關係屬性相關聯的回調方法的是 OnPropertyChanged。OnPropertyChanged 的文檔中包含大量重寫此方法的危險警告。

  如果正在編寫實現依賴關係屬性的類,可以想象:類的用戶希望在特定依賴關係屬性更改時觸發事件。在這種情況下,可以顯式提供這些事件。可以將事件命名爲屬性名後加單詞 "Changed"(類似於在 Windows 窗體中的事件命名方式),但使用 DependencyPropertyChangedEventHandler 委託定義事件。作爲模型,您會發現 UIElement 中的許多事件都與依賴關係屬性相關聯,其中包括 FocusableChanged 和 IsEnabledChanged。

  對於前面顯示的 PopArt 示例,您可以將 SwirlyBrushChanged 事件定義爲如下所示:

public event DependencyPropertyChangedEventHandler
 SwirlyBrushChanged;

  在前面顯示的 OnSwirlyBrushChanged 方法實例中,採用如下方式觸發事件:

if (SwirlyBrushChanged != null)
 SwirlyBrushChanged(this, args);

  這段代碼非常短小,但令人吃驚的是其他依賴關係屬性並沒有與之關聯的事件。

  如果您需要使用的類沒有所需的事件,那麼您必須尋找另一種方式以便在特定依賴關係屬性更改時收到通知。令人欣慰的是,至少有三種解決方案能應對這一挑戰:一種比較奇特,一種比較強硬,而最後一種既不奇特,也不強硬。

  動態綁定技術

  比較奇特的方法是動態創建依賴關係屬性和數據綁定,以此接收依賴關係屬性更改的通知。如果正在編寫繼承自 DependencyObject 的類,同時可以訪問包含依賴關係屬性的對象,並且需要在其中某個依賴關係屬性更改時得到通知,那您可以創建依賴關係屬性和動態綁定以便得到所需的信息。

  例如,假設您的類可以訪問名爲 txtblk 的 TextBlock 類型的對象,並且您希望知道 Text 屬性何時發生更改。Text 屬性由名爲 TextProperty 的 DependencyProperty 提供支持,但 TextBlock 並未定義 TextChanged 事件。爲修正這一問題,您需要首先註冊一個相同類型的 DependencyProperty 作爲希望監控的屬性:

DependencyProperty MyTextProperty =
 DependencyProperty.Register("MyText", typeof(string), GetType(),
 new PropertyMetadata(OnMyTextChanged));

  該 DependencyProperty 對象不需要是公共靜態字段。如此處所示,可以在實例方法內部註冊該對象。OnMyTextChanged 回調也可以作爲實例方法。

  當您註冊完 DependencyProperty 後,可以定義從 TextBlock 對象到該屬性的綁定:

Binding binding = new Binding("Text");
binding.Source = txtblk;
BindingOperations.SetBinding(this, MyTextProperty, binding);

  現在對 txtblk 對象 Text 屬性的任何更改都將調用您的 OnMyTextChanged 回調。

  您甚至可以在對所監控的 DependencyObject 和 DependencyProperty 一無所知的情況下注冊該 DependencyProperty 並定義回調。假設您僅知道需要跟蹤 DependencyObject 派生類 obj 中名爲 Property 的 DependencyProperty 變量,代碼如下:

DependencyProperty OnTheFlyProperty =
 DependencyProperty.Register("OnTheFly",
 property.PropertyType,
 GetType(),
 new PropertyMetadata(OnTheFlyChanged));
Binding binding = new Binding(property.Name);
binding.Source = obj;
BindingOperations.SetBinding(this, OnTheFlyProperty, binding);

  此解決方案最適合單個對象的一個或多個屬性。當處理相同類型的多個對象(例如集合中的對象)時,您需要爲每個對象的每個屬性創建唯一的動態依賴關係屬性和綁定。這樣整個架構很快就會變得無法控制。

  Freezable 差異

  Freezable 類是接收有關依賴關係對象更改的較爲強硬的方法。它確實可以完成工作,但您無法實際指出這一強硬措施針對的目標是什麼。

  Freezable 替代由 DependencyObject 定義的 OnPropertyChanged 方法,並且定義了名爲 Changed 的新屬性,根據文檔說明,該屬性將在“修改此 Freezable 或其所包含的任何對象”(增加的強調)時觸發。在這段話中,只要將“其所包含的任何對象”理解爲由依賴關係屬性支持的 Freezable 類型的子屬性,那麼結果確實是正確的。

  例如,假設名爲 A 的 Freezable 類包含 B 類型名爲 B 的屬性。B 類同樣派生自 Freezable 並且包含 C 類型名爲 C 的屬性。C 類也派生自 Freezable 幷包含 double 類型名爲 D 的屬性。所有三個屬性都由依賴關係屬性支持。創建所有這些類型的對象;更改屬性 D;則類 A、B 和 C 都會觸發 Changed 事件。(請注意,本專欄源代碼下載中包含的 NestedFreezableNotificationsDemo 項目可以演示此情形。)

  主要問題在於該 Changed 事件基於 EventHandler 委託,並且無法準確通告 Freezable 對象中哪個屬性(或子屬性)發生了變化。並且這些通知的開銷較大,而這正是 Freezable 類因其無法更改且使所有通知消失而得名的原因。

  Freezable 在 WPF 圖形系統中使用廣泛。Brush、Pen、Geometry 和 Drawing 類都派生自 Freezable。有幾種集合也派生自 Freezable,其中包括 DoubleCollection、PointCollection、VectorCollection 和 Int32Collection。這些集合還實現了 Changed 事件,當集合發生更改時將觸發該事件。其他由 Freezable 對象組成的 Freezable 集合(例如 GeometryCollection 和 TransformCollection)會在集合中某個項目的任何屬性或子屬性發生更改時觸發 Changed 事件。

  例如,PathGeometry 派生自 Freezable。它包含同樣派生自 Freezable 的 PathFigureCollection 類型的 Figures 屬性。PathFigureCollection 包含同樣派生自 Freezable 的 PathFigure 類型的對象。PathFigure 包含名爲 Segments 的屬性,它屬於 PathSegmentCollection 類型,也派生自 Freezable。PathSegment 派生自 Freezable,並且是 PolyLineSegment 和 PolyBezierSegment 等類的基類。這些類包含名爲 Points 的屬性,它同樣派生自 Freezable,類型爲 PointCollection。

  總體效果:如任何單獨的點發生變化,更改通知將沿對象層次結構向上傳遞,並最終引起圖形對象完全重繪。

  PathGeometry 是否嘗試準確指出哪個點發生了變化,並僅更改相應部分的圖形?換句話說,它是否執行增量更新?從其可以獲得的信息來判斷,它不可能做到這一點。PathGeometry 所知道的僅是某個點發生了變化,需要一切從頭再來。

  如果這樣(僅通知嵌套子屬性集合中的某個屬性發生更改)能夠滿足您的要求,那 Freezable 類將會是非常理想的選擇。還有一個很有用的泛型 FreezableCollection<T>。類型參數可以不必是 Freezable 類型。它只需是 DependencyObject 即可。但如果是 Freezable 類型,集合項目的屬性或 Freezable 子屬性的更改會使集合觸發 Changed 事件。

  請記住 Freezable 類存在某些限制。如果從 FreezableCollection<T> 派生類,您必須重寫 CreateInstanceCore(只需調用類的構造函數並返回該對象),否則您可能會得到令您抓狂的奇怪錯誤。

  使用 DependencyPropertyDescriptor

  第三種從依賴關係屬性獲取通知事件的技術是採用 DependencyPropertyDescriptor 類。這確實不是顯而易見的解決方案,因爲在文檔中介紹這種技術“主要供應用程序設計人員使用”,但它的確有效,而這纔是最重要的。

  再次假設您有類型爲 TextBlock 的對象,並且您希望知道 Text 屬性何時發生更改。首先您需要創建類型爲 DependencyPropertyDescriptor 的對象:

DepedencyPropertyDescriptor descriptor =
 DependencyPropertyDescriptor.FromProperty(
 TextBlock.TextProperty, typeof(TextBlock));

  第一個參數是 DependencyProperty 對象,第二個參數是擁有該依賴關係屬性或繼承它的類。請注意,DependencyPropertyDescriptor 並未與任何特定的 TextBlock 對象相關聯。

  然後您可以註冊事件處理程序,以檢測特定 TextBlock 對象(例如名爲 txtblk 的對象)中 Text 屬性的變化:

descriptor.AddValueChanged(txtblk, OnTextChanged);

  請記住還需要 RemoveValueChanged 方法以便刪除事件處理程序。

  OnTextChanged 事件處理程序基於簡單的 EventHandler 委託簽名,並很可能採用如下方式定義:

void OnTextChanged(object sender, EventArgs args) {
 ...
}

  請注意,EventArgs 參數並不提供任何信息。但是,傳遞給事件處理程序的第一個參數是 Text 值發生變化的 TextBlock 對象。

  您可能不希望在多個依賴關係屬性間共享這些事件處理程序——您希望爲每個屬性提供單獨的事件處理程序。但您可以輕鬆在多個對象之間共享這些事件處理程序,因爲可以通過傳遞給事件處理程序的第一個參數區分對象。

  現在萬事俱備,最後一步就是構建集合,它在集合中的項目依賴關係屬性發生變化時會觸發通知。

  DependencyObjects 集合

  理論上講,如果有派生自 DependencyObject 類型的對象集合,您應該能夠爲特定依賴關係屬性創建 DependencyPropertyDescriptor 對象,以便在集合中任何項目的這些屬性發生變化時得到通知。

  處理來自 DependencyPropertyDescriptor 的通知的方法會有一些妨礙。您不希望在多個依賴關係屬性間共享一個方法,因爲這樣您將無法確定哪個依賴關係屬性發生了更改。您希望爲每個依賴關係屬性創建單獨的方法。通常,無法在運行之前確定所需的方法個數。雖然可以在運行時動態創建方法,但這會產生中間語言。相對簡單的方法是:定義一個包含專門處理這些通知的方法的類,然後爲每個希望監控的依賴關係屬性創建一個該類的實例。

  我創建了 ObservableDependencyObjectCollection<T> 集合類用來完成這項工作,它派生自 ObservableCollection<T>。請注意,此集合中的項目必須是派生自 DependencyObject 的類的實例。集合類爲每個由該類型參數定義或繼承的 DependencyProperty,或者爲選定的 DependencyProperty 對象列表創建 DependencyPropertyDescriptor 對象。

  ObservableDependencyObjectCollection<T> 定義了名爲 ItemDependencyPropertyChanged 的新事件。圖 4 顯示該事件參數的定義和對此事件的委託。ItemDependencyPropertyChangedEventArgs 類似於正常的 DependencyPropertyChangedEventArgs,不同之處在於它包含 Item 屬性而沒有 OldValue 屬性。

依賴關係屬性和通知圖 4 ItemDependencyPropertyChangedEventArgs

public struct ItemDependencyPropertyChangedEventArgs {
 DependencyObject item;
 DependencyProperty property;
 object newValue;
 public ItemDependencyPropertyChangedEventArgs(
  DependencyObject item,
  DependencyProperty property,
  object newValue) {
  this.item = item;
  this.property = property;
  this.newValue = newValue;
 }
 public DependencyObject Item {
  get { return item; }
 }
 public DependencyProperty Property {
  get { return property; }
 }
 public object NewValue {
  get { return newValue; }
 }
}
public delegate void ItemDependencyPropertyChangedEventHandler(
 Object sender,
 ItemDependencyPropertyChangedEventArgs args);

  我將分三個部分爲您介紹實際的 ObservableDependencyObjectCollection<T> 類。類的第一部分如圖 5 所示。儘管該類派生自 ObservableCollection<T>,但它將類型參數限制爲 DependencyObject。ObservableCollection<T> 有兩個構造函數:一個不帶參數,而另一個使用泛型 List<T> 對象初始化內容。新類不僅包括這兩個構造函數,而且它還添加了另外兩個構造函數用於指定希望監控的 DependencyProperty 對象列表。

  例如,假設您希望監控 Button 對象集合,但您只希望在兩個與字體相關的屬性發生變化時得到通知。此構造函數如下:

ObservableDependencyObjectCollection<Button> buttons =
 new ObservableDependencyObjectCollection(
 Button.FontSize, Button.FontFamily);

  您可以隨後爲該集合安裝事件處理程序:

buttons.ItemDependencyPropertyChanged +=
 OnItemDependencyPropertyChanged;

  圖 5 中的所有構造函數最終都會調用 GetExplicitDependencyProperties 或 GetAllDependencyProperties。第一種方法僅爲構造函數參數中所列出的每個 DependencyProperty 對象調用 CreateDescriptor。第二種方法使用反射獲取由類型參數及其祖先(即 BindingFlags.FlattenHierarchy 的用意)共同定義的 DependencyProperty 類型的所有公共字段,並隨後爲每個對象調用 CreateDescriptor。

依賴關係屬性和通知圖 5 ObservableDependencyObjectCollection<T> 構造函數

public class ObservableDependencyObjectCollection<T> :
 ObservableCollection<T> where T : DependencyObject {
 public event ItemDependencyPropertyChangedEventHandler
  ItemDependencyPropertyChanged;
 List<DescriptorWrapper> descriptors = new List<DescriptorWrapper>();
 public ObservableDependencyObjectCollection() {
  GetAllDependencyProperties();
 }
 public ObservableDependencyObjectCollection(List<T> list) : base(list) {
  GetAllDependencyProperties();
 }
 public ObservableDependencyObjectCollection(
  params DependencyProperty[] properties) {
  GetExplicitDependencyProperties(properties);
 }
 public ObservableDependencyObjectCollection(List<T> list,
  params DependencyProperty[] properties) : base(list) {
  GetExplicitDependencyProperties(properties);
 }
 void GetExplicitDependencyProperties(
  params DependencyProperty[] properties) {
  foreach (DependencyProperty property in properties)
   CreateDescriptor(property);
 }
 void GetAllDependencyProperties() {
  FieldInfo[] fieldInfos =
   typeof(T).GetFields(BindingFlags.Public |
             BindingFlags.Static |
        BindingFlags.FlattenHierarchy);
  foreach (FieldInfo fieldInfo in fieldInfos)
   if (fieldInfo.FieldType == typeof(DependencyProperty))
    CreateDescriptor(fieldInfo.GetValue(null) as DependencyProperty);
 }
 void CreateDescriptor(DependencyProperty property) {
  DescriptorWrapper descriptor =
   new DescriptorWrapper(typeof(T), property,
   OnItemDependencyPropertyChanged);
   descriptors.Add(descriptor);
 }

  對 CreateDescriptor 方法的每次調用都將創建一個 DescriptorWrapper 類型的新對象,並向該對象傳遞集合中項目的類型、要監控的 DependencyProperty,以及名爲 OnItemDependencyPropertyChanged 的回調方法。

  圖 6 中所示的 DescriptorWrapper 類位於 ObservableDependencyObjectCollection<T> 的內部,實際封裝 DependencyPropertyDescriptor 對象。構造函數保存與 DependencyPropertyDescriptor 和回調方法相關聯的特定 DependencyProperty。

依賴關係屬性和通知圖 6 DescriptorWrapper 類

class DescriptorWrapper {
 DependencyPropertyChangedEventHandler
  OnItemDependencyPropertyChanged;
 DependencyPropertyDescriptor desc;
 DependencyProperty dependencyProperty;
 public DescriptorWrapper(Type targetType,
  DependencyProperty dependencyProperty,
  DependencyPropertyChangedEventHandler
  OnItemDependencyPropertyChanged) {
  desc = DependencyPropertyDescriptor.FromProperty(
   dependencyProperty, targetType);
  this.dependencyProperty = dependencyProperty;
  this.OnItemDependencyPropertyChanged =
   OnItemDependencyPropertyChanged;
 }
 public void AddValueChanged(DependencyObject dependencyObject) {
  desc.AddValueChanged(dependencyObject, OnValueChanged);
 }
 public void RemoveValueChanged(DependencyObject dependencyObject) {
  desc.RemoveValueChanged(dependencyObject, OnValueChanged);
 }
 void OnValueChanged(object sender, EventArgs args) {
  OnItemDependencyPropertyChanged(sender,
   new DependencyPropertyChangedEventArgs(
   dependencyProperty,
   null,
   (sender as DependencyObject).GetValue(
   dependencyProperty)));
 }
}

  該類還包括兩種分別名爲 AddValueChanged 和 RemoveValueChanged 的方法,它們會調用 DependencyPropertyDescriptor 對象中的相應方法。OnValueChanged 事件處理程序將篩選返回集合類的信息。

  ObservableDependencyObjectCollection<T> 類的最後一部分如圖 7 所示。OnCollectionChanged 的替代方法負責遍歷所有添加到集合的項目(以及每個 DescriptorWrapper),並調用 AddValueChanged。每個從集合中刪除的項目都會刪除相應的事件處理程序。

依賴關係屬性和通知圖 7 ObservableDependencyObjectCollection<T>

protected override void OnCollectionChanged(
 NotifyCollectionChangedEventArgs args) {
 base.OnCollectionChanged(args);
 if (args.NewItems != null)
  foreach (DependencyObject obj in args.NewItems)
   foreach (DescriptorWrapper descriptor in descriptors)
    descriptor.AddValueChanged(obj);
 if (args.OldItems != null)
  foreach (DependencyObject obj in args.OldItems)
   foreach (DescriptorWrapper descriptor in descriptors)
    descriptor.RemoveValueChanged(obj);
}
protected void OnItemDependencyPropertyChanged(object item,
 DependencyPropertyChangedEventArgs args) {
 if (ItemDependencyPropertyChanged != null)
  ItemDependencyPropertyChanged(this,
   new ItemDependencyPropertyChangedEventArgs(
   item as DependencyObject,
   args.Property, args.NewValue));
}

  ObservableDependencyObjectCollection<T> 類的最後一個 OnItemDependencyPropertyChanged 方法從 DescriptorWrapper 中調用,並會觸發 ItemDependencyPropertyChanged 事件,它是整個練習的目的。

  ObservableDependencyObjectCollection<T> 類是結構化的類,因此它將在根據類型參數構造集合時創建所有必需的 DescriptorWrapper 對象。如果將類型參數指定爲 DependencyObject 並使用無參數的構造函數,則不會創建任何 DescriptorWrapper 對象,因爲 DependencyObject 不會定義任何其自身的依賴關係屬性。但是,可以將類型參數指定爲 DependencyObject 幷包括 DependencyProperty 對象的顯式列表,這樣通知也能正常工作。

  在實際情形中,您可能希望將 ObservableDependencyObjectCollection<T> 的類型參數設置爲 FrameworkElement,並使用各種 FrameworkElement 的派生對象(例如 TextBlock 和 Button)填充集合。集合將僅報告 UIElement 和 FrameworkElement 定義的依賴關係屬性的更改,但不報告由派生類定義的屬性更改。

  爲提高靈活性,必須基於這些項目的類型和由這些類型定義的依賴關係屬性編寫集合,從而能在添加項目時創建新的 DescriptorWrapper 對象。因爲不希望創建重複的 DescriptorWrapper 對象,所以您需要首先檢查是否已經爲每種特殊依賴關係屬性創建過 DescriptorWrapper。

  當從集合中刪除項目時,您需要刪除不再需要的 DescriptorWrapper 對象。這意味着應該爲每個 DescriptorWrapper 連接使用計數,並且當使用計數減爲零時,應該從 ObservableDependencyObjectCollection<T> 的描述符集合中刪除 DescriptorWrapper。

  也可以創建非常靈活且監控所有項目屬性的集合類,而不必考慮項目是否定義了依賴關係屬性或是否實現了 INotifyPropertyChanged 接口。

  如您所見,只要正確處理,即使是依賴關係屬性也可以像 Microsoft® .NET Framework 中的前輩們一樣學會生成事件。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章