INotifyPropertyChanged 及其他 - 第二部分
添加对事件抑制和事件传播的支持。

引言
INotifyPropertyChanged
接口提供了一个标准事件,用于对象通知客户端其某个属性已更改。这对于数据绑定很有帮助,如本文 文章中所述,因为它允许绑定控件根据直接对底层对象所做的更改来更新其显示。虽然此事件达到了其目的,但仍有改进的空间。
在本文系列的 第一部分中,我们对 INotifyPropertyChanged
接口进行了一些改进。本文将继续在前一部分代码的基础上进行改进。我们将扩展代码以支持事件抑制和事件传播。为了避免本文过长,我将在后续文章中讨论批处理事件。
事件抑制
引言
每次属性更改时都会触发 PropertyChanged
和 PropertyChanging
事件,但有时我们可能不希望这些事件被触发。例如,如果您正在加载数据,或者您已经验证了对象是否允许更改,那么您可能不希望这些事件触发。为此,我们将更新代码,以便我们可以抑制这些事件。
解决方案
为了实现这一点,我们将向基类添加两个方法,称为 SuspendPropertyEvents
和 ResumePropertyEvents
。我们可以通过两种方式实现这些方法。
第一种方法是添加一个名为 propertyEventsSuspended
的私有布尔字段。我们将此字段设置为 true 来调用 SuspendPropertyEvents
,并设置为 false 来调用 ResumePropertyEvents
。在触发 PropertyChanged
或 PropertyChanging
之前,我们将验证此字段是否设置为 false。如果字段设置为 true,则我们不会触发事件。这种方法的缺点是无法嵌套调用这些方法。例如
TestObject test = new TestObject();
test.SuspendPropertyEvents();
try
{
// .. Set some properties ...
test.SuspendPropertyEvents();
try
{
// .. Set some properties ...
}
finally
{
test.resumePropertyEvents();
}
// .. Set some properties, here events would fire ...
}
finally
{
test.resumePropertyEvents();
}
在这种情况下,在第一次调用 ResumePropertyEvents
后,事件将开始触发。显然,上面的例子是人为的,但内部的 try-finally 块可能很容易成为一个将被调用的方法的一部分,该方法用于执行对 TestObject
的更改,或者是一个与该对象相隔数级的调用。基本上,您作为编码员现在必须意识到您调用的任何方法是否会调用 ResumePropertyEvents
。
第二种实现通过使用一个初始化为零 (0) 的 Int32 字段(而不是布尔值)来解决这个问题。此字段由 SuspendPropertyEvents
递增,由 ResumePropertyEvents
递减。在触发事件之前,我们验证此字段是否设置为零 (0)。如果值不为零 (0),则我们不触发事件。这解决了嵌套调用 SuspendPropertyEvents
和 ResumePropertyEvents
的问题。
以下代码显示了带有最新更新的基类。我只展示了 OnPropertyChanged
的一个版本中的更改,但同样适用于 OnPropertyChanged
和 OnPropertyChanging
的所有重载。
/// <summary>
/// This class implements the <see cref="T:IPropertyNotification"/>
/// interface and provides helper methods for derived classes.
/// </summary>
public class PropertyNotificationObject : IPropertyNotification
{
// ... Existing code ...
#region Methods
// ... Existing code ...
/// <summary>
/// Raises the <see cref="E:PropertyChanged"/> event.
/// </summary>
/// <param name="e">
/// The <see cref="PropertyNotificationEventArgs"/> instance
/// containing the event data.
/// </param>
protected void OnPropertyChanged(PropertyNotificationEventArgs e)
{
if (true == this.PropertyEventsSuspended) // ** Check for suspension
return;
PropertyChangedEventHandler temp = this.PropertyChanged;
if (null != temp)
temp(this, e);
}
// ... Existing code ...
/// <summary>
/// Resumes the <see cref="PropertyChanged"/> and
/// <see cref="PropertyChanging"/> events.
/// </summary>
public void ResumePropertyEvents()
{
this.propertyEventSuspendCount--;
}
// ... Existing code ...
/// <summary>
/// Suspends the <see cref="PropertyChanged"/> and
/// <see cref="PropertyChanging"/> events.
/// </summary>
public void SuspendPropertyEvents()
{
this.propertyEventSuspendCount++;
}
#endregion // Methods
#region Properties/Fields
/// <summary>
/// Gets a value indicating whether the <see cref="PropertyChanged"/>
/// and <see cref="PropertyChanging"/> events are suspended.
/// </summary>
/// <value>
/// <c>true</c> if the property events are suspended;
/// otherwise, <c>false</c>.
/// </value>
public Boolean PropertyEventsSuspended
{
get
{
return (0 != this.propertyEventSuspendCount);
}
}
/// <summary>
/// Holds the suspension count for the <see cref="PropertyChanged"/>
/// and <see cref="PropertyChanging"/> events. When 0, then the
/// events are not suspended.
/// </summary>
private Int32 propertyEventSuspendCount = 0;
#endregion // Properties/Fields
}
结论
这种实现方式在我们的对象要被多个线程访问时会引入一个小问题。如果两个线程同时调用 SuspendPropertyEvents
和/或 ResumePropertyEvents
,那么我们的计数器很可能会不同步。技术上讲,如果使用布尔字段也会存在同样的问题,但我们已经表明,即使在单线程下,布尔字段也无效。我们在这里不解决这个问题,但可以使用 "lock" 关键字轻松纠正。
事件传播
引言
当处理使用不可变类型的属性时,我们可以轻松控制它们的设置。这同样意味着我们可以轻松地触发 PropertyChanged
和 PropertyChanging
事件。当属性类型是可变的时,这会成为一个问题。对于可变属性类型,底层对象可以在不使用主对象上属性的 set 访问器的情况下被更改。例如,在下面的代码中,ChildObject
的 Name
属性被更改了,但 ParentObject
却从未收到通知。
/// <summary>
/// This is a child object.
/// </summary>
public class ChildObject
{
#region Properties/Fields
/// <summary>
/// Holds the name.
/// </summary>
private String name = String.Empty;
/// <summary>
/// Gets or sets the name.
/// </summary>
/// <value>The name.</value>
public String Name
{
get
{
return this.name;
}
set
{
this.name = value;
}
}
#endregion // Properties/Fields
}
/// <summary>
/// This is a parent object with a single child.
/// </summary>
public class ParentObject : PropertyNotificationObject
{
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="ParentObject"/> class.
/// </summary>
public ParentObject()
{
this.Child = new ChildObject();
}
#endregion // Constructors
#region Properties/Fields
/// <summary>
/// Holds the child object.
/// </summary>
private ChildObject child;
/// <summary>
/// Gets or sets the child object.
/// </summary>
/// <value>The child object.</value>
[TypeConverter(typeof(ExpandableObjectConverter))]
public ChildObject Child
{
get
{
return this.child;
}
set
{
SetProperty<ChildObject>("Child",
ref this.child, value);
}
}
/// <summary>
/// Holds the name.
/// </summary>
private String name = String.Empty;
/// <summary>
/// Gets or sets the name.
/// </summary>
/// <value>The name.</value>
public String Name
{
get
{
return this.name;
}
set
{
SetProperty<String>("Name",
ref this.name, value);
}
}
#endregion // Properties/Fields
}
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
ParentObject parent = new ParentObject();
parent.PropertyChanged += new
PropertyChangedEventHandler(parent_PropertyChanged);
parent.Child.Name = "Testing"; // Event not fired!!!
}
private static void parent_PropertyChanged(object sender,
PropertyChangedEventArgs e)
{
// Do something
}
}
这就是事件传播发挥作用的地方。如果我们向 ChildObject
添加了对 IPropertyNotification
的支持——由于此类型更改已提供,因此此处不显示此代码——那么 PropertyChanging
和 PropertyChanged
事件就可以通过 ParentObject
进行传播。
集合也带来了独特的问题。 .NET 集合通常有一个可设置的单一属性 Item
,这相当于 C# 中的 "this" 访问器属性。此外,集合有许多修改 Item
属性的方法,例如 Add
、Remove
、Clear
。这种类设计不仅限于集合。任何类都可以定义一个设置一个或多个属性的方法。
在本篇文章中,我将“简单类”定义为只能通过属性定义设置其属性的类,例如属性不被类中定义的任何方法修改。因此,“复杂类”将被定义为可以通过属性定义或通过方法来设置其属性的类。我们将分别处理这两种类型。
简单类解决方案
为了连接简单类的事件传播,我们需要执行以下两项操作之一。
首先,我们可以将父对象的引用存储在子对象的新属性中,或者使用其他某种机制来获取父对象。这将需要我们在子对象中存储额外的信息,以便它可以将其事件转发给父对象。这意味着父对象必须在子对象“添加到”父对象时将自身引用传递给子对象。
第二种选择要求我们从父对象向子对象添加事件处理程序。因此,子对象不会将事件转发给父对象,而是由父对象侦听来自子对象的事件并自行转发。我们将实现此选项,因为它更简洁。我们将在基类及其 SetProperty
方法中添加通用支持,而不是为每个子对象添加连接代码。更改在此处显示。
/// <summary>
/// This class implements the <see cref="T:IPropertyNotification"/>
/// interface and provides helper methods for derived classes.
/// </summary>
public class PropertyNotificationObject : IPropertyNotification
{
// ... Existing code ...
#region Methods
/// <summary>
/// Adds event handlers for <see cref="T:IPropertyNotification"/>
/// events, if the object supports that interface.
/// </summary>
/// <param name="obj">The obj.</param>
protected void AddPropertyEventHandlers(Object obj)
{
IPropertyNotification propNotify = obj as IPropertyNotification;
if (null != propNotify)
{
propNotify.PropertyChanged += new
PropertyChangedEventHandler(SubObject_PropertyChanged);
propNotify.PropertyChanging += new
PropertyChangingEventHandler(SubObject_PropertyChanging);
}
}
// ... Existing code ...
/// <summary>
/// Raises the <see cref="E:PropertyChanged"/> event.
/// </summary>
/// <param name="sender">The sender.</param>
/// <param name="e">
/// The <see cref="PropertyNotificationEventArgs"/>
/// instance containing the event data.
/// </param>
protected void OnPropertyChanged(Object sender,
PropertyChangedEventArgs e)
{
if (true == this.PropertyEventsSuspended)
return;
PropertyChangedEventHandler temp = this.PropertyChanged;
if (null != temp)
temp(sender, e);
}
// ... Existing code ...
/// <summary>
/// Raises the <see cref="E:PropertyChanging"/> event.
/// </summary>
/// <param name="sender">The sender.</param>
/// <param name="e">
/// The <see cref="CancelPropertyNotificationEventArgs"/>
/// instance containing the event data.
/// </param>
protected void OnPropertyChanging(Object sender,
CancelPropertyNotificationEventArgs e)
{
if (true == this.PropertyEventsSuspended)
return;
PropertyChangingEventHandler temp = this.PropertyChanging;
if (null != temp)
temp(sender, e);
}
/// <summary>
/// Removes event handlers for <see cref="T:IPropertyNotification"/>
/// events, if the object supports that interface.
/// </summary>
/// <param name="obj">The obj.</param>
protected void RemovePropertyEventHandlers(Object obj)
{
IPropertyNotification propNotify = obj as IPropertyNotification;
if (null != propNotify)
{
propNotify.PropertyChanged -= new
PropertyChangedEventHandler(SubObject_PropertyChanged);
propNotify.PropertyChanging -= new
PropertyChangingEventHandler(SubObject_PropertyChanging);
}
}
// ... Existing code ...
/// <summary>
/// This method is used to set a property while firing associated
/// PropertyChanging and PropertyChanged events.
/// </summary>
/// <param name="propertyName">Name of the
/// property.</param>
/// <param name="propertyField">The property field.
/// </param>
/// <param name="value">The value.</param>
protected void SetProperty<T>(String propertyName,
ref T propertyField, T value)
{
if (false == Object.Equals(value, propertyField))
{
if (true == OnPropertyChanging(propertyName,
propertyField, value))
{
T oldValue = propertyField;
propertyField = value;
// ** Remove handlers from old value, if any
RemovePropertyEventHandlers(oldValue);
// ** Add handlers to new value, if any
AddPropertyEventHandlers(propertyField);
OnPropertyChanged(propertyName, oldValue,
propertyField);
}
}
}
/// <summary>
/// Handles the PropertyChanged event of any sub-objects.
/// </summary>
/// <param name="sender">
/// The source of the event.
/// </param>
/// <param name="e">
/// The <see cref="PropertyChangedEventArgs"/>
/// instance containing the event data.
/// </param>
private void SubObject_PropertyChanged(Object sender,
PropertyChangedEventArgs e)
{
if (true == this.PropagatePropertyNotifications)
OnPropertyChanged(sender, e);
}
/// <summary>
/// Handles the PropertyChanging event of any sub-objects.
/// </summary>
/// <param name="sender">
/// The source of the event.</param>
/// <param name="e">
/// The <see cref="CancelPropertyNotificationEventArgs"/>
/// instance containing the event data.
/// </param>
private void SubObject_PropertyChanging(object sender,
CancelPropertyNotificationEventArgs e)
{
if (true == this.PropagatePropertyNotifications)
OnPropertyChanging(sender, e);
}
// ... Existing code ...
#endregion // Methods
#region Properties/Fields
/// <summary>
/// Holds a value indicating whether the
/// <see cref="T:IPropertyNotification"/>
/// events of child objects
/// should be propagated through this object's event sinks.
/// </summary>
private Boolean propagatePropertyNotifications = true;
/// <summary>
/// Gets or sets a value indicating whether the
/// <see cref="T:IPropertyNotification"/>
/// events of child objects should be propagated through
/// this object's event sinks.
/// </summary>
/// <value>
/// <c>true</c> if property events should be
/// propagated; otherwise, <c>false</c>.
/// </value>
public Boolean PropagatePropertyNotifications
{
get
{
return this.propagatePropertyNotifications;
}
set
{
SetProperty<Boolean>(
"PropagatePropertyNotifications",
ref this.propagatePropertyNotifications, value);
}
}
// ... Existing code ...
#endregion // Properties/Fields
}
我们已更新 SetProperty
方法,使其调用旧属性值的 RemoveEventHandlers
,并调用新属性值的 AddEventHandlers
。这些新方法确定给定对象是否支持 IPropertyNotification
接口。如果支持,它将添加或删除事件处理程序。然后,事件处理程序将转发或传播来自子对象的任何事件。此转发机制由一个名为 PropagatePropertyNotifications
的新布尔属性控制。最后,我们为接受发送方作为参数的 OnPropertyChanged
和 OnPropertyChanging
添加了重载方法,因为我们不能再硬编码 "this" 了。
现在,假设上面的 ChildObject
已更新以支持 IPropertyNotification
接口,那么来自 ChildObject
的事件将被传播到 ParentObject
。这支持任意数量或深度的对象。因此,如果一个名为 GrandChild
的属性,类型为 GrandChildObject
(我们假设它支持 IPropertyNotification
),被添加到 ChildObject
类中,那么 ParentObject
将收到关于其属性更改的通知,此外还会收到 ChildObject
的通知。
最后,请记住,ParentObject
的 Child
属性需要初始化,如 ParentObject
类的构造函数所示。如果您初始化了底层字段,则事件处理程序将不会被连接。
复杂类解决方案
复杂类既有属性,也有通过方法执行的操作。.NET 集合类是复杂类的完美示例。它们有一个名为 Item
的单一属性——同样,这相当于 C# 中的 "this" 访问器——并且有许多可以应用于此属性的操作,例如 Add
和 Remove
。我们将重点实现一个支持泛型列表类的属性通知。此处显示的相同概念可应用于任何复杂类。
在为集合实现 IPropertyNotification
时存在一些问题。首先,如果集合中的任何对象支持 IPropertyNotification
接口,那么它们的事件将不会被传播。其次,属性更改事件中提供的信息并未指明集合如何更改,例如,添加了项、插入了项、删除了项。
为了解决上述两个问题,我们将实现一个支持 IPropertyNotification
接口的自定义泛型 List 类。我们的泛型 List 类将在内部使用 System.Collections.Generic.List<T>
的实例来存储其项。这使我们可以专注于添加所需的功能,而不必担心列表实现所需的细节。我们将实现 System.Collections.Generic.List<T>
支持的所有接口,但我们会集成对 IPropertyNotification
事件的调用。我们泛型 List 类的完整实现太长,无法在此包含,但已包含在演示项目中。无论如何,我们将在下面介绍其中的两个方法。
在深入研究之前,我们将创建一个帮助我们从列表对象触发这些事件的基类。此外,我们将添加两个派生自 PropertyNotificationEventArgs
的类,用于我们的新列表。当我们从列表触发 IPropertyNotification
事件时,我们希望能够准确地捕获已更改的内容。例如,如果我向列表中添加一个对象,那么事件参数应该包括该对象及其在列表中的位置。
为此,我们的第一个类将称为 ListPropertyNotificationEventArgs
。我们将添加一个枚举属性来描述已执行的列表操作,例如 Add
、Remove
。我们还将添加一个 Int32 属性来保存项的索引。此类和相关的枚举在此处显示。
/// <summary>
/// This enumeration holds all the possible operations that can
/// be performed on an object that supports the
/// <see cref="T:IList{T}"/> interface.
/// </summary>
public enum ListOperation
{
/// <summary>
/// Indicates that operation is unknown.
/// </summary>
Unknown,
/// <summary>
/// Indicates that operation is
/// <see cref="M:IList{T}.Add"/>.
/// </summary>
Add,
/// <summary>
/// Indicates that operation is
/// <see cref="M:IList{T}.Remove"/>
/// or <see cref="M:IList{T}.RemoveAt"/>.
/// </summary>
Remove,
/// <summary>
/// Indicates that operation is
/// <see cref="M:IList{T}.Insert"/>.
/// </summary>
Insert,
/// <summary>
/// Indicates that operation is
/// <see cref="M:IList{T}.Clear"/>.
/// </summary>
Clear,
/// <summary>
/// Indicates that operation is
/// <see cref="M:IList{T}.Item[Int32]"/>.
/// </summary>
Set
}
/// <summary>
/// This class extends <see cref="T:PropertyNotificationEventArgs"/>
/// to support objects that implement the
/// <see cref="T:IList{T}"/> interface.
/// </summary>
public class ListPropertyNotificationEventArgs
: PropertyNotificationEventArgs
{
#region Constructors
/// <summary>
/// Initializes a new instance of the
/// <see cref="ListPropertyNotificationEventArgs"/>
/// class.
/// </summary>
/// <param name="propertyName">
/// The name of the property that is associated with this
/// notification.
/// </param>
public ListPropertyNotificationEventArgs(String propertyName)
: base(propertyName)
{
this.index = -1;
this.operation = ListOperation.Unknown;
}
/// <summary>
/// Initializes a new instance of the
/// <see cref="ListPropertyNotificationEventArgs"/>
/// class.
/// </summary>
/// <param name="propertyName">
/// The name of the property that is associated with this
/// notification.
/// </param>
/// <param name="oldValue">The old value.</param>
/// <param name="newValue">The new value.</param>
public ListPropertyNotificationEventArgs(String propertyName,
Object oldValue, Object newValue)
: base(propertyName, oldValue, newValue)
{
this.index = -1;
this.operation = ListOperation.Unknown;
}
/// <summary>
/// Initializes a new instance of the
/// <see cref="ListPropertyNotificationEventArgs"/> class.
/// </summary>
/// <param name="propertyName">The name of the
/// property that is associated with this
/// notification.</param>
/// <param name="operation">The operation.</param>
/// <param name="index">The index.</param>
/// <param name="oldValue">The old value.</param>
/// <param name="newValue">The new value.</param>
public ListPropertyNotificationEventArgs(String propertyName,
ListOperation operation, Int32 index,
Object oldValue, Object newValue)
: base(propertyName, oldValue, newValue)
{
this.index = index;
this.operation = operation;
}
#endregion // Constructors
#region Properties/Fields
/// <summary>
/// Holds the index into the list that was affected by the
/// operation.
/// </summary>
private Int32 index;
/// <summary>
/// Gets the index into the list that was affected by the
/// operation.
/// </summary>
/// <value>The index.</value>
public Int32 Index
{
get
{
return this.index;
}
}
/// <summary>
/// Holds the operation that was performed on the list.
/// </summary>
private ListOperation operation;
/// <summary>
/// Gets the operation that was performed on the list.
/// </summary>
/// <value>The operation.</value>
public ListOperation Operation
{
get
{
return this.operation;
}
}
#endregion // Properties/Fields
}
第二个类派生自 CancelPropertyNotificationEventArgs
,并添加了与 ListPropertyNotificationEventArgs
相同的属性。因此,此类(称为 ListCancelPropertyNotificationEventArgs
)的代码不在此包含。我更希望类名为 CancelListPropertyNotificationEventArgs
,并让它派生自 ListPropertyNotificationEventArgs
。不幸的是,为了在新类中用于现有的 PropertyChanging
事件,它需要派生自 CancelPropertyNotificationEventArgs
。
我可以改变 PropertyChanging
事件的签名,使其接受一个 EventArgs
类来解决这个问题,但我选择不这样做。另一个选择是创建一个派生自 EventArgs
的类,该类有两个属性:Cancel
和 EventArgs
。前一个属性将指示是否取消事件。后一个属性将包含另一个 EventArgs
,其中包含实际的事件数据。这还将允许我们为两个事件重用 PropertyNotificationEventArgs
。然而,目前我们将忽略这两个想法,只在 ListCancelPropertyNotificationEventArgs
中复制代码。
接下来,我们将创建一个名为 ListPropertyNotificationObject
的新基类,它将派生自 PropertyNotificationObject
。由于此类仅添加了使触发 IPropertyNotification
事件更容易的方法,因此此处未包含其代码。我们的列表类将称为 PropertyNotificationList<T>
。它将实现 IList<T>
接口,从而实现 ICollection<T>
、IEnumerable<T>
和 IEnumerable
接口。其定义如下所示。
/// <summary>
/// This list supports the <see cref="T:IPropertyNotification"/>
/// interface, but is otherwise identical to <see cref="T:List{T}"/>.
/// </summary>
[Serializable]
public class PropertyNotificationList<T> : ListPropertyNotificationObject,
ICollection<T>, IEnumerable<T>,
IEnumerable, IList<T>
{
// ... Members ...
}
现在我们有了类定义,我们将实现接口并复制 List<T>
构造函数。首先,我们将查看 Add
方法,如下所示。
/// <summary>
/// Adds an item to the <see cref="T:ICollection`1"/>.
/// </summary>
/// <param name="item">
/// The object to add to the <see cref="T:ICollection`1"/>.
/// </param>
/// <exception cref="T:NotSupportedException"/>
/// The <see cref="T:ICollection`1"></see> is read-only.
/// </exception>
public void Add(T item)
{
Int32 index = this.items.Count;
if (false == OnPropertyChanging("Item",
ListOperation.Add, index, null, item))
{
this.items.Add(item);
AddPropertyEventHandlers(item);
OnPropertyChanged("Item", ListOperation.Add,
index, null, item);
}
}
如您所见,Add
方法与 SetProperty
方法非常相似。我们触发 changing 事件,应用更改,然后触发 changed 事件。这里需要注意的一点是,我们将 IPropertyNotification
事件处理程序添加到要添加的项中。我们这样做是因为我们想知道该对象是否已更改,就像我们之前对 ChildObject
所做的那样。相反,在 Remove
方法中,我们从项中删除事件处理程序,如下所示。
/// <summary>
/// Removes the first occurrence of a specific object from the
/// <see cref="T:ICollection`1"/>.
/// </summary>
/// <param name="item">
/// The object to remove from the <see cref="T:ICollection`1"/>.
/// </param>
/// <returns>
/// true if item was successfully removed from the
/// <see cref="T:ICollection`1"/>; otherwise, false. This method
/// also returns false if item is not found in the original
/// <see cref="T:ICollection`1"/>.
/// </returns>
/// <exception cref="T:NotSupportedException">
/// The <see cref="T:ICollection`1"/> is read-only.
/// </exception>
public Boolean Remove(T item)
{
// Get the index of the item. if the item was not
// found then we won't be changing the list so
// skip property change events.
Int32 index = IndexOf(item);
if (index >= 0)
{
if (false == OnPropertyChanging("Item",
ListOperation.Remove, index, item, null))
{
RemovePropertyEventHandlers(item);
this.items.RemoveAt(index);
OnPropertyChanged("Item", ListOperation.Remove,
index, item, null);
}
}
return false;
}
列表中其余的方法以类似的方式实现。
结论
使用新的传播功能,客户端可以向单个对象连接事件处理程序,以接收给定对象及其任何子对象的属性更改和属性已更改通知。我们的新泛型 List 类允许客户端从集合接收属性更改和已更改通知。此外,我们的新泛型 List 类可以将其任何事件传播到其父对象或父对象。
总结
我们已将属性通知基础结构扩展到包括事件抑制和事件传播。这两个功能为事件触发的时间提供了更精细地控制,并简化了处理多层对象时事件的使用。演示应用程序包含本文中的所有完成代码以及一个展示其运行的简单示例。在本文系列的下一部分中,我们将处理以下改进:
- 批处理更改支持:当更新大量属性时,可能希望将这些更改分组为单个事件。在这种情况下,我们希望了解所有更改的属性以及它们如何更改。
历史
- 2007年5月30日 -- 发布原始版本
- 2007年8月1日 -- 文章已编辑并移至 CodeProject.com 主要文章库