清除可观察集合时,e.OldItems 中没有项

我这里有些东西真的让我措手不及。

我有一个 T 的观察收集,充满了项目。我还有一个附加到 CollectionChanged 事件的事件处理程序。

当您 安全集合时,它会导致 CollectionChanged 事件,e. Action 设置为 NotifyCollectionChangedAction。重置。这很正常。但奇怪的是 e. OldItems 和 e. NewItems 都没有任何内容。我希望 e. OldItems 被从集合中移除的所有项填充。

还有其他人看过这个吗? 如果有,他们是怎么绕过去的?

一些背景知识: 我正在使用 CollectionChanged 事件来附加和分离另一个事件,因此如果我没有在 e. OldItems 中获得任何项目... 我将无法从该事件中分离。


澄清: 我确实知道,文档中没有 直截了当说明它必须以这种方式工作。但对于其他每一个行动,它都在通知我它已经做了什么。因此,我的假设是,它会告诉我... 在清除/重置的情况下,以及。


下面是示例代码,如果您希望自己复制它,首先是 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>

下一步,代码背后:

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 };
}
}
50720 次浏览

Looking at the NotifyCollectionChangedEventArgs, it appears that OldItems only contains items changed as a result of Replace, Remove, or Move action. It doesn't indicate that it will contain anything on Clear. I suspect that Clear fires the event, but does not registered the removed items and does not invoke the Remove code at all.

This is how ObservableCollection works, you can work around this by keeping your own list outside of the ObservableCollection (adding to the list when action is Add, remove when action is Remove etc.) then you can get all the removed items (or added items) when action is Reset by comparing your list with the ObservableCollection.

Another option is to create your own class that implements IList and INotifyCollectionChanged, then you can attach and detach events from within that class (or set OldItems on Clear if you like) - it's really not difficult, but it is a lot of typing.

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.

Ok, even though I still wish that ObservableCollection behaved as I wished ... the code below is what I ended up doing. Basically, I created a new collection of T called TrulyObservableCollection and overrided the ClearItems method which I then used to raise a Clearing event.

In the code that uses this TrulyObservableCollection, I use this Clearing event to loop through the items that are still in the collection at that point to do the detach on the event that I was wishing to detach from.

Hope this approach helps someone else as well.

public class TrulyObservableCollection<T> : ObservableCollection<T>
{
public event EventHandler<EventArgs> Clearing;
protected virtual void OnClearing(EventArgs e)
{
if (Clearing != null)
Clearing(this, e);
}


protected override void ClearItems()
{
OnClearing(EventArgs.Empty);
base.ClearItems();
}
}

I tackled this one in a slightly different manner as I wanted to register to one event and handle all additions and removals in the event handler. I started off overriding the collection changed event and redirecting reset actions to removal actions with a list of items. This all went wrong as I was using the observable collection as an items source for a collection view and got "Range actions not supported".

I finally created a new event called CollectionChangedRange which acts in the manner I expected the inbuilt version to act.

I can't imagine why this limitation would be allowed and hope that this post at least stops others from going down the dead end that I did.

/// <summary>
/// An observable collection with support for addrange and clear
/// </summary>
/// <typeparam name="T"></typeparam>
[Serializable]
[TypeConverter(typeof(ExpandableObjectConverter))]
public class ObservableCollectionRange<T> : ObservableCollection<T>
{
private bool _addingRange;


[field: NonSerialized]
public event NotifyCollectionChangedEventHandler CollectionChangedRange;


protected virtual void OnCollectionChangedRange(NotifyCollectionChangedEventArgs e)
{
if ((CollectionChangedRange == null) || _addingRange) return;
using (BlockReentrancy())
{
CollectionChangedRange(this, e);
}
}


public void AddRange(IEnumerable<T> collection)
{
CheckReentrancy();
var newItems = new List<T>();
if ((collection == null) || (Items == null)) return;
using (var enumerator = collection.GetEnumerator())
{
while (enumerator.MoveNext())
{
_addingRange = true;
Add(enumerator.Current);
_addingRange = false;
newItems.Add(enumerator.Current);
}
}
OnCollectionChangedRange(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, newItems));
}


protected override void ClearItems()
{
CheckReentrancy();
var oldItems = new List<T>(this);
base.ClearItems();
OnCollectionChangedRange(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, oldItems));
}


protected override void InsertItem(int index, T item)
{
CheckReentrancy();
base.InsertItem(index, item);
OnCollectionChangedRange(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, index));
}


protected override void MoveItem(int oldIndex, int newIndex)
{
CheckReentrancy();
var item = base[oldIndex];
base.MoveItem(oldIndex, newIndex);
OnCollectionChangedRange(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Move, item, newIndex, oldIndex));
}


protected override void RemoveItem(int index)
{
CheckReentrancy();
var item = base[index];
base.RemoveItem(index);
OnCollectionChangedRange(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item, index));
}


protected override void SetItem(int index, T item)
{
CheckReentrancy();
var oldItem = base[index];
base.SetItem(index, item);
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, oldItem, item, index));
}
}


/// <summary>
/// A read only observable collection with support for addrange and clear
/// </summary>
/// <typeparam name="T"></typeparam>
[Serializable]
[TypeConverter(typeof(ExpandableObjectConverter))]
public class ReadOnlyObservableCollectionRange<T> : ReadOnlyObservableCollection<T>
{
[field: NonSerialized]
public event NotifyCollectionChangedEventHandler CollectionChangedRange;


public ReadOnlyObservableCollectionRange(ObservableCollectionRange<T> list) : base(list)
{
list.CollectionChangedRange += HandleCollectionChangedRange;
}


private void HandleCollectionChangedRange(object sender, NotifyCollectionChangedEventArgs e)
{
OnCollectionChangedRange(e);
}


protected virtual void OnCollectionChangedRange(NotifyCollectionChangedEventArgs args)
{
if (CollectionChangedRange != null)
{
CollectionChangedRange(this, args);
}
}


}

I was just going through some of the charting code in the Silverlight and WPF toolkits and noticed that they also solved this problem (in a kind of similar way) ... and I thought I would go ahead and post their solution.

Basically, they also created a derived ObservableCollection and overrode ClearItems, calling Remove on each item being cleared.

Here is the code:

/// <summary>
/// An observable collection that cannot be reset.  When clear is called
/// items are removed individually, giving listeners the chance to detect
/// each remove event and perform operations such as unhooking event
/// handlers.
/// </summary>
/// <typeparam name="T">The type of item in the collection.</typeparam>
public class NoResetObservableCollection<T> : ObservableCollection<T>
{
public NoResetObservableCollection()
{
}


/// <summary>
/// Clears all items in the collection by removing them individually.
/// </summary>
protected override void ClearItems()
{
IList<T> items = new List<T>(this);
foreach (T item in items)
{
Remove(item);
}
}
}

Well, I decided to get dirty with it myself.

Microsoft put a LOT of work into always making sure the NotifyCollectionChangedEventArgs doesn't have any data when calling a reset. I'm assuming this was a performance/memory decision. If you are resetting a collection with 100,000 elements, I'm assuming they didn't want to duplicate all those elements.

But seeing as my collections never have more then 100 elements, I don't see a problem with it.

Anyway I created an inherited class with the following method:

protected override void ClearItems()
{
CheckReentrancy();
List<TItem> oldItems = new List<TItem>(Items);


Items.Clear();


OnPropertyChanged(new PropertyChangedEventArgs("Count"));
OnPropertyChanged(new PropertyChangedEventArgs("Item[]"));


NotifyCollectionChangedEventArgs e =
new NotifyCollectionChangedEventArgs
(
NotifyCollectionChangedAction.Reset
);


FieldInfo field =
e.GetType().GetField
(
"_oldItems",
BindingFlags.Instance | BindingFlags.NonPublic
);
field.SetValue(e, oldItems);


OnCollectionChanged(e);
}

For the scenario of attaching and detaching event handlers to the elements of the ObservableCollection there is also a "client-side" solution. In the event handling code you can check if the sender is in the ObservableCollection using the Contains method. Pro: you can work with any existing ObservableCollection. Cons: the Contains method runs with O(n) where n is the number of elements in the ObservableCollection. So this is a solution for small ObservableCollections.

Another "client-side" solution is to use an event handler in the middle. Just register all events to the event handler in the middle. This event handler in turn notifies the real event handler trough a callback or an event. If a Reset action occurs remove the callback or event create a new event handler in the middle and forget about the old one. This approach also works for big ObservableCollections. I used this for the PropertyChanged event (see code below).

    /// <summary>
/// Helper class that allows to "detach" all current Eventhandlers by setting
/// DelegateHandler to null.
/// </summary>
public class PropertyChangedDelegator
{
/// <summary>
/// Callback to the real event handling code.
/// </summary>
public PropertyChangedEventHandler DelegateHandler;
/// <summary>
/// Eventhandler that is registered by the elements.
/// </summary>
/// <param name="sender">the element that has been changed.</param>
/// <param name="e">the event arguments</param>
public void PropertyChangedHandler(Object sender, PropertyChangedEventArgs e)
{
if (DelegateHandler != null)
{
DelegateHandler(sender, e);
}
else
{
INotifyPropertyChanged s = sender as INotifyPropertyChanged;
if (s != null)
s.PropertyChanged -= PropertyChangedHandler;
}
}
}

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.

The ObservableCollection as well as the INotifyCollectionChanged interface are clearly written with a specific use in mind: UI building and its specific performance characteristics.

When you want notifications of collection changes then you are generally only interested in Add and Remove events.

I use the following interface:

using System;
using System.Collections.Generic;


/// <summary>
/// Notifies listeners of the following situations:
/// <list type="bullet">
/// <item>Elements have been added.</item>
/// <item>Elements are about to be removed.</item>
/// </list>
/// </summary>
/// <typeparam name="T">The type of elements in the collection.</typeparam>
interface INotifyCollection<T>
{
/// <summary>
/// Occurs when elements have been added.
/// </summary>
event EventHandler<NotifyCollectionEventArgs<T>> Added;


/// <summary>
/// Occurs when elements are about to be removed.
/// </summary>
event EventHandler<NotifyCollectionEventArgs<T>> Removing;
}


/// <summary>
/// Provides data for the NotifyCollection event.
/// </summary>
/// <typeparam name="T">The type of elements in the collection.</typeparam>
public class NotifyCollectionEventArgs<T> : EventArgs
{
/// <summary>
/// Gets or sets the elements.
/// </summary>
/// <value>The elements.</value>
public IEnumerable<T> Items
{
get;
set;
}
}

I've also written my own overload of Collection where:

  • ClearItems raises Removing
  • InsertItem raises Added
  • RemoveItem raises Removing
  • SetItem raises Removing and Added

Of course, AddRange can be added as well.

This is a hot subject ... because in my opinion, Microsoft did not do its job properly ... yet again. Don't misunderstand me, I like Microsoft, but they are not perfect!

I read most of the previous comments. I agree with all those who think that Microsoft did not programmed Clear() properly.

In my opinion, at least, it needs an argument to make it possible to detach objects from an event ... but I also understand the impact of it. Then, I thought up this proposed solution.

I hope it will make everybody happy, or at least, most everyone ...

Eric

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Reflection;


namespace WpfUtil.Collections
{
public static class ObservableCollectionExtension
{
public static void RemoveAllOneByOne<T>(this ObservableCollection<T> obsColl)
{
foreach (T item in obsColl)
{
while (obsColl.Count > 0)
{
obsColl.RemoveAt(0);
}
}
}


public static void RemoveAll<T>(this ObservableCollection<T> obsColl)
{
if (obsColl.Count > 0)
{
List<T> removedItems = new List<T>(obsColl);
obsColl.Clear();


NotifyCollectionChangedEventArgs e =
new NotifyCollectionChangedEventArgs
(
NotifyCollectionChangedAction.Remove,
removedItems
);
var eventInfo =
obsColl.GetType().GetField
(
"CollectionChanged",
BindingFlags.Instance | BindingFlags.NonPublic
);
if (eventInfo != null)
{
var eventMember = eventInfo.GetValue(obsColl);
// note: if eventMember is null
// nobody registered to the event, you can't call it.
if (eventMember != null)
eventMember.GetType().GetMethod("Invoke").
Invoke(eventMember, new object[] { obsColl, e });
}
}
}
}
}

http://msdn.microsoft.com/en-us/library/system.collections.specialized.notifycollectionchangedaction(VS.95).aspx

Please read this documentation with your eyes open and your brain turned on. Microsoft did everything right. You must re-scan your collection when it throws a Reset notification for you. You get a Reset notification because throwing Add/Remove for each item (being removed from and added back to collection) is too expensive.

Orion Edwards is completely right (respect, man). Please think wider when reading the documentation.

To keep it simple why don't you override the ClearItem method and do whatever you want there ie Detach the items from the event.

public class PeopleAttributeList : ObservableCollection<PeopleAttributeDto>,    {
{
protected override void ClearItems()
{
Do what ever you want
base.ClearItems();
}


rest of the code omitted
}

Simple, clean, and contain within the collection code.

Another option is to replace the Reset event with a single Remove event that has all the cleared items in its OldItems property as follows:

public class ObservableCollectionNoReset<T> : ObservableCollection<T>
{
protected override void ClearItems()
{
List<T> removed = new List<T>(this);
base.ClearItems();
base.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, removed));
}


protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
if (e.Action != NotifyCollectionChangedAction.Reset)
base.OnCollectionChanged(e);
}
// Constructors omitted
...
}

Advantages:

  1. No need to subscribe to an additional event (as required by accepted answer)

  2. Doesn't generate an event for each object removed (some other proposed solutions result in multiple Removed events).

  3. Subscriber only needs to check NewItems & OldItems on any event to add/remove event handlers as required.

Disadvantages:

  1. No Reset event

  2. Small (?) overhead creating copy of list.

  3. ???

EDIT 2012-02-23

Unfortunately, when bound to WPF list based controls, Clearing a ObservableCollectionNoReset collection with multiple elements will result in an exception "Range actions not supported". To be used with controls with this limitation, I changed the ObservableCollectionNoReset class to:

public class ObservableCollectionNoReset<T> : ObservableCollection<T>
{
// Some CollectionChanged listeners don't support range actions.
public Boolean RangeActionsSupported { get; set; }


protected override void ClearItems()
{
if (RangeActionsSupported)
{
List<T> removed = new List<T>(this);
base.ClearItems();
base.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, removed));
}
else
{
while (Count > 0 )
base.RemoveAt(Count - 1);
}
}


protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
if (e.Action != NotifyCollectionChangedAction.Reset)
base.OnCollectionChanged(e);
}


public ObservableCollectionNoReset(Boolean rangeActionsSupported = false)
{
RangeActionsSupported = rangeActionsSupported;
}


// Additional constructors omitted.
}

This isn't as efficient when RangeActionsSupported is false (the default) because one Remove notification is generated per object in the collection

I've found a solution that allows the user to both capitalize on the efficiency of adding or removing many items at a time while only firing one event - and satisfy the needs of UIElements to get the Action.Reset event args while all other users would like a list of elements added and removed.

This solution involves overriding the CollectionChanged event. When we go to fire this event, we can actually look at the target of each registered handler and determine their type. Since only ICollectionView classes require NotifyCollectionChangedAction.Reset args when more than one item changes, we can single them out, and give everyone else proper event args that contain the full list of items removed or added. Below is the implementation.

public class BaseObservableCollection<T> : ObservableCollection<T>
{
//Flag used to prevent OnCollectionChanged from firing during a bulk operation like Add(IEnumerable<T>) and Clear()
private bool _SuppressCollectionChanged = false;


/// Overridden so that we may manually call registered handlers and differentiate between those that do and don't require Action.Reset args.
public override event NotifyCollectionChangedEventHandler CollectionChanged;


public BaseObservableCollection() : base(){}
public BaseObservableCollection(IEnumerable<T> data) : base(data){}


#region Event Handlers
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
if( !_SuppressCollectionChanged )
{
base.OnCollectionChanged(e);
if( CollectionChanged != null )
CollectionChanged.Invoke(this, e);
}
}


//CollectionViews raise an error when they are passed a NotifyCollectionChangedEventArgs that indicates more than
//one element has been added or removed. They prefer to receive a "Action=Reset" notification, but this is not suitable
//for applications in code, so we actually check the type we're notifying on and pass a customized event args.
protected virtual void OnCollectionChangedMultiItem(NotifyCollectionChangedEventArgs e)
{
NotifyCollectionChangedEventHandler handlers = this.CollectionChanged;
if( handlers != null )
foreach( NotifyCollectionChangedEventHandler handler in handlers.GetInvocationList() )
handler(this, !(handler.Target is ICollectionView) ? e : new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
#endregion


#region Extended Collection Methods
protected override void ClearItems()
{
if( this.Count == 0 ) return;


List<T> removed = new List<T>(this);
_SuppressCollectionChanged = true;
base.ClearItems();
_SuppressCollectionChanged = false;
OnCollectionChangedMultiItem(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, removed));
}


public void Add(IEnumerable<T> toAdd)
{
if( this == toAdd )
throw new Exception("Invalid operation. This would result in iterating over a collection as it is being modified.");


_SuppressCollectionChanged = true;
foreach( T item in toAdd )
Add(item);
_SuppressCollectionChanged = false;
OnCollectionChangedMultiItem(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, new List<T>(toAdd)));
}


public void Remove(IEnumerable<T> toRemove)
{
if( this == toRemove )
throw new Exception("Invalid operation. This would result in iterating over a collection as it is being modified.");


_SuppressCollectionChanged = true;
foreach( T item in toRemove )
Remove(item);
_SuppressCollectionChanged = false;
OnCollectionChangedMultiItem(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, new List<T>(toRemove)));
}
#endregion
}

If your ObservableCollection is not getting clear, then you may try this below code. it may help you:

private TestEntities context; // This is your context


context.Refresh(System.Data.Objects.RefreshMode.StoreWins, context.UserTables); // to refresh the object context

I had the same issue, and this was my solution. It seems to work. Does anyone see any potential problems with this approach?

// overriden so that we can call GetInvocationList
public override event NotifyCollectionChangedEventHandler CollectionChanged;


protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
NotifyCollectionChangedEventHandler collectionChanged = CollectionChanged;
if (collectionChanged != null)
{
lock (collectionChanged)
{
foreach (NotifyCollectionChangedEventHandler handler in collectionChanged.GetInvocationList())
{
try
{
handler(this, e);
}
catch (NotSupportedException ex)
{
// this will occur if this collection is used as an ItemsControl.ItemsSource
if (ex.Message == "Range actions are not supported.")
{
handler(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
else
{
throw ex;
}
}
}
}
}
}

Here are some other useful methods in my class:

public void SetItems(IEnumerable<T> newItems)
{
Items.Clear();
foreach (T newItem in newItems)
{
Items.Add(newItem);
}
NotifyCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}


public void AddRange(IEnumerable<T> newItems)
{
int index = Count;
foreach (T item in newItems)
{
Items.Add(item);
}
NotifyCollectionChangedEventArgs e = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, new List<T>(newItems), index);
NotifyCollectionChanged(e);
}


public void RemoveRange(int startingIndex, int count)
{
IList<T> oldItems = new List<T>();
for (int i = 0; i < count; i++)
{
oldItems.Add(Items[startingIndex]);
Items.RemoveAt(startingIndex);
}
NotifyCollectionChangedEventArgs e = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, new List<T>(oldItems), startingIndex);
NotifyCollectionChanged(e);
}


// this needs to be overridden to avoid raising a NotifyCollectionChangedEvent with NotifyCollectionChangedAction.Reset, which our other lists don't support
new public void Clear()
{
RemoveRange(0, Count);
}


public void RemoveWhere(Func<T, bool> criterion)
{
List<T> removedItems = null;
int startingIndex = default(int);
int contiguousCount = default(int);
for (int i = 0; i < Count; i++)
{
T item = Items[i];
if (criterion(item))
{
if (removedItems == null)
{
removedItems = new List<T>();
startingIndex = i;
contiguousCount = 0;
}
Items.RemoveAt(i);
removedItems.Add(item);
contiguousCount++;
}
else if (removedItems != null)
{
NotifyCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, removedItems, startingIndex));
removedItems = null;
i = startingIndex;
}
}
if (removedItems != null)
{
NotifyCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, removedItems, startingIndex));
}
}


private void NotifyCollectionChanged(NotifyCollectionChangedEventArgs e)
{
OnPropertyChanged(new PropertyChangedEventArgs("Count"));
OnPropertyChanged(new PropertyChangedEventArgs("Item[]"));
OnCollectionChanged(e);
}

I found another "simple" solution deriving from ObservableCollection, but it is not very elegant because it uses Reflection... If you like it here is my solution:

public class ObservableCollectionClearable<T> : ObservableCollection<T>
{
private T[] ClearingItems = null;


protected override void OnCollectionChanged(System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
switch (e.Action)
{
case System.Collections.Specialized.NotifyCollectionChangedAction.Reset:
if (this.ClearingItems != null)
{
ReplaceOldItems(e, this.ClearingItems);
this.ClearingItems = null;
}
break;
}
base.OnCollectionChanged(e);
}


protected override void ClearItems()
{
this.ClearingItems = this.ToArray();
base.ClearItems();
}


private static void ReplaceOldItems(System.Collections.Specialized.NotifyCollectionChangedEventArgs e, T[] olditems)
{
Type t = e.GetType();
System.Reflection.FieldInfo foldItems = t.GetField("_oldItems", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
if (foldItems != null)
{
foldItems.SetValue(e, olditems);
}
}
}

Here I save the current elements in an array field in the ClearItems method, then I intercept the call of OnCollectionChanged and overwrite the e._oldItems private field (through Reflections) before launching base.OnCollectionChanged

You can override ClearItems method and raise event with Remove action and OldItems .

public class ObservableCollection<T> : System.Collections.ObjectModel.ObservableCollection<T>
{
protected override void ClearItems()
{
CheckReentrancy();
var items = Items.ToList();
base.ClearItems();
OnPropertyChanged(new PropertyChangedEventArgs("Count"));
OnPropertyChanged(new PropertyChangedEventArgs("Item[]"));
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, items, -1));
}
}

Part of System.Collections.ObjectModel.ObservableCollection<T> realization:

public class ObservableCollection<T> : Collection<T>, INotifyCollectionChanged, INotifyPropertyChanged
{
protected override void ClearItems()
{
CheckReentrancy();
base.ClearItems();
OnPropertyChanged(CountString);
OnPropertyChanged(IndexerName);
OnCollectionReset();
}


private void OnPropertyChanged(string propertyName)
{
OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
}


private void OnCollectionReset()
{
OnCollectionChanged(new   NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}


private const string CountString = "Count";


private const string IndexerName = "Item[]";
}

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.