In a project of mine I needed a class "Document" to be able to notify any change in its fields. This class keep a collection of class "AnotherClass". Using an ObservableCollection for this collection is not enough because my application can't receive notification from Document if a field INSIDE an instance of AnotherClass changes.
I wrote this implementation of "ObservableCollectionDeep" to overcome this problem:
public class ObservableCollectionDeep<T> : ObservableCollection<T> where T : INotifyPropertyChanged
{
private bool _FreezeNotifications;
private Action<object, PropertyChangedEventArgs> _CollectionItemChangedHandler;
private Action<object, NotifyCollectionChangedEventArgs> _CollectionChangedHandler;
public ObservableCollectionDeep(Action<object, NotifyCollectionChangedEventArgs> collectionChangedHandler,
Action<object, PropertyChangedEventArgs> collectionItemChangedHandler)
: base()
{
_CollectionChangedHandler = collectionChangedHandler;
_CollectionItemChangedHandler = collectionItemChangedHandler;
}
public void AddRange(IEnumerable<T> collection)
{
_FreezeNotifications = true;
collection.ToList().ForEach(p => Add(p));
_FreezeNotifications = false;
var eventArgs = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, collection);
onCollectionChanged(this, eventArgs);
base.OnCollectionChanged(eventArgs);
}
public bool Replace(T element, T newElement)
{
if (this.Contains(element))
{
_FreezeNotifications = true;
var index = this.IndexOf(element);
this.Insert(index, newElement);
this.Remove(element);
_FreezeNotifications = false;
var eventArgs = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, newElement, element);
onCollectionChanged(this, eventArgs);
base.OnCollectionChanged(eventArgs);
return true;
}
return false;
}
[XmlIgnore]
public new T this[int index]
{
get { return base[index]; }
set { Replace(base[index], value); }
}
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
handleNotificationsFromItemsInCollection(e);
if (!_FreezeNotifications)
{
onCollectionChanged(this, e);
base.OnCollectionChanged(e);
}
}
private void handleNotificationsFromItemsInCollection(NotifyCollectionChangedEventArgs e)
{
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
{
var elem = e.NewItems.Cast<INotifyPropertyChanged>();
if (elem != null)
{
elem.ToList().ForEach(p => p.PropertyChanged += onCollectionItemPropertyChanged);
}
}
break;
case NotifyCollectionChangedAction.Move:
break;
case NotifyCollectionChangedAction.Remove:
{
var elem = e.OldItems.Cast<INotifyPropertyChanged>();
if (elem != null)
{
elem.ToList().ForEach(p => p.PropertyChanged -= onCollectionItemPropertyChanged);
}
}
break;
case NotifyCollectionChangedAction.Replace:
{
var elem = e.OldItems.Cast<INotifyPropertyChanged>();
if (elem != null)
{
elem.ToList().ForEach(p => p.PropertyChanged -= onCollectionItemPropertyChanged);
}
elem = e.NewItems.Cast<INotifyPropertyChanged>();
if (elem != null)
{
elem.ToList().ForEach(p => p.PropertyChanged += onCollectionItemPropertyChanged);
}
}
break;
case NotifyCollectionChangedAction.Reset:
{
if (e.OldItems != null)
{
var elem = e.OldItems.Cast<INotifyPropertyChanged>();
if (elem != null)
{
elem.ToList().ForEach(p => p.PropertyChanged -= onCollectionItemPropertyChanged);
}
}
}
break;
default:
break;
}
}
protected override void ClearItems()
{
this.ToList().ForEach(p => p.PropertyChanged -= onCollectionItemPropertyChanged);
base.ClearItems();
}
protected virtual void onCollectionItemPropertyChanged(object sender, PropertyChangedEventArgs e)
{
_CollectionItemChangedHandler(sender, e);
}
protected virtual void onCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
_CollectionChangedHandler(sender, e);
}
}
public static class ObservableCollectionDeepExtensionMethods
{
public static ObservableCollectionDeep<T> ToObservableCollectionDeep<T>(this IEnumerable<T> collection,
Action<object, NotifyCollectionChangedEventArgs> collectionChangedHandler,
Action<object, PropertyChangedEventArgs> collectionItemChangedHandler)
where T : INotifyPropertyChanged
{
var c = new ObservableCollectionDeep<T>(collectionChangedHandler, collectionItemChangedHandler);
c.AddRange(collection);
return c;
}
}
Using ObservableCollectionDeep instead of ObservableCollection and "re-throwing" the notifications coming from this collection to the upper-level thanks to the two delegates, my application can subscribe to Document.PropertyChanged only and get notified for any changes.
I would like to have some feedback; Pros and Cons are welcome!
Thank you.