2010-07-21 55 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,它就像一個魅力。

相關問題