依赖关系属性和通知

依赖关系属性和通知
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 中的前辈们一样学会生成事件。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


什么是设计模式一套被反复使用、多数人知晓的、经过分类编目的、代码 设计经验 的总结;使用设计模式是为了 可重用 代码、让代码 更容易 被他人理解、保证代码 可靠性;设计模式使代码编制  真正工程化;设计模式使软件工程的 基石脉络, 如同大厦的结构一样;并不直接用来完成代码的编写,而是 描述 在各种不同情况下,要怎么解决问题的一种方案;能使不稳定依赖于相对稳定、具体依赖于相对抽象,避免引
单一职责原则定义(Single Responsibility Principle,SRP)一个对象应该只包含 单一的职责,并且该职责被完整地封装在一个类中。Every  Object should have  a single responsibility, and that responsibility should be entirely encapsulated by t
动态代理和CGLib代理分不清吗,看看这篇文章,写的非常好,强烈推荐。原文截图*************************************************************************************************************************原文文本************
适配器模式将一个类的接口转换成客户期望的另一个接口,使得原本接口不兼容的类可以相互合作。
策略模式定义了一系列算法族,并封装在类中,它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。
设计模式讲的是如何编写可扩展、可维护、可读的高质量代码,它是针对软件开发中经常遇到的一些设计问题,总结出来的一套通用的解决方案。
模板方法模式在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中,使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。
迭代器模式提供了一种方法,用于遍历集合对象中的元素,而又不暴露其内部的细节。
外观模式又叫门面模式,它提供了一个统一的(高层)接口,用来访问子系统中的一群接口,使得子系统更容易使用。
单例模式(Singleton Design Pattern)保证一个类只能有一个实例,并提供一个全局访问点。
组合模式可以将对象组合成树形结构来表示“整体-部分”的层次结构,使得客户可以用一致的方式处理个别对象和对象组合。
装饰者模式能够更灵活的,动态的给对象添加其它功能,而不需要修改任何现有的底层代码。
观察者模式(Observer Design Pattern)定义了对象之间的一对多依赖,当对象状态改变的时候,所有依赖者都会自动收到通知。
代理模式为对象提供一个代理,来控制对该对象的访问。代理模式在不改变原始类代码的情况下,通过引入代理类来给原始类附加功能。
工厂模式(Factory Design Pattern)可细分为三种,分别是简单工厂,工厂方法和抽象工厂,它们都是为了更好的创建对象。
状态模式允许对象在内部状态改变时,改变它的行为,对象看起来好像改变了它的类。
命令模式将请求封装为对象,能够支持请求的排队执行、记录日志、撤销等功能。
备忘录模式(Memento Pattern)保存一个对象的某个状态,以便在适当的时候恢复对象。备忘录模式属于行为型模式。 基本介绍 **意图:**在不破坏封装性的前提下,捕获一个对象的内部状态,并在该
顾名思义,责任链模式(Chain of Responsibility Pattern)为请求创建了一个接收者对象的链。这种模式给予请求的类型,对请求的发送者和接收者进行解耦。这种类型的设计模式属于行为
享元模式(Flyweight Pattern)(轻量级)(共享元素)主要用于减少创建对象的数量,以减少内存占用和提高性能。这种类型的设计模式属于结构型模式,它提供了减少对象数量从而改善应用所需的对象结