清除ObservableCollection时,e.OldItems中没有项目
我在这里有一些东西让我非常吃惊。
我有一个充满了项目的T的ObservableCollection。 我也有一个事件处理程序附加到CollectionChanged事件。
清除集合时,会导致eChanged事件将e.Action设置为NotifyCollectionChangedAction.Reset。 好的,这很正常。 但是奇怪的是,e.OldItems和e.NewItems都没有任何东西。 我期望e.OldItems被填充从集合中删除的所有项目。
有没有人看过这个? 如果是这样,他们怎么绕过它?
一些背景:我正在使用CollectionChanged事件附加和从另一个事件分离,因此,如果我没有得到任何项目在e.OldItems …我将无法脱离该事件。
澄清:我知道文件没有直接说明它必须这样做。 但是对于其他的行动,它是通知我做了什么。 所以,我的假设是,它会告诉我…在清除/重置的情况下。
如果你想自己重现它,下面是示例代码。 首先closuresxaml:
<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 }; } }
它不声称包含旧的项目,因为重置并不意味着列表已被清除
这意味着发生了一些戏剧性的事情,而且制定添加/删除的成本很可能会超过从头重新扫描列表的成本…所以这就是你应该做的。
MSDNbuild议将整个集合重新sorting为一个候选重置的示例。
重申一下。 重置并不意味着明确 ,这意味着您对列表的假设现在是无效的。 把它看作是一个全新的列表 。 清除恰好是这样的一个例子,但也可能有其他的例子。
一些例子:
我有一个这样的列表,其中有很多项目,并且已经将数据绑定到WPF ListView
以显示在屏幕上。
如果清除列表并引发.Reset
事件,那么性能几乎是即时的,但是如果反而引发了许多单独的.Remove
事件,则性能非常糟糕,因为WPF会逐个删除项目。 我也使用.Reset
在我自己的代码,以表明列表已被重新sorting,而不是发出成千上万的个人Move
操作。 和Clear一样,在举办很多单项比赛的时候也会有很大的performance。
我们在这里有同样的问题。 CollectionChanged中的Reset操作不包含OldItems。 我们有一个解决方法:我们使用了下面的扩展方法:
public static void RemoveAll(this IList list) { while (list.Count > 0) { list.RemoveAt(list.Count - 1); } }
我们最终不支持Clear()函数,并为Reset操作在CollectionChanged事件中引发NotSupportedException。 RemoveAll将使用正确的OldItems触发CollectionChanged事件中的Remove操作。
另一个选项是用一个单独的Remove事件replaceReset事件,该事件包含其OldItems属性中的所有已清除项目,如下所示:
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 ... }
优点:
-
无需订阅额外的事件(根据接受的答案)
-
不会为每个删除的对象生成一个事件(其他一些build议的解决scheme会导致多个删除的事件)。
-
订阅者只需要在任何事件上检查NewItems&OldItems,根据需要添加/删除事件处理程序。
缺点:
-
无重置事件
-
小(?)开销创build列表副本。
-
???
编辑2012-02-23
不幸的是,绑定到基于WPF列表的控件时,使用多个元素清除ObservableCollectionNoReset集合将导致exception“不支持范围操作”。 为了与这个限制的控件一起使用,我将ObservableCollectionNoReset类更改为:
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. }
当RangeActionsSupported为false(缺省值)时,效率不高,因为每个对象在集合中都会生成一个Remove通知
我发现了一个解决scheme,允许用户同时使用添加或删除多个项目的效率,同时只触发一个事件 – 并满足UIElements的需求,以获取Action.Reset事件参数,而所有其他用户就像添加和删除元素的列表一样。
该解决scheme涉及重写CollectionChanged事件。 当我们去发射这个事件时,我们实际上可以查看每个注册处理器的目标并确定它们的types。 由于只有ICollectionView类需要NotifyCollectionChangedAction.Reset
参数,当多个项目发生更改时,我们可以将它们单独出来,并向其他人提供适当的事件参数,其中包含已删除或添加的项目的完整列表。 以下是实施。
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 }
好吧,尽pipe我仍然希望ObservableCollection的行为如我所愿…下面的代码是我最终做的。 基本上,我创build了一个名为TrulyObservableCollection的T的新集合,并重写了ClearItems方法,然后我使用该方法提高Clearing事件。
在使用这个TrulyObservableCollection的代码中,我使用这个Clearing事件来循环处理当前仍在集合中的项目,以便对我希望从中分离的事件进行分离。
希望这种方法也可以帮助别人。
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(); } }
好吧,我知道这是一个非常古老的问题,但我已经想出了一个很好的解决scheme,并认为我会分享。 这个解决scheme从许多伟大的答案中获得灵感,但具有以下优点:
- 不需要从ObservableCollection创build一个新类并重写方法
- 不要篡改NotifyCollectionChanged的工作(所以不要搞乱Reset)
- 不利用反思
这里是代码:
public static void Clear<T>(this ObservableCollection<T> collection, Action<ObservableCollection<T>> unhookAction) { unhookAction.Invoke(collection); collection.Clear(); }
这个扩展方法只需要一个Action
,在收集被清除之前被调用。
这是ObservableCollection的工作原理,你可以通过在ObservableCollection之外保留你自己的列表来解决这个问题(当添加动作时添加到列表中,当删除动作时删除等),那么你可以得到所有删除的项目(或添加项目)通过比较列表和ObservableCollection来重置动作时。
另一个select是创build自己的类实现IList和INotifyCollectionChanged,然后你可以附加和分离该类内的事件(或设置OldItems清除,如果你喜欢) – 这并不困难,但它是很多input。
我以一种稍微不同的方式处理了这个问题,因为我想注册一个事件并处理事件处理程序中的所有添加和删除。 我开始重写收集已更改的事件,并将重置操作redirect到具有项目列表的删除操作。 这一切都出错了,因为我使用可观察集合作为集合视图的项目源,并得到“范围操作不支持”。
我最终创build了一个名为CollectionChangedRange的新事件,它以我期望的内置版本的方式进行操作。
我无法想象为什么这个限制会被允许,希望这个职位至less能阻止别人走下我所做的死路。
/// <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); } } }
对于将事件处理程序附加到ObservableCollection的元素的scheme,还有一个“客户端”解决scheme。 在事件处理代码中,您可以使用Contains方法检查发件人是否在ObservableCollection中。 临:你可以使用任何现有的ObservableCollection。 缺点:Contains方法以O(n)运行,其中n是ObservableCollection中元素的数量。 所以这是一个小ObservableCollections的解决scheme。
另一个“客户端”解决scheme是在中间使用事件处理程序。 只需将所有事件注册到中间的事件处理程序。 这个事件处理程序依次通过callback或事件通知真实的事件处理程序。 如果发生重置操作,请删除callback或事件,在中间创build一个新的事件处理程序,并忘记旧事件处理程序。 这种方法也适用于大ObservableCollections。 我用这个PropertyChanged事件(见下面的代码)。
/// <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; } } }
查看NotifyCollectionChangedEventArgs ,看起来OldItems只包含由于Replace,Remove或Move操作而更改的项目。 这并不表示它将包含任何清除。 我怀疑Clear引发了事件,但是没有注册已删除的项目,也根本没有调用Remove代码。
好吧,我决定自己弄脏它。
微软把大量的工作放在总是确保NotifyCollectionChangedEventArgs调用重置时没有任何数据。 我假设这是一个性能/记忆的决定。 如果您要重新设置一个包含100,000个元素的集合,那么我假设他们不想复制所有这些元素。
但看到我的collections从来没有超过100个元素,我没有看到它的问题。
无论如何,我用下面的方法创build了一个inheritance类:
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); }
ObservableCollection以及INotifyCollectionChanged接口清楚地写入了一个特定用途:UI构build及其特定的性能特征。
当你想收集变化的通知,那么你通常只对添加和删除事件感兴趣。
我使用以下界面:
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; } }
我也写了自己的重载集合,其中:
- ClearItems引发删除
- InsertItem引发添加
- RemoveItem引发删除
- SetItem引发删除和添加
当然,也可以添加AddRange。
我只是通过Silverlight和WPF工具包中的一些图表代码,并注意到他们也解决了这个问题(以一种类似的方式)…我想我会继续并发布他们的解决scheme。
基本上,他们也创build了一个派生的ObservableCollection,并覆盖了ClearItems,在每个被清除的项上调用Remove。
这里是代码:
/// <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); } } }
这是一个热门话题,因为在我看来,微软并没有正确地做好自己的工作。 不要误解我,我喜欢微软,但他们并不完美!
我读了大部分以前的评论。 我同意那些认为微软没有正确编程Clear()的人。
在我看来,至less它需要一个论据来把事件从事件中分离出来……但是我也了解它的影响。 然后,我想到了这个build议的解决scheme。
我希望这会让每个人都高兴,至less大多数人…
埃里克
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 }); } } } } }
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.
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[]"; }
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