C# ObservableCollection:调用OnCollectionChanged时包含多个新项
请注意,我正在尝试使用NotifyCollectionChangedAction.Add操作而不是.Reset。后者确实有效,但对于大型集合来说效率不是很高 所以我将ObservableCollection子类化:C# ObservableCollection:调用OnCollectionChanged时包含多个新项,c#,generics,c#-4.0,subclass,observablecollection,C#,Generics,C# 4.0,Subclass,Observablecollection,请注意,我正在尝试使用NotifyCollectionChangedAction.Add操作而不是.Reset。后者确实有效,但对于大型集合来说效率不是很高 所以我将ObservableCollection子类化: public class SuspendableObservableCollection<T> : ObservableCollection<T> 公共类SuspendableObservableCollection:ObservableCollection
public class SuspendableObservableCollection<T> : ObservableCollection<T>
公共类SuspendableObservableCollection:ObservableCollection
出于某种原因,此代码:
private List<T> _cachedItems;
...
public void FlushCache() {
if (_cachedItems.Count > 0) {
foreach (var item in _cachedItems)
Items.Add(item);
OnCollectionChanged(new NotifyCollectionChangedEventArgs(
NotifyCollectionChangedAction.Add, (IList<T>)_cachedItems));
}
}
private List\u cachedItems;
...
公共void FlushCache(){
如果(_cachedItems.Count>0){
foreach(缓存数据项中的变量项)
项目。添加(项目);
OnCollectionChanged(新建NotifyCollectionChangedEventArgs(
NotifyCollectionChangedAction.Add,(IList)u cachedItems);
}
}
正在投掷
集合添加事件指的是不属于集合的项目
这似乎是BCL中的一个错误
在调用OnCollectionChanged之前,我可以单步执行并查看是否已将新项添加到此。项
哇
刚刚有了一个惊人的发现。这些方法都不适用于我(flush、addrange),因为只有当此集合绑定到我的Listview时才会触发错误
TestObservableCollection<Trade> testCollection = new TestObservableCollection<Trade>();
List<Trade> testTrades = new List<Trade>();
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!!!
TestObservableCollection testCollection=新的TestObservableCollection();
List testTrades=新列表();
对于(int i=0;i<200000;i++)
添加(t);
testCollection.AddRange(testTrades);//这里没问题。。
_trades.AddRange(testTrades);//这个绑定到ListView。。嘘!!!
总之,ObservableCollection确实支持添加增量列表,但ListView不支持。Andyp想出了一个解决方法,使其能够在下面的CollectionView中工作,但由于调用了.Refresh(),这与只调用OnCollectionChanged(.Reset)没有什么不同 我认为您需要将其转换为
IList
:
base.OnCollectionChanged(新的NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add,(IList)\u cachedItems))代码>您可以为ObservableCollection实现AddRange(),如下所示:
公共类RangeObservableCollection:ObservableCollection
{
私人bool\u通知;
公共覆盖事件NotifyCollectionChangedEventHandler CollectionChanged;
CollectionChangedMultiItem上受保护的虚拟无效(
NotifyCollectionChangedEventArgs(e)
{
NotifyCollectionChangedEventHandler处理程序=this.CollectionChanged;
if(处理程序!=null)
{
中的foreach(NotifyCollectionChangedEventHandler处理程序
handlers.GetInvocationList())
{
if(handler.Target是CollectionView)
((CollectionView)handler.Target).Refresh();
其他的
处理者(本,e);
}
}
}
CollectionChanged上的受保护覆盖无效(NotifyCollectionChangedEventArgs e)
{
如果(!\u禁止通知)
{
基础。变更的集合(e);
如果(CollectionChanged!=null)
CollectionChanged.Invoke(this,e);
}
}
public void AddRange(IEnumerable列表)
{
if(list==null)
抛出新的ArgumentNullException(“列表”);
_SuppressNotification=true;
foreach(列表中的T项)
{
增加(项目);
}
_SuppressNotification=false;
OnCollectionChangedMultiItem(新的NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add,list));
}
}
更新:绑定到ListBox后,我也看到了InvalidOperationException(与您看到的消息相同)。根据这一点,这是因为CollectionView不支持范围操作。幸运的是,本文还提供了一个解决方案(尽管感觉有点“黑客化”)
更新2:添加了一个修复程序,在OnCollectionChanged()的重写实现中引发重写的CollectionChanged事件。感谢您的启发和YP。我对您的实现有一些问题,例如在测试中使用CollectionView而不是ICollectionView,以及手动调用元素上的“Reset”。从CollectionView继承的元素实际上可能会以比调用“this.Reset()”更多的方式处理这些参数,因此最好还是激发它们的处理程序,只使用它们所需的Action=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
}
公共类BaseObservableCollection:ObservableCollection
{
//用于防止OnCollectionChanged在诸如Add(IEnumerable)和Clear()之类的批量操作期间触发的标志
private bool\u SuppressCollectionChanged=false;
///重写,以便我们可以手动调用已注册的处理程序,并区分需要和不需要操作的处理程序。重置args。
公共覆盖事件NotifyCollectionChangedEventHandler CollectionChanged;
public BaseObservableCollection():base(){}
公共BaseObservableCollection(IEnumerable数据):基(数据){}
#区域事件处理程序
CollectionChanged上的受保护覆盖无效(NotifyCollectionChangedEventArgs e)
{
如果(!\u SuppressCollectionChanged)
{
基础。变更的集合(e);
如果(CollectionChanged!=null)
CollectionChanged.Invoke(this,e);
}
}
//CollectionViews在向其传递NotifyCollectionChangedEventArgs时引发错误,该NotifyCollectionChangedEventArgs指示超过
//已添加或删除一个元素。他们希望收到“Action=Reset”通知,但这不合适
//对于代码中的应用程序,我们实际上会检查通知的类型,并传递自定义事件args。
CollectionChangedMultiItem上受保护的虚拟无效(NotifyCollectionChangedEventArgs e)
{
NotifyCollectionChangedEventHandler处理程序=this.CollectionChanged;
如果(ha)
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
}
public class ObservableRangeCollection<T> : ObservableCollection<T>
{
private bool suppressNotification;
public ObservableRangeCollection() { }
public ObservableRangeCollection(IEnumerable<T> 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<T>
&& !(handler.Target is ReadOnlyObservableRangeCollection<T>))
{
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<T> 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<T>)items);
}
public void ReplaceWithRange(IEnumerable<T> items)
{
Items.Clear();
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
AddRange(items);
}
public void RemoveRange(IEnumerable<T> 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<T> : ReadOnlyObservableCollection<T>
{
public ReadOnlyObservableRangeCollection(ObservableCollection<T> 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);
}
}
}
}