2010-07-21 15 views
10

tenga en cuenta que estoy intentando utilizar la acción NotifyCollectionChangedAction.Add en lugar de .Reset. este último funciona, pero no es muy eficiente con grandes colecciones.ObservableCollection: llamando a OnCollectionChanged con múltiples elementos nuevos

así que una subclase ObservableCollection:

public class SuspendableObservableCollection<T> : ObservableCollection<T> 

por alguna razón, este código:

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

es tirar Una colección Añadir evento se refiere al elemento que no pertenece a la colección

esto parece ser un bu g en BCL?

puedo paso a través y ver antes de llamar OnCollectionChanged que se agregan nuevos elementos a this.Items

WOW

acaba de hacer un descubrimiento sorprendente. Ninguno de estos enfoques funcionó para mí (flush, addrange), porque el error parece activarse SOLAMENTE si esta colección está ligada a mi 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!!! 

En conclusión, ObservableCollection ¿Apoya la adición de listas incrementales, pero un ListView que no. Andyp descubrió una solución para hacer que funcione con CollectionView a continuación, pero desde que se llama a .Refresh(), eso no es diferente a llamar a OnCollectionChanged (.Reset) ..

+0

¿Por qué RemoveRange, AddRange fire Reset? ¿Tal vez alguien no podría entender la diferencia entre quitar, agregar y restablecer significados? –

Respuesta

6

puede implementar AddRange() para el ObservableCollection como éste, como se muestra 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)); 
    } 
} 

ACTUALIZACIÓN: Después de unirse a ListBox que era s También hay una InvalidOperationException (el mismo mensaje que estaba viendo). De acuerdo con esto article es porque CollectionView no admite acciones de rango. Afortunadamente, el artículo también proporciona una solución (aunque se siente un poco "hack-ish").

ACTUALIZACIÓN 2: Se agregó una corrección que provoca el evento CollectionChanged anulado en la implementación anulada de OnCollectionChanged().

+0

gracias, pero estoy tratando de alejarme de la acción .Reset. El punto aquí es que quiero agregar solo elementos nuevos. Si mi colección alcanza un tamaño grande, .reset es muy lento ya que también lo estoy filtrando –

+0

Ah, me perdí eso: actualicé mi código para usar NotifyCollectionChangedAction.Add en lugar de Restablecer. – andyp

+0

Agregué un enlace y un código que resuelve (evita) el problema de CollectionView con las operaciones de rango. – andyp

-1

Creo que necesita convertirlo en IList :

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

+0

gracias, ahora estoy de vuelta a "Una colección Agregar evento se refiere a un artículo que no pertenece a la colección" –

+0

He ajustado mi código, gracias –

+0

hmmm, ¿qué tal en lugar de 'Items.Add (elemento)', 'base.Add (artículo) '? –

1

Gracias por la inspiración AndyP. Tuve algunos problemas con su implementación, como el uso de CollectionView en lugar de ICollectionView en la prueba, y también llamé manualmente "Restablecer" en los elementos. Los elementos que heredan de CollectionView podrían en realidad tratar estos argumentos de más formas que llamar a "this.Reset()", por lo que es preferible disparar sus manejadores, solo con Action = Reset args que requieren en lugar del argumento de evento mejorado que incluir la lista de elementos modificados. A continuación está mi implementación (muy similar).

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

¿Cómo lo lograrías si BaseObservableCollection se define en un proyecto portátil? Creo que ICollectionView es específico de Windows y, por lo tanto, no está disponible para su uso. – user2481095

0

Después de muchas iteraciones, terminamos con esta versión de ObservableRangeCollection y ReadOnlyObservableRangeCollection que se basa en el código de la respuesta aceptada, y que no se tenga que modificar en los últimos 6 meses:

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

Básicamente reemplazamos todos los usos de ObservableCollection en nuestra aplicación por ObservableRangeCollection, y funciona como un encanto.

Cuestiones relacionadas