ObservableCollection:使用多个新项调用OnCollectionChanged
请注意,我正在尝试使用NotifyCollectionChangedAction.Add操作而不是.Reset。 后者确实有效,但对于大型集合来说效率不高。
所以我inheritance了ObservableCollection:
public class SuspendableObservableCollection : ObservableCollection
出于某种原因,这段代码:
private List _cachedItems; ... public void FlushCache() { if (_cachedItems.Count > 0) { foreach (var item in _cachedItems) Items.Add(item); OnCollectionChanged(new NotifyCollectionChangedEventArgs( NotifyCollectionChangedAction.Add, (IList)_cachedItems)); } }
抛出一个集合添加事件是指不属于集合的项目
这似乎是BCL的一个错误?
我可以在调用OnCollectionChanged之前逐步查看将新项添加到this.Items
哇
刚刚发现了一个惊人的发现。 这些方法都不适合我(flush,addrange),因为只有当这个集合绑定到我的Listview时才会触发错误!
TestObservableCollection testCollection = new TestObservableCollection(); List testTrades = new List(); for (int i = 0; i < 200000; i++) testTrades.Add(t); testCollection.AddRange(testTrades); // no problems here.. _trades.AddRange(testTrades); // this one is bound to ListView .. BOOOM!!!
总之,ObservableCollection确实支持添加增量列表,但ListView不支持。 Andyp想出了一个解决方法,使其适用于下面的CollectionView,但是由于调用了.Refresh(),这与调用OnCollectionChanged(.Reset)没什么不同。
您可以为ObservableCollection实现AddRange(),如下所示:
public class RangeObservableCollection : ObservableCollection { private bool _SuppressNotification; public override event NotifyCollectionChangedEventHandler CollectionChanged; protected virtual void OnCollectionChangedMultiItem( NotifyCollectionChangedEventArgs e) { NotifyCollectionChangedEventHandler handlers = this.CollectionChanged; if (handlers != null) { foreach (NotifyCollectionChangedEventHandler handler in handlers.GetInvocationList()) { if (handler.Target is CollectionView) ((CollectionView)handler.Target).Refresh(); else handler(this, e); } } } protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e) { if (!_SuppressNotification) { base.OnCollectionChanged(e); if (CollectionChanged != null) CollectionChanged.Invoke(this, e); } } public void AddRange(IEnumerable list) { if (list == null) throw new ArgumentNullException("list"); _SuppressNotification = true; foreach (T item in list) { Add(item); } _SuppressNotification = false; OnCollectionChangedMultiItem(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, list)); } }
更新:绑定到ListBox后,我也看到了InvalidOperationException(您看到的消息相同)。 根据这篇文章 ,因为CollectionView不支持范围操作。 幸运的是,这篇文章也提供了一个解决方案(虽然它感觉有点“hack-ish”)。
更新2:添加了一个修复程序,在重写的OnCollectionChanged()实现中引发重写的CollectionChanged事件。
感谢AndyP的灵感。 我的实现有一些问题,例如在测试中使用CollectionView而不是ICollectionView,以及在元素上手动调用“Reset”。 从CollectionViewinheritance的元素可能实际上以比调用“this.Reset()”更多的方式处理这些args,所以最好仍然触发它们的处理程序,只需要使用它们需要的Action = Reset args而不是改进的事件args包括已更改的项目列表。 下面是我(非常相似)的实现。
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 }
在多次迭代之后,我们最终得到了这个版本的ObservableRangeCollection
和ReadOnlyObservableRangeCollection
,它基于接受答案的代码,我们在过去的6个月内没有必要修改:
public class ObservableRangeCollection : ObservableCollection { private bool suppressNotification; public ObservableRangeCollection() { } public ObservableRangeCollection(IEnumerable items) : base(items) { } public override event NotifyCollectionChangedEventHandler CollectionChanged; protected virtual void OnCollectionChangedMultiItem( NotifyCollectionChangedEventArgs e) { var handlers = CollectionChanged; if (handlers == null) return; foreach (NotifyCollectionChangedEventHandler handler in handlers.GetInvocationList()) { if (handler.Target is ReadOnlyObservableCollection && !(handler.Target is ReadOnlyObservableRangeCollection )) { throw new NotSupportedException( "ObservableRangeCollection is wrapped in ReadOnlyObservableCollection which might be bound to ItemsControl " + "which is internally using ListCollectionView which does not support range actions.\n" + "Instead of ReadOnlyObservableCollection, use ReadOnlyObservableRangeCollection"); } var collectionView = handler.Target as ICollectionView; if (collectionView != null) { collectionView.Refresh(); } else { handler(this, e); } } } protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e) { if (suppressNotification) return; base.OnCollectionChanged(e); if (CollectionChanged != null) { CollectionChanged.Invoke(this, e); } } public void AddRange(IEnumerable items) { if (items == null) return; suppressNotification = true; var itemList = items.ToList(); foreach (var item in itemList) { Add(item); } suppressNotification = false; if (itemList.Any()) { OnCollectionChangedMultiItem(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, itemList)); } } public void AddRange(params T[] items) { AddRange((IEnumerable )items); } public void ReplaceWithRange(IEnumerable items) { Items.Clear(); OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); AddRange(items); } public void RemoveRange(IEnumerable items) { suppressNotification = true; var removableItems = items.Where(x => Items.Contains(x)).ToList(); foreach (var item in removableItems) { Remove(item); } suppressNotification = false; if (removableItems.Any()) { OnCollectionChangedMultiItem(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, removableItems)); } } } public class ReadOnlyObservableRangeCollection : ReadOnlyObservableCollection { public ReadOnlyObservableRangeCollection(ObservableCollection list) : base(list) { } protected override event NotifyCollectionChangedEventHandler CollectionChanged; protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e) { var handlers = CollectionChanged; if (handlers == null) return; foreach (NotifyCollectionChangedEventHandler handler in handlers.GetInvocationList()) { var collectionView = handler.Target as ICollectionView; if (collectionView != null) { collectionView.Refresh(); } else { handler(this, e); } } } }
我们基本上用ObservableCollection
替换了我们应用程序中ObservableCollection
所有用法,它就像一个魅力。
我相信你需要把它投射到IList
:
base.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, (IList)_cachedItems));