Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

When Clearing an ObservableCollection, There are No Items in e.OldItems

I have something here that is really catching me off guard.

I have an ObservableCollection of T that is filled with items. I also have an event handler attached to the CollectionChanged event.

When you Clear the collection it causes an CollectionChanged event with e.Action set to NotifyCollectionChangedAction.Reset. Ok, that's normal. But what is weird is that neither e.OldItems or e.NewItems has anything in it. I would expect e.OldItems to be filled with all items that were removed from the collection.

Has anyone else seen this? And if so, how have they gotten around it?

Some background: I am using the CollectionChanged event to attach and detach from another event and thus if I don't get any items in e.OldItems ... I won't be able to detach from that event.


CLARIFICATION: I do know that the documentation doesn't outright state that it has to behave this way. But for every other action, it is notifying me of what it has done. So, my assumption is that it would tell me ... in the case of Clear/Reset as well.


Below is the sample code if you wish to reproduce it yourself. First off the xaml:

<Window
    x:Class="ObservableCollection.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Window1"
    Height="300"
    Width="300"
>
    <StackPanel>
        <Button x:Name="addButton" Content="Add" Width="100" Height="25" Margin="10" Click="addButton_Click"/>
        <Button x:Name="moveButton" Content="Move" Width="100" Height="25" Margin="10" Click="moveButton_Click"/>
        <Button x:Name="removeButton" Content="Remove" Width="100" Height="25" Margin="10" Click="removeButton_Click"/>
        <Button x:Name="replaceButton" Content="Replace" Width="100" Height="25" Margin="10" Click="replaceButton_Click"/>
        <Button x:Name="resetButton" Content="Reset" Width="100" Height="25" Margin="10" Click="resetButton_Click"/>
    </StackPanel>
</Window>

Next, the code behind:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Collections.ObjectModel;

namespace ObservableCollection
{
    /// <summary>
    /// Interaction logic for Window1.xaml
    /// </summary>
    public partial class Window1 : Window
    {
        public Window1()
        {
            InitializeComponent();
            _integerObservableCollection.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(_integerObservableCollection_CollectionChanged);
        }

        private void _integerObservableCollection_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
        {
            switch (e.Action)
            {
                case System.Collections.Specialized.NotifyCollectionChangedAction.Add:
                    break;
                case System.Collections.Specialized.NotifyCollectionChangedAction.Move:
                    break;
                case System.Collections.Specialized.NotifyCollectionChangedAction.Remove:
                    break;
                case System.Collections.Specialized.NotifyCollectionChangedAction.Replace:
                    break;
                case System.Collections.Specialized.NotifyCollectionChangedAction.Reset:
                    break;
                default:
                    break;
            }
        }

        private void addButton_Click(object sender, RoutedEventArgs e)
        {
            _integerObservableCollection.Add(25);
        }

        private void moveButton_Click(object sender, RoutedEventArgs e)
        {
            _integerObservableCollection.Move(0, 19);
        }

        private void removeButton_Click(object sender, RoutedEventArgs e)
        {
            _integerObservableCollection.RemoveAt(0);
        }

        private void replaceButton_Click(object sender, RoutedEventArgs e)
        {
            _integerObservableCollection[0] = 50;
        }

        private void resetButton_Click(object sender, RoutedEventArgs e)
        {
            _integerObservableCollection.Clear();
        }

        private ObservableCollection<int> _integerObservableCollection = new ObservableCollection<int> { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 };
    }
}
like image 584
cplotts Avatar asked Oct 22 '08 01:10

cplotts


3 Answers

It doesn't claim to include the old items, because Reset doesn't mean that the list has been cleared

It means that some dramatic thing has taken place, and the cost of working out the add/removes would most likely exceed the cost of just re-scanning the list from scratch... so that's what you should do.

MSDN suggests an example of the entire collection being re-sorted as a candidate for reset.

To reiterate. Reset doesn't mean clear, it means Your assumptions about the list are now invalid. Treat it as if it's an entirely new list. Clear happens to be one instance of this, but there could well be others.

Some examples:
I've had a list like this with a lot of items in it, and it has been databound to a WPF ListView to display on-screen.
If you clear the list and raise the .Reset event, the performance is pretty much instant, but if you instead raise many individual .Remove events, the performance is terrible, as WPF removes the items one by one. I've also used .Reset in my own code to indicate that the list has been re-sorted, rather than issuing thousands of individual Move operations. As with Clear, there is a large performance hit when when raising many individual events.

like image 198
Orion Edwards Avatar answered Oct 21 '22 09:10

Orion Edwards


We had the same issue here. The Reset action in CollectionChanged does not include the OldItems. We had a workaround: we used instead the following extension method:

public static void RemoveAll(this IList list)
{
   while (list.Count > 0)
   {
      list.RemoveAt(list.Count - 1);
   }
}

We ended up not supporting the Clear() function, and throwing a NotSupportedException in CollectionChanged event for Reset actions. The RemoveAll will trigger a Remove action in CollectionChanged event, with the proper OldItems.

like image 24
decasteljau Avatar answered Oct 21 '22 10:10

decasteljau


Okay, I know this is a very old question but I have come up with a good solution to the issue and thought I would share. This solution takes inspiration from a lot of the great answers here but has the following advantages:

  • No need to create a new class and override methods from ObservableCollection
  • Does not tamper with the workings of NotifyCollectionChanged (so no messing with Reset)
  • Does not make use of reflection

Here is the code:

 public static void Clear<T>(this ObservableCollection<T> collection, Action<ObservableCollection<T>> unhookAction)
 {
     unhookAction.Invoke(collection);
     collection.Clear();
 }

This extension method simply takes an Action which will be invoked before the collection is cleared.

like image 15
DeadlyEmbrace Avatar answered Oct 21 '22 10:10

DeadlyEmbrace