2010-07-21 62 views
10

请注意,我正在尝试使用NotifyCollectionChangedAction.Add操作而不是.Reset。后者确实有效,但对大型馆藏来说效率不高。ObservableCollection:调用OnCollectionChanged与多个新项目

,所以我子类的ObservableCollection:

public class SuspendableObservableCollection<T> : ObservableCollection<T> 

出于某种原因,这个代码:

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)); 
     } 
    } 

抛出 集合添加的事件是指不属于集合项目

这似乎是一个BU g BCL?调用OnCollectionChanged新的项目加入到this.Items

WOW

我可以逐步看到之前

刚刚作出了一个惊人的发现。这些方法都不适用于我(刷新,添加范围),因为只有当这个集合被绑定到我的列表视图时,错误才会被触发!

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!!! 

总之,ObservableCollection不支持添加增量列表,但ListView不支持。 Andyp想出了一个解决方法,使其与下面的CollectionView一起工作,但由于调用了.Refresh(),这与调用OnCollectionChanged(.Reset)没有什么不同。

+0

为什么RemoveRange,AddRange触发重置?也许有人不明白删除,添加和重置意义之间的区别? – 2017-02-01 11:45:09

回答

6

你可以像这样的ObservableCollection实现的AddRange()如图所示here

public class RangeObservableCollection<T> : ObservableCollection<T> 
{ 
    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<T> 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)); 
    } 
} 

UPDATE:绑定列表框后,我得s也出现InvalidOperationException异常(您看到相同的消息)。根据这article这是因为CollectionView不支持范围操作。幸运的是,这篇文章也提供了一个解决方案(尽管它感觉有点“黑客”)。

更新2:添加了一个修复程序,该程序在OnCollectionChanged()的重写实现中引发了重写的CollectionChanged事件。

+0

谢谢,但我想改变远离。复位行动。这里的整点是,我想添加只有新的项目。如果我的收藏达到大尺寸,.reset是非常缓慢的,因为我正在过滤它 – 2010-07-21 19:57:44

+0

啊,我错过了 - 更新我的代码使用NotifyCollectionChangedAction.Add,而不是重置 – andyp 2010-07-21 20:10:06

+0

添加了一个链接和代码,解决(避免)CollectionView的问题与范围操作 – andyp 2010-07-21 20:38:31

-1

我相信你需要将它转换为IList

base.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, (IList)_cachedItems));

+0

谢谢,现在我回到“一个集合添加事件是指不属于集合的项目” – 2010-07-21 15:38:33

+0

我调整了我的代码,谢谢 – 2010-07-21 15:43:58

+0

嗯,怎么样,而不是'Items.Add(item)','base.Add (项目)'? – 2010-07-21 17:15:23

1

感谢AndyP的灵感。我在实现中遇到了一些问题,例如在测试中使用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 
} 
+0

如果BaseObservableCollection在便携式项目中定义,您将如何完成此操作?我相信ICollectionView特定于Windows,因此无法使用。 – user2481095 2017-07-11 22:18:51

0

多次迭代后,我们结束了这个版本的ObservableRangeCollectionReadOnlyObservableRangeCollection这是基于公认的答案的代码,这是我们没有在过去6个月来修改:

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); 
      } 
     } 
    } 
} 

我们基本上将ObservableCollection在我们的应用程序中的所有使用替换为ObservableRangeCollection,它就像一个魅力。