Lors de l’effacement d’une collection Observable, il n’y a aucun élément dans e.OldItems

J’ai quelque chose ici qui me prend vraiment au dépourvu.

J’ai une collection ObservableC de T qui est remplie d’éléments. J’ai également un gestionnaire d’événements attaché à l’événement CollectionChanged.

Lorsque vous effacez la collection, il provoque un événement CollectionChanged avec e.Action défini sur NotifyCollectionChangedAction.Reset. Ok, c’est normal. Mais ce qui est étrange, c’est que ni e.OldItems ni e.NewItems ne contiennent quelque chose. Je m’attendrais à ce que e.OldItems soit rempli avec tous les éléments qui ont été retirés de la collection.

Quelqu’un d’autre a-t-il vu cela? Et si oui, comment l’ont-ils compris?

Un peu d’arrière-plan: j’utilise l’événement CollectionChanged pour attacher et détacher un autre événement et donc si je ne reçois aucun élément dans e.OldItems … Je ne pourrai pas me détacher de cet événement.

CLARIFICATION: Je sais que la documentation ne dit pas clairement qu’elle doit se comporter de la sorte. Mais pour chaque autre action, il m’informe de ce qu’il a fait. Donc, mon hypothèse est que cela me dirait … dans le cas de Clear / Reset également.

Vous trouverez ci-dessous l’exemple de code si vous souhaitez le reproduire vous-même. Tout d’abord le xaml:

  

Ensuite, le code derrière:

 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 { ///  /// Interaction logic for Window1.xaml ///  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 _integerObservableCollection = new ObservableCollection { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 }; } } 

Il ne prétend pas inclure les anciens éléments, car la réinitialisation ne signifie pas que la liste a été effacée

Cela signifie que quelque chose de dramatique a eu lieu et que le coût de l’élaboration des ajouts et des retraits dépasserait très probablement le coût de la simple re-parsing de la liste à partir de rien… alors faites ce que vous devriez faire.

MSDN propose un exemple de recollection de la collection entière en tant que candidat à la réinitialisation.

Recommencer. Réinitialiser ne signifie pas clair , cela signifie que vos hypothèses concernant la liste sont désormais invalides. Traitez-le comme si c’était une liste entièrement nouvelle . Clarté se trouve être un exemple de cela, mais il pourrait bien y en avoir d’autres.

Quelques exemples:
J’ai une liste comme celle-ci avec beaucoup d’éléments, et elle a été reliée à une ListView de ListView WPF à afficher à l’écran.
Si vous effacez la liste et .Reset événement .Reset , les performances sont quasiment instantanées, mais si vous relancez plusieurs événements individuels, les performances sont terribles, car WPF supprime les éléments un par un. J’ai également utilisé .Reset dans mon propre code pour indiquer que la liste a été re-sortingée, plutôt que d’émettre des milliers d’opérations de Move individuelles. Comme avec Clear, il y a un grand impact sur les performances lors de la création de nombreux événements individuels.

Nous avons eu le même problème ici. L’action Reset dans CollectionChanged n’inclut pas les OldItems. Nous avons eu une solution de contournement: nous avons plutôt utilisé la méthode d’extension suivante:

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

Nous avons fini par ne pas supporter la fonction Clear () et lancer un événement NotSupportedException dans CollectionChanged pour les actions Reset. Le RemoveAll déclenchera une action Remove dans l’événement CollectionChanged, avec les OldItems appropriés.

Une autre option consiste à remplacer l’événement Reset par un seul événement Remove qui contient tous les éléments effacés de sa propriété OldItems, comme suit:

 public class ObservableCollectionNoReset : ObservableCollection { protected override void ClearItems() { List removed = new List(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 ... } 

Avantages:

  1. Pas besoin de s’abonner à un événement supplémentaire (comme requirejs par la réponse acceptée)

  2. Ne génère pas d’événement pour chaque object supprimé (d’autres solutions proposées entraînent plusieurs événements supprimés).

  3. L’abonné doit uniquement vérifier NewItems & OldItems sur tous les événements pour append / supprimer des gestionnaires d’événements, si nécessaire.

Désavantages:

  1. Aucun événement de réinitialisation

  2. Petit (?) Surcharge créant une copie de la liste.

  3. ???

EDIT 2012-02-23

Malheureusement, lorsqu’elle est liée à des contrôles basés sur une liste WPF, l’effacement d’une collection ObservableCollectionNoReset avec plusieurs éléments entraîne une exception “Actions de plage non sockets en charge”. Pour être utilisé avec des contrôles avec cette limitation, j’ai changé la classe ObservableCollectionNoReset en:

 public class ObservableCollectionNoReset : ObservableCollection { // Some CollectionChanged listeners don't support range actions. public Boolean RangeActionsSupported { get; set; } protected override void ClearItems() { if (RangeActionsSupported) { List removed = new List(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. } 

Ce n’est pas aussi efficace lorsque RangeActionsSupported est false (valeur par défaut) car une notification Remove est générée par object dans la collection.

J’ai trouvé une solution qui permet à l’utilisateur de tirer parti de l’efficacité de l’ajout ou de la suppression de nombreux éléments à la fois tout en ne tirant qu’un seul événement – et de répondre aux besoins des UIElements pour obtenir les arguments de l’événement Action. comme une liste d’éléments ajoutés et supprimés.

Cette solution implique le remplacement de l’événement CollectionChanged. Lorsque nous allons déclencher cet événement, nous pouvons réellement regarder la cible de chaque gestionnaire enregistré et déterminer leur type. Étant donné que seules les classes ICollectionView requièrent des NotifyCollectionChangedAction.Reset lorsque plusieurs éléments sont modifiés, nous pouvons les isoler et donner à tous les autres arguments d’événement corrects contenant la liste complète des éléments supprimés ou ajoutés. Ci-dessous la mise en œuvre.

 public class BaseObservableCollection : ObservableCollection { //Flag used to prevent OnCollectionChanged from firing during a bulk operation like Add(IEnumerable) 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 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 removed = new List(this); _SuppressCollectionChanged = true; base.ClearItems(); _SuppressCollectionChanged = false; OnCollectionChangedMultiItem(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, removed)); } public void Add(IEnumerable 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(toAdd))); } public void Remove(IEnumerable 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(toRemove))); } #endregion } 

Ok, même si je souhaite toujours que ObservableCollection se soit comporté comme je le souhaitais … le code ci-dessous est ce que j’ai fini par faire. Fondamentalement, j’ai créé une nouvelle collection de T appelée TrulyObservableCollection et j’ai écrasé la méthode ClearItems que j’ai ensuite utilisée pour générer un événement Clearing.

Dans le code qui utilise cette TrulyObservableCollection, j’utilise cet événement Clearing pour parcourir les éléments qui se trouvent encore dans la collection à ce stade afin de faire le lien avec l’événement que je souhaitais détacher.

J’espère que cette approche aide également quelqu’un d’autre.

 public class TrulyObservableCollection : ObservableCollection { public event EventHandler Clearing; protected virtual void OnClearing(EventArgs e) { if (Clearing != null) Clearing(this, e); } protected override void ClearItems() { OnClearing(EventArgs.Empty); base.ClearItems(); } } 

D’accord, je sais que c’est une très vieille question mais j’ai trouvé une bonne solution au problème et j’ai pensé partager. Cette solution s’inspire de nombreuses bonnes réponses mais présente les avantages suivants:

  • Pas besoin de créer une nouvelle classe et de remplacer les méthodes depuis ObservableCollection
  • Ne modifie pas le fonctionnement de NotifyCollectionChanged (donc pas de problème avec Reset)
  • Ne fait pas appel à la reflection

Voici le code:

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

Cette méthode d’extension prend simplement une Action qui sera appelée avant que la collection ne soit effacée.

Voici comment fonctionne ObservableCollection, vous pouvez contourner ce problème en conservant votre propre liste en dehors de ObservableCollection (en ajoutant à la liste lorsque l’action est Add, en supprimant l’action Remove etc.), vous pouvez obtenir tous les éléments supprimés (ou éléments ajoutés) ) lorsque l’action est réinitialisée en comparant votre liste avec la collection ObservableCollection.

Une autre option consiste à créer votre propre classe qui implémente IList et INotifyCollectionChanged, puis vous pouvez attacher et détacher des événements de cette classe (ou définir OldItems sur Clear si vous le souhaitez) – ce n’est vraiment pas difficile, mais c’est beaucoup de frappe.

J’ai abordé celui-ci d’une manière légèrement différente car je voulais m’inscrire à un événement et gérer tous les ajouts et les suppressions dans le gestionnaire d’événements. J’ai commencé par remplacer l’événement de collecte et redirect les actions de réinitialisation vers les actions de suppression avec une liste d’éléments. Tout cela s’est mal passé car j’utilisais la collection observable en tant que source d’éléments pour une vue de collection et obtenais “Actions de plage non sockets en charge”.

J’ai finalement créé un nouvel événement appelé CollectionChangedRange qui agit de la manière dont je m’attendais à ce que la version intégrée fonctionne.

Je ne peux pas imaginer pourquoi cette limitation serait autorisée et j’espère que cet article empêche au moins les autres de descendre dans l’impasse que j’ai faite.

 ///  /// An observable collection with support for addrange and clear ///  ///  [Serializable] [TypeConverter(typeof(ExpandableObjectConverter))] public class ObservableCollectionRange : ObservableCollection { 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 collection) { CheckReentrancy(); var newItems = new List(); 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(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)); } } ///  /// A read only observable collection with support for addrange and clear ///  ///  [Serializable] [TypeConverter(typeof(ExpandableObjectConverter))] public class ReadOnlyObservableCollectionRange : ReadOnlyObservableCollection { [field: NonSerialized] public event NotifyCollectionChangedEventHandler CollectionChangedRange; public ReadOnlyObservableCollectionRange(ObservableCollectionRange 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); } } } 

Pour le scénario de rattachement et de détachement de gestionnaires d’événements aux éléments de ObservableCollection, il existe également une solution «côté client». Dans le code de gestion des événements, vous pouvez vérifier si l’expéditeur se trouve dans ObservableCollection à l’aide de la méthode Contains. Pro: vous pouvez travailler avec n’importe quelle ObservableCollection existante. Inconvénients: la méthode Contains s’exécute avec O (n) où n est le nombre d’éléments dans ObservableCollection. C’est donc une solution pour les petites collections Observable.

Une autre solution “côté client” consiste à utiliser un gestionnaire d’événements au milieu. Enregistrez simplement tous les événements dans le gestionnaire d’événements au milieu. Ce gestionnaire d’événements informe à son tour le gestionnaire d’événements réel via un rappel ou un événement. Si une action de réinitialisation se produit, supprimez le rappel ou l’événement créez un nouveau gestionnaire d’événements au milieu et oubliez l’ancien. Cette approche fonctionne également pour les grandes ObservableCollections. Je l’ai utilisé pour l’événement PropertyChanged (voir le code ci-dessous).

  ///  /// Helper class that allows to "detach" all current Eventhandlers by setting /// DelegateHandler to null. ///  public class PropertyChangedDelegator { ///  /// Callback to the real event handling code. ///  public PropertyChangedEventHandler DelegateHandler; ///  /// Eventhandler that is registered by the elements. ///  /// the element that has been changed. /// the event arguments 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; } } } 

En examinant NotifyCollectionChangedEventArgs , il apparaît qu’OldItems ne contient que des éléments modifiés à la suite d’une action Remplacer, Supprimer ou Déplacer. Cela n’indique pas qu’il contiendra quelque chose sur Clear. Je pense que Clear déclenche l’événement, mais n’enregistre pas les éléments supprimés et n’appelle pas du tout le code Remove.

Eh bien, j’ai décidé de me salir moi-même.

Microsoft met BEAUCOUP de travail à toujours s’assurer que NotifyCollectionChangedEventArgs n’a pas de données lors de l’appel d’une réinitialisation. Je suppose que c’était une décision performance / mémoire. Si vous réinitialisez une collection avec 100 000 éléments, je suppose qu’ils ne veulent pas dupliquer tous ces éléments.

Mais comme mes collections ne contiennent jamais plus de 100 éléments, je ne vois pas de problème.

Quoi qu’il en soit, j’ai créé une classe héritée avec la méthode suivante:

 protected override void ClearItems() { CheckReentrancy(); List oldItems = new List(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); } 

L’interface ObservableCollection ainsi que l’interface INotifyCollectionChanged sont clairement écrites pour une utilisation spécifique: la construction de l’interface utilisateur et ses caractéristiques de performances spécifiques.

Lorsque vous souhaitez recevoir des notifications de modifications de collection, vous n’êtes généralement intéressé que par les événements Ajouter et Supprimer.

J’utilise l’interface suivante:

 using System; using System.Collections.Generic; ///  /// Notifies listeners of the following situations: ///  /// Elements have been added. /// Elements are about to be removed. ///  ///  /// The type of elements in the collection. interface INotifyCollection { ///  /// Occurs when elements have been added. ///  event EventHandler> Added; ///  /// Occurs when elements are about to be removed. ///  event EventHandler> Removing; } ///  /// Provides data for the NotifyCollection event. ///  /// The type of elements in the collection. public class NotifyCollectionEventArgs : EventArgs { ///  /// Gets or sets the elements. ///  /// The elements. public IEnumerable Items { get; set; } } 

J’ai aussi écrit ma propre surcharge de Collection où:

  • ClearItems soulève la suppression
  • InsertItem lève Ajouté
  • RemoveItem déclenche la suppression
  • SetItem déclenche la suppression et l’ajout

Bien entendu, AddRange peut également être ajouté.

J’étais en train de parcourir quelques-uns des codes dans les toolkits Silverlight et WPF et j’ai remarqué qu’ils résolvaient également ce problème (de manière similaire) … et j’ai pensé aller de l’avant et poster leur solution.

Fondamentalement, ils ont également créé une ObservableCollection dérivée et ont surpassé ClearItems, appelant Remove sur chaque élément à effacer.

Voici le code:

 ///  /// 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. ///  /// The type of item in the collection. public class NoResetObservableCollection : ObservableCollection { public NoResetObservableCollection() { } ///  /// Clears all items in the collection by removing them individually. ///  protected override void ClearItems() { IList items = new List(this); foreach (T item in items) { Remove(item); } } } 

C’est un sujet brûlant … car à mon avis, Microsoft n’a pas fait son travail correctement … encore une fois. Ne vous méprenez pas, j’aime Microsoft, mais ils ne sont pas parfaits!

J’ai lu la plupart des commentaires précédents. Je suis d’accord avec tous ceux qui pensent que Microsoft n’a pas programmé Clear () correctement.

A mon avis, au moins, il faut un argument pour permettre de détacher des objects d’un événement … mais je comprends aussi son impact. Ensuite, j’ai pensé à cette solution proposée.

J’espère que cela rendra tout le monde heureux, ou du moins la plupart des gens …

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(this ObservableCollection obsColl) { foreach (T item in obsColl) { while (obsColl.Count > 0) { obsColl.RemoveAt(0); } } } public static void RemoveAll(this ObservableCollection obsColl) { if (obsColl.Count > 0) { List removedItems = new List(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 }); } } } } } 

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 PeopleAtsortingbuteList : ObservableCollection, { { protected override void ClearItems() { Do what ever you want base.ClearItems(); } rest of the code omitted } 

Simple, clean, and contain within the collection code.

I had the same issue, and this was my solution. Cela semble fonctionner. 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 newItems) { Items.Clear(); foreach (T newItem in newItems) { Items.Add(newItem); } NotifyCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); } public void AddRange(IEnumerable newItems) { int index = Count; foreach (T item in newItems) { Items.Add(item); } NotifyCollectionChangedEventArgs e = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, new List(newItems), index); NotifyCollectionChanged(e); } public void RemoveRange(int startingIndex, int count) { IList oldItems = new List(); for (int i = 0; i < count; i++) { oldItems.Add(Items[startingIndex]); Items.RemoveAt(startingIndex); } NotifyCollectionChangedEventArgs e = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, new List(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 criterion) { List 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(); 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 : ObservableCollection { 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 : System.Collections.ObjectModel.ObservableCollection { 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 realization:

 public class ObservableCollection : Collection, INotifyCollectionChanged, INotifyPropertyChanged { protected override void ClearItems() { CheckReentrancy(); base.ClearItems(); OnPropertyChanged(CountSsortingng); OnPropertyChanged(IndexerName); OnCollectionReset(); } private void OnPropertyChanged(ssortingng propertyName) { OnPropertyChanged(new PropertyChangedEventArgs(propertyName)); } private void OnCollectionReset() { OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); } private const ssortingng CountSsortingng = "Count"; private const ssortingng IndexerName = "Item[]"; } 

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.

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