Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Observe PropertyChanged on items in a collection

Tags:

c#

wpf

.net-4.0

I'm trying to hook into an event on INotifyPropertyChanged objects in a collection.

Every answer that I've ever seen to this question has said to handle it as follows:

void NotifyingItems_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
    if( e.NewItems != null )
    {
        foreach( INotifyPropertyChanged item in e.NewItems )
        {
            item.PropertyChanged += new PropertyChangedEventHandler(CollectionItemChanged);
        }
    }
    if( e.OldItems != null )
    {
        foreach( ValidationMessageCollection item in e.OldItems )
        {
            item.PropertyChanged -= CollectionItemChanged;
        }
    }
}

My problem is that this completely fails whenever a developer calls Clear() on the NotifyingItems collection. When that happens, this event handler is called with e.Action == Reset and both e.NewItems and e.OldItems equal to null (I would expect the latter to contain all items).

The problem is those items don't go away, and they aren't destroyed, they are just no longer supposed to be monitored by the current class - but since I never got the chance to unmap their PropertyChangedEventHandler - they keep calling my CollectionItemChanged handler even after they've been cleared from my NotifyingItems list. How is such a situation supposed to be handled with this 'well established' pattern?

like image 559
Alain Avatar asked Feb 22 '12 16:02

Alain


1 Answers

Edit: This solution doesn't work

This solution from the question Rachel linked to appears to be brilliant:

If I replace my NotifyingItems ObservableCollection with an inheriting class that overrides the overrideable Collection.ClearItems() method, then I can intercept the NotifyCollectionChangedEventArgs and replace it with a Remove instead of a Reset operation, and pass the list of removed items:

//Makes sure on a clear, the list of removed items is actually included.
protected override void ClearItems()
{
    if( this.Count == 0 ) return;

    List<T> removed = new List<T>(this);
    base.ClearItems();
    base.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, removed));
}

protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
    //If the action is a reset (from calling base.Clear()) our overriding Clear() will call OnCollectionChanged, but properly.
    if( e.Action != NotifyCollectionChangedAction.Reset )
        base.OnCollectionChanged(e);
}

Brilliant, and nothing needs to be changed anywhere except in my own class.


*edit*

I loved this solution, but it doesn't work... You're not allowed to raise a NotifyCollectionChangedEventArgs that has more than one item changed unless the action is "Reset". You get the following runtime exception: Range actions are not supported. I don't know why it has to be so damn picky about this, but now this leaves no option but to remove each item one at a time... firing a new CollectionChanged event for each one. What a damn hassle.

like image 132
Alain Avatar answered Oct 15 '22 06:10

Alain