Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

INotifyPropertyChanged and beyond - Part II

0.00/5 (No votes)
30 May 2007 2  
Add support for event suppression and event propagation
Screenshot - screenshot.png

Introduction

The INotifyPropertyChanged interface provides a standard event for objects to notify clients that one of its properties has changed. This is helpful for data binding, as described in this article, since it allows for bound controls to update their display based on changes made directly to the underlying object. While this event serves its purpose, there is still room for improvement.

In Part I of this series, we built some improvements into the INotifyPropertyChanged interface. This article continues to build upon the code from Part I by adding additional improvements. We will extend our code to support event suppression and event propagation. In an attempt to keep this article from being excessively long, I will address batched events in a follow-up article.

Event suppression

Introduction

The PropertyChanged and PropertyChanging events are fired every time a property is changed, but there may be times when we do not want these events to be fired. For example, if you are loading data or if you have already verified that an object should be allowed to change, then you may not want these events to fire. To that end, we will update the code so that we can suppress the events.

Solution

To accomplish this, we will add two methods to our base class called SuspendPropertyEvents and ResumePropertyEvents. There are two ways we could implement these methods.

The first involves adding a private Boolean field member called propertyEventsSuspended. We would set this field to true in SuspendPropertyEvents and set it to false in ResumePropertyEvents. Before firing PropertyChanged or PropertyChanging, we would verify that this field is set to false. If the field is set to true, then we would not fire the event(s). The problem with this approach is that you cannot have nested calls to these methods. For example:

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();
}

In this scenario, the events would start firing after the first call to ResumePropertyEvents. Obviously the example above is contrived, but the inner try-finally block could easily be part of a method that is called to perform changes on the TestObject or a method several levels removed. Basically, you as the coder must now be aware of whether any methods you call are going to call ResumePropertyEvents.

The second implementation solves this by using an Int32 field initialized to zero (0), instead of a Boolean. This field is incremented by SuspendPropertyEvents and decremented by ResumePropertyEvents. Before firing the events, we verify that this field is set to zero (0). If the value is not zero (0), then we do not fire the events. This solves the problem of having nested called to SuspendPropertyEvents and ResumePropertyEvents.

The following code shows the base class with the latest updates. I only show the changes to one version of OnPropertyChanged, but the same applies to all overloads of OnPropertyChanged and 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

}

Conclusion

This implementation introduces a slight problem if our objects are going to be accessed by multiple threads. If two threads were to call SuspendPropertyEvents and/or ResumePropertyEvents at the same time, then our counter would most likely get out of sync. Technically, the same problem would exist if we used a Boolean field, but we have already shown that the Boolean field is ineffective even with just a single thread. We will not address this problem here, but it could easily be corrected using the "lock" keyword.

Event propagation

Introduction

When working with properties that use immutable types, we can easily control when they are set. This also means we can easily fire the PropertyChanged and PropertyChanging events. This becomes a problem when the property type is mutable. With mutable property types, the underlying object can be changed without using the set accessor for the property on the main object. For example, in the code below the ChildObject has its Name property changed, but the ParentObject is never notified:

/// <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

    }
}

This is where event propagation comes to the rescue. If we add support for IPropertyNotification to the ChildObject -- this code will not be shown here, since this type of change has already been given -- then the PropertyChanging and PropertyChanged events can be propagated through the ParentObject.

Collections introduce a unique problem, as well. The .NET collections typically have a single settable property called Item, which equates to the "this" accessor property in C#. In addition, Collections have numerous methods that modify the Item property, e.g. Add, Remove, Clear. This class design is not limited to Collections. Any class can define a method that sets one or more properties.

For this article, I will define a "simple class" as a class whose properties can only be set through the property definition, e.g. the properties are not modified by a method defined in the class. Therefore a "complex class" will be defined as a class whose properties can be set from the property definition or through methods. We will address each type separately.

Simple class solution

In order to hook up event propagation for simple classes, we need to do one of the following two things.

First, we could store a reference to the parent object in a new property in the sub-object or use some other mechanism of getting the parent object. This would require us to store additional information in the sub-object so that it can forward its events to its parent object. This means that the parent object must pass a reference to itself to its sub-objects when they are "added" to the parent.

The second option requires us to add event handlers to the sub-objects from the parent object. So, instead of the sub-object forwarding its events to the parent, the parent would listen to events from its sub-objects and forward them itself. We will implement this option because it is a bit cleaner. Instead of adding code to hook up each individual sub-object, we will add generic support to our base class and its SetProperty method. The changes are shown here:

/// <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

}

We have updated the SetProperty method so that it calls RemoveEventHandlers on the old property value and AddEventHandlers on the new property value. These new methods determine if the given object supports the IPropertyNotification interface. If it does, it will add or remove the event handlers. The event handlers will then forward or propagate any events from the sub-objects. This forwarding mechanism is controlled by a new Boolean property called PropagatePropertyNotifications. Finally, we add overloaded methods for OnPropertyChanged and OnPropertyChanging that accept the sender as a parameter because we can no longer hard-code "this."

Now, assuming that ChildObject above is updated to support the IPropertyNotification interface, then the events from ChildObject will be propagated to ParentObject. This supports any number or depth of objects. So, if a property called GrandChild of type GrandChildObject -- which we will assume supports IPropertyNotification -- was added to the ChildObject class, then the ParentObject would receive notifications about its property changes, in addition to the ChildObject.

Finally, keep in mind that the Child property of ParentObject needs to be initialized, as shown in the constructor of the ParentObject class. If you initialize the underlying field, then the event handlers will not be hooked up.

Complex class solution

Complex classes have both properties and operations, executed through methods. The .NET collection classes are perfect examples of complex classes. They have a single property called Item -- again, this equates to the "this" accessor in C# -- and have numerous operations that can be applied to this property, such as Add and Remove. We will focus on implementing property notification for a generic list class. The same concepts shown here can be applied to any complex class.

There are a couple problems with implementing IPropertyNotification as-is for collections. First, if any of the objects in the collection support the IPropertyNotification interface, then their events would not be propagated. Second, the information provided in the property change events do not indicate how the collection changed, e.g. item added, item inserted, item removed.

In order to solve the two problems listed above, we will implement a custom generic List class that supports the IPropertyNotification interface. Our generic list class will use an instance of System.Collections.Generic.List<T> internally to store its items. This allows us to focus on adding the functionality we want without having to worry about the details required for a list implementation. We will implement all of the interfaces supported by System.Collections.Generic.List<T>, but we will integrate calls to the IPropertyNotification events. The full implementation of our generic list class is too long to be included here, but is included in the demo project. Regardless, we will cover two of the methods here.

Before we dive into that, we are going to create a base class to help us with firing these events from list objects. In addition, we will add two classes derived from PropertyNotificationEventArgs for our new list. When we fire the IPropertyNotification events from our list, it would be nice to capture exactly what has changed. For example, if I add an object to the list then I would like the event arguments to include the object and its position in the list.

To that end, our first class will be called ListPropertyNotificationEventArgs. We will add an enumeration property that describes the list operation that was performed, e.g. Add, Remove. We will also add an Int32 property to hold the index of the item. This class and the associated enumeration are shown here:

/// <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

}

The second class derives from CancelPropertyNotificationEventArgs and adds the same properties as ListPropertyNotificationEventArgs. Therefore the code for this class, called ListCancelPropertyNotificationEventArgs, is not included here. I would prefer to have the class named CancelListPropertyNotificationEventArgs and have it derive from ListPropertyNotificationEventArgs. Unfortunately, in order to use the new class in the existing PropertyChanging event it needs to derive from CancelPropertyNotificationEventArgs.

I could get around this problem by changing the signature of the PropertyChanging event so that it accepts an EventArgs class, but I chose not to do that. Another option would be to make an EventArgs derived class that has two properties: Cancel and EventArgs. The former property would indicate whether to cancel the event. The latter would hold another EventArgs that contains the actual event data. This would also allow us to reuse the PropertyNotificationEventArgs for both events. However, for now we will ignore both of these ideas and just duplicate the code in ListCancelPropertyNotificationEventArgs.

Next, we will create a new base class called ListPropertyNotificationObject, which will derive from PropertyNotificationObject. Since this class simply adds methods that make firing the IPropertyNotification events easier, I have not included its code here. Our list class will be called PropertyNotificationList<T>. It will implement the IList<T> interface and thus the ICollection<T>, IEnumerable<T> and IEnumerable interfaces. It is defined as shown here:

/// <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 ...

}

Now that we have the class definition, we will implement the interfaces and duplicate the List<T> constructors. First, we will look at the Add method as shown here:

/// <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);
    }
}

As you can see, the Add method is very similar to the SetProperty method. We fire the changing event, apply the change and then fire the changed event. One thing to note here is that we add IPropertyNotification event handlers to the item being added. We do this because we want to know if that object is changed, just like we did earlier with the ChildObject. Conversely, in the Remove method, we remove the event handlers from the item as shown here:

/// <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;
}

The remaining methods in the list are implemented in a similar manner.

Conclusion

Using the new propagation feature, clients can hook up event handlers to a single object to receive property changing and property changed notifications for the given object and any sub-object. Our new generic list class allows clients to receive property changing and changed notifications from a collection. In addition, our new generic list class can propagate any events up to its parent or parents.

Wrap-up

We have extended our property notification infrastructure to include event suppression and event propagation. These two features add finer control over when the events are fired and simplify the use of the events when working with multiple levels of objects. The demo application contains all the completed code from this article and a simple example that shows it in action. In the next part of this article series, we will tackle the following improvements:

  1. Batch Change support: When lots of properties are being updated, it may be desirable to group these changes into a single event. In this case, we would like to know all of the properties that changed and how they changed.

History

  • 30 May, 2007 -- Original version posted
  • 1 August, 2007 -- Article edited and moved to the main CodeProject.com article base

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here