Sunday, 19 October 2008

ObservableCollection<T>

Following on from the previous post where I noted that ObservableCollection<T>.CollectionChanged didn't do what I wanted, I've created my own version of ObservableCollection that exposes a new event ItemPropertyChanged. I then hook up to the PropertyChanged event of the business object as it is added to the collection. The new class adds a new property HasChanges which reflects the change status of any property in any business object in the collection.

Caveats:
The business object must implement INotifyPropertyChanged.
There is no "undo" mechanism to set the HasChanges flag back to false

public class MyObservableCollection : ObservableCollection
{
public new void Add(T item)
{
// If the class represented by T implements the INotifyPropertyChanged
// interface hook up the PropertyChanged event handler
if ((item as INotifyPropertyChanged) != null)
{
(item as INotifyPropertyChanged).PropertyChanged += new PropertyChangedEventHandler(OnItemPropertyChanged);
}
base.Add(item);
}

public bool HasChanges { get; set; }
public event PropertyChangedEventHandler ItemPropertyChanged;
public MyObservableCollection()
{
HasChanges = false;
}
protected void OnItemPropertyChanged(object sender, PropertyChangedEventArgs e)
{
HasChanges = true;
OnPropertyChanged("HasChanges");
}
protected void OnPropertyChanged(string propertyName)
{
if (ItemPropertyChanged != null)
{
ItemPropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}

If an object of this type is bound directly to the DataContext (or ObjectDataProvider) the HasChanges property can be referenced directly in the xaml.

If the collection object is intantiated by a Model class the Model will need to implement INotifyPropertyChanged and expose its own HasChanges property with its value set by wiring up the ItemPropertyChanged event:
public MyObservableCollection Abc { get; set; }

public Model()
{
Abc = new MyObservableCollection();
Abc.ItemPropertyChanged += new PropertyChangedEventHandler(BusinessPropertyChanged);
}
public bool HasChanges
{
get { return Abc.HasChanges; }
}
protected void BusinessPropertyChanged(object sender, PropertyChangedEventArgs e)
{
OnPropertyChanged("HasChanges");
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}

1 comment:

Allan said...

I can see that this solution would resolve my immediate problem.
I am however concerned that in the Add method you connect to an event handler, but nowhere do you destroy the event Handler.
If I was to do a clear the collection or remove an item, am I correct in thinking I need to write code to PropertyChanged -=
Also what happens when you lose reference to MyObservableCollection. Should your code have to destroy the connected events or does garbage collection take care of this?