Use INotifyPropertyChanged with BindingList

Save/Share Google Yahoo! Digg It Reddit del.icio.us
My Zimbio

After reading Bil Simser’s discussion on DataTable vs. BindingList<T>, I’m convinced that BindingList<T> is the way to go for representing domain objects in C#. However, effective use of BindingList does require a certain amount of planning and forethought on the part of the developer. There are a number of things you can do to increase your chances of success with BindingList, but this post is about having your list items implement INotifyPropertyChanged in order to propagate change events through the BindingList itself.

Suppose you’re writing a Windows client application that contains a DataGridView with a list of customer accounts. The Account object is initially defined like this:

public class Account

{

private decimal balance;

public decimal Balance

{

get

{

return balance;

}

set

{

balance = value;

}

}

}

You’d assign a BindingSource to the DataSource property of the grid, and you’d assign a BindingList<Account> to the DataSource property of the BindingSource. Now, your DataGridView will automatically receive notification of list items being added to, removed from, or reordered in the list.

The DataGridView receives these notification through the ListChanged event on BindingList which passes ListChangedEventArgs. The ListChangedType property on the event arguments will indicate ItemAdded, ItemDeleted, or ItemMoved along with an appropriate index to indicate where in the list the change occurred.

One ListChangedType you won’t get for the above Account class is ItemChanged. What if, for example, the account balance changes? The DataGridView won’t automatically reflect this change without some other programmatic intervention. That’s because the BindingList, at this point, is only firing events about changes in the list itself. It’s unaware of state changes in the individual items, so it doesn’t know to fire events indicating ItemChanged.

We solve this by making Account implement the INotifyPropertyChanged interface. This involves declaring the PropertyChanged event and raising the event when the Balance property changes. So, let’s refactor the Account class:

using System.ComponentModel;

public class Account : INotifyPropertyChanged

{

private decimal balance;

public event PropertyChangedEventHandler PropertyChanged;

public decimal Balance

{

get

{

return balance;

}

set

{

balance = value;

if (PropertyChanged != null)

{

PropertyChanged( this, new PropertyChangedEventArgs(“Balance”) );

}

}

}

}

Now, when Account objects are added to a BindingList, the BindingList will detect the INotifyPropertyChanged interface and hook up listeners for the PropertyChanged event. When the PropertyChanged event fires on an Account, the BindingList that holds it will fire a ListChanged event with ListChangedType of ItemChanged.

At this point, I have two concerns with our Account class. First, identifying the changed property by a string value in the event arguments is just plain ugly. It’s brittle and prone to getting out of sync with the property name during refactoring (not to mention getting out of sync with any event delegates that need to identify the origin of a PropertyChanged event by name).

My other concern is that the PropertyChanged event will always fire when the property setter is called, even if the old and new values are identical. This isn’t necessarily the end of the world, but sometimes it’s best to avoid firing superfluous events.

So, we’ll create a utility method for property setters to use. This may either be a method on the Account class, or (my preference) a static method on a utility class:

public static bool Set<T>(object owner, string propName,

ref T oldValue, T newValue, PropertyChangedEventHandler eventHandler)

{

// make sure the property name really exists

if (owner.GetType().GetProperty(propName) == null)

{

throw new ArgumentException(“No property named ‘” +

propName + “‘ on ” + owner.GetType().FullName);

}

// we only raise an event if the value has changed

if (!Equals(oldValue, newValue))

{

oldValue = newValue;

if (eventHandler != null)

{

eventHandler(owner, new PropertyChangedEventArgs(propName));

}

}

}

The first if-block ensures that the named property really exists on the owner type and throws an exception if it doesn’t. It’d be better to have a compile time error, but this’ll do for now. You should also perform this check in any delegates that listen for change events on a named property (those are even more likely to get out of sync).

The second if-block determines if the value supplied by the caller is actually different than the underlying field. A PropertyChanged event is fired only if they differ.

Now, our Account class takes on its final form:

using System.ComponentModel;

public class Account : INotifyPropertyChanged

{

private decimal balance;

public event PropertyChangedEventHandler PropertyChanged;

public decimal Balance

{

get

{

return balance;

}

set

{

Set(this, “Balance”, ref balance, value, PropertyChanged);

}

}

}

Caveat

BindingList<T> is broken in a number of ways which I’ll cover in later posts. A biggie to be concerned with here is that the serialization and deserialization of BindingList<T> subtypes is flawed. Event handlers aren’t serializable, so all of the hooks that BindingList sets up to listen to PropertyChanged events get lost when the list is serialized. So, if you define a subtype such as:

public class MyBindingList<T> : BindingList<T>

and your list goes through a serialization/deserialization step (like loading objects from a file or receiving them over a remote connection), you’ll stop getting ListChanged events for list item property changes.

One solution to the serialization problem is to rebuild all of the event listeners internally as part of an OnDeserialized method as I discuss in my post Fixing BindingList Deserialization. The fact that BindingList doesn’t already do this for you is disappointing.

Save/Share Google Yahoo! Add to Technorati Favorites Digg It Reddit
del.icio.us My Zimbio

6 Responses to “Use INotifyPropertyChanged with BindingList”

  1. Mike DiRenzo Says:

    Mike:

    I posted a reply to your Code Project article about how the events become decoupled during ser/deser process. After reading this article, My Code Project reply should probably be deleted. My reply dealt with the “dirtiness” of properties and how to handle them.

    Using the technique described above (INotifyPropertyChanged, how would I go about marshalling changes to a data store? For example, using my technique, I have three extra inherited boolean properties for every class: IsDirty, IsDeleted, IsNew. I set these props in every other property defined in my class in the setter. So, if any property changes, the class’s IsDirty property is set to TRUE. If I add a new object/class to my collection (List), I set the class’s IsNew property to TRUE – same holds true for deletions. I can send the entire List to a data class method and iterate through it and examine the properties and take appropriate CRUD action.

    I am earnestly looking for a better approach. If you have time, please respond.

    -Mike DiRenzo
    mike_direnzo@hotmail.com

  2. Mike Gavaghan Says:

    Mike,

    It sounds like you’re describing object persistence, which is a rather different problem. I was tackling binary serialization which solely applies to things like remoting or streaming to a file.

    My first thought is that, using the approach you’re describing, you probably would not encounter the ser/deser bug. Your DAOs would be directly instantiating BindingList instances on deserialization – instead of the .NET runtime. You’d be adding items manually and, thus, reconnecting the events.

    However, you might save yourself a lot of grief by adopting NHibernate (http://www.hibernate.org/343.html). That will handle all of the object-relational mapping needs (like dirty flags, CRUD, etc.) It’s all automagic – you only need to declare your object state.

    Unfortunately, NHibernate uses a dynamic implementation of collection classes – not BindingList – so you’d lose the benefits of property change notifications. You’d need to copy your collections into a BindingList any time change notifcation is needed (and copy everything back to capture changes). Ugly? You bet. But. it’s neither as tedious nor as error prone as coding your entire persistent domain model as handcrafted DAOs.

    –Mike

  3. andres Says:

    Based on this I make a more generic solution thats implements INotifyPropertyChanged, INotifyPropertyChanging, IEditableObject and other level of manual discard changes. This can be used in this way:

    public class AnyBussinesObjectUIRepresentation : Entities.AnyBussinesObjectBase, IEditableUIObject
    {
    public override bool Active
    {
    get { return editableData.GetData(“Active”); }
    set { editableData.SetData(“Active”, value); }
    }

    public override string Description
    {
    get { return editableData.GetData(“Description”); }
    set { editableData.SetData(“Description”, value); }
    }

    public override string Code
    {
    get { return editableData.GetData(“Code”); }
    set { editableData.SetData(“Code”, value); }
    }

    public override Guid Id
    {
    get { return editableData.GetData(“Id”); }
    set { editableData.SetData(“Id”, value); }
    }

    public override string Name
    {
    get { return editableData.GetData(“Name”); }
    set { editableData.SetData(“Name”, value); }
    }

    #region IEditableUIObject Implementation

    private IEditableUIObjectSupporter editableData = new EditableUIObjectSupporter();

    public event PropertyChangingEventHandler PropertyChanging
    {
    add { editableData.PropertyChanging += value; }
    remove { editableData.PropertyChanging -= value; }
    }

    public event PropertyChangedEventHandler PropertyChanged
    {
    add { editableData.PropertyChanged += value; }
    remove { editableData.PropertyChanged -= value; }
    }

    [IgnoreDataMember]
    public bool DiscartableChangesControl
    {
    get { return editableData.DiscartableChangesControl; }
    set { editableData.DiscartableChangesControl = value; }
    }

    public bool IsDirty
    {
    get { return editableData.IsDirty; }
    }

    public void DiscardChanges()
    {
    editableData.DiscardChanges();
    }

    public void AcceptChanges()
    {
    editableData.AcceptChanges();
    }

    public ValueChangedDescriptorCollection GetChanges()
    {
    return editableData.GetChanges();
    }

    void IEditableObject.BeginEdit()
    {
    editableData.BeginEdit();
    }

    void IEditableObject.CancelEdit()
    {
    editableData.CancelEdit();
    }

    void IEditableObject.EndEdit()
    {
    editableData.EndEdit();
    }

    #endregion
    }

    I don’t paste the base code because there are 300 lines but you can see it in http://code.google.com/p/facturanet/source/browse/#svn/trunk/Facturanet.Core/UI

    Maybe the Hashtables are not so efficients, but I think that it will be enough for my project.

    Thanks for the inspiration.

  4. Andrei Kournikov Says:

    Hi,

    It’s worth mentioning that the handler of INotifyPropertyChanged notifications in BindingList is very poorly implemented. Specifically when the BindingList receives notifications of new object, it compares this object with the previous object that was used for notification. If they are not identical, the binding list does a terrible thing – it runs through ALL objects to get the index of the notifying object.
    Here are more details and explanations how to improve the performance: http://www.blog.dapfor.com/why-bindinglist-is-slow-with-objects-implementing-inotifypropertychanged-interface

    Best regards,

  5. Kurtis Lininger Says:

    Mike,

    Thanks for the code! The only change I did was wrap your “// make sure the property name really exists” code with “#if DEBUG / #endif” to save some time after things have proven to work.

  6. stoffy28 Says:

    Than k you, it’s exactly what i need.

Leave a Reply