2008-09-11 17 views
31

Estaba considerando la clase System.Collections.ObjectModel ObservableCollection<T>. Éste es extraño porque¿Qué colección .Net para agregar múltiples objetos a la vez y recibir notificaciones?

  • tiene un método Add que tiene sólo uno artículo. Sin AddRange o equivalente.
  • los argumentos del evento de notificación tiene una propiedad newitems, que es un IList (de objetos .. No T)

Mi necesidad aquí es añadir un lote de objetos a una colección y el oyente también recibe el lote como parte de la notificación. ¿Me estoy perdiendo algo con ObservableCollection? ¿Hay otra clase que cumpla con mi especificación?

Actualización: No quiero hacer lo mío en la medida de lo posible. Tendría que construir en agregar/eliminar/cambiar, etc. un montón de cosas.


Q relacionadas:
https://stackoverflow.com/questions/670577/observablecollection-doesnt-support-addrange-method-so-i-get-notified-for-each

+4

Gishu, cuidado, si se unen a un listview la mayor parte de las implementaciones aquí va a explotar. –

Respuesta

1

Heredar del Listado < T> y anular la opción Agregar() y AddRange) métodos (para provocar un evento?

2

Si desea heredar de una colección de algún tipo, probablemente sea mejor heredar de System.Collections.ObjectModel.Collection porque proporciona métodos virtuales para anular. Tendrás que sombrear los métodos fuera de List si vas por esa ruta.

No estoy al tanto de cualquier colecciones integradas que proporcionan esta funcionalidad, aunque daría la bienvenida a ser corregido :)

18

Parece que la interfaz INotifyCollectionChanged permite la actualización cuando se añadieron varios elementos, por lo que No estoy seguro de por qué ObservableCollection<T> no tiene un AddRange. Puede hacer un método de extensión para AddRange, pero eso causaría un evento por cada elemento que se agrega. Si eso no es aceptable que debe ser capaz de heredar de ObservableCollection<T> de la siguiente manera:

public class MyObservableCollection<T> : ObservableCollection<T> 
{ 
    // matching constructors ... 

    bool isInAddRange = false; 

    protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e) 
    { 
     // intercept this when it gets called inside the AddRange method. 
     if (!isInAddRange) 
      base.OnCollectionChanged(e); 
    } 


    public void AddRange(IEnumerable<T> items) 
    { 
     isInAddRange = true; 
     foreach (T item in items) 
      Add(item); 
     isInAddRange = false; 

     var e = new NotifyCollectionChangedEventArgs(
      NotifyCollectionChangedAction.Add, 
      items.ToList()); 
     base.OnCollectionChanged(e); 
    } 
} 
+0

El fragmento de código necesita algunas correcciones ... no hay ToList() en IEnumerable y AddRange debería tomar una ICollection para ser coherente ... Como tuve que pasar por el revés temporal para mis grandes planes de cumplir mi objetivo semanal , publicando mi código de muestra ... un poco más corto. – Gishu

+2

Gishu, el método ToList() es un método de extensión LINQ disponible en IEnumerable. –

+0

Entendido ... Debe establecer la configuración del proyecto para usar .NET 3.5 y agregar la referencia de ensamblado LINQ y la directiva using para obtenerlo. – Gishu

-1

Método de extensión hombre al rescate!

/// <summary> 
    /// Adds all given items to the collection 
    /// </summary> 
    /// <param name="collection">The collection.</param> 
    /// <param name="toAdd">Objects to add.</param> 
    public static void AddAll<T>(this IList<T> collection, params T[] toAdd) 
    { 
     foreach (var o in toAdd) 
      collection.Add(o); 
    } 
+0

Esto es dolorosamente lento ... por ejemplo, intenta agregar 3000 elementos a un observable colección (dile adiós a la interfaz de usuario durante 3 minutos) –

+1

Si estás obligado a la interfaz de usuario, sí, muy probablemente. http://blogs.msdn.com/nathannesbit/archive/2009/04/20/addrange-and-observablecollection.aspx – Will

+0

Buena adición a las otras soluciones, pero probablemente no sea la mejor en cuanto a rendimiento. Siempre que crear tus propias colecciones sea una opción, creo que las otras soluciones son probablemente mejores a largo plazo. – jpierson

4

No sólo es System.Collections.ObjectModel.Collection<T> una buena apuesta, pero en los documentos de ayuda no an example de cómo reemplazar sus diversos métodos protegidos con el fin de conseguir la notificación. (Desplácese hacia abajo al Ejemplo 2)

+0

gracias - enlace útil ... hizo una nota mental. – Gishu

6

Bueno, la idea es la misma que la de fryguybob - algo raro que ObservableCollection está medio hecho. Los argumentos de eventos para esto ni siquiera se utilizan los genéricos .. me hace uso un IList (eso es por lo que ayer .. :) Probado fragmentos sigue ...

using System.Collections.Generic; 
using System.Collections.ObjectModel; 
using System.Collections.Specialized; 

namespace MyNamespace 
{ 
    public class ObservableCollectionWithBatchUpdates<T> : ObservableCollection<T> 
    { 
     public void AddRange(ICollection<T> obNewItems) 
     { 
      IList<T> obAddedItems = new List<T>(); 
      foreach (T obItem in obNewItems) 
      { 
       Items.Add(obItem); 
       obAddedItems.Add(obItem); 
      } 
      NotifyCollectionChangedEventArgs obEvtArgs = new NotifyCollectionChangedEventArgs(
       NotifyCollectionChangedAction.Add, 
       obAddedItems as System.Collections.IList); 
      base.OnCollectionChanged(obEvtArgs); 
     } 

    } 
} 
+4

Probé este enfoque antes. Lamentablemente, esto no funcionará para los enlaces WPF, porque las notificaciones de varios elementos no son compatibles. Consulte [este error] (https://connect.microsoft.com/WPF/feedback/details/514922/range-actions-not-supported-in-collectionview) en MS Connect –

4

Si utiliza cualquiera de las implementaciones anteriores que envían un comando agregar rango y enlazar la colección observable a una vista de lista obtendrá este desagradable error.

 
NotSupportedException 
    at System.Windows.Data.ListCollectionView.ValidateCollectionChangedEventArgs(NotifyCollectionChangedEventArgs e) 
    at System.Windows.Data.ListCollectionView.ProcessCollectionChanged(NotifyCollectionChangedEventArgs args) 
    at System.Collections.Specialized.NotifyCollectionChangedEventHandler.Invoke(Object sender, NotifyCollectionChangedEventArgs e) 
    at System.Collections.ObjectModel.ObservableCollection`1.OnCollectionChanged(NotifyCollectionChangedEventArgs e) 

La implementación he ido con utiliza el evento de reinicio que se implementa de manera más uniforme en todo el marco WPF:

public void AddRange(IEnumerable<T> collection) 
    { 
     foreach (var i in collection) Items.Add(i); 
     OnPropertyChanged("Count"); 
     OnPropertyChanged("Item[]"); 
     OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); 
    } 
+0

puede ir con un complemento para cada elemento, pero su UI se detendrá lentamente –

+0

En realidad, no, no lo hará - WPF UI se presenta en lotes, como un mensaje de correo. Esta función terminará antes de que se verifique la actualización de la UI. – cunningdave

+0

¿Alguien intentó esto con xceeds WPF datagrid? – springy76

0

Para añadir rápidamente usted podría usar:

((List<Person>)this.Items).AddRange(NewItems); 
3

he visto este tipo de pregunta muchas veces, y me pregunto por qué incluso Microsoft está promoviendo ObservableCollection todas partes dónde si no hay una mejor colección ya que eso es disponibles ..

BindingList<T>

que le permite desactivar las notificaciones y hacer mayor la ópera ciones y luego activar las notificaciones.

+1

Muy buen punto acerca de 'BindingList ' pero desafortunadamente implementa 'IBindingList ' en lugar de 'IObservable ' ... Lo último es necesario para las aplicaciones WPF que usan mvvm. – RonnBlack

1

Otra solución que es similar al patrón CollectionView:

public class DeferableObservableCollection<T> : ObservableCollection<T> 
{ 
    private int deferLevel; 

    private class DeferHelper<T> : IDisposable 
    { 
     private DeferableObservableCollection<T> owningCollection; 
     public DeferHelper(DeferableObservableCollection<T> owningCollection) 
     { 
      this.owningCollection = owningCollection; 
     } 

     public void Dispose() 
     { 
      owningCollection.EndDefer(); 
     } 
    } 

    private void EndDefer() 
    { 
     if (--deferLevel <= 0) 
     { 
      deferLevel = 0; 
      OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); 
     } 
    } 

    public IDisposable DeferNotifications() 
    { 
     deferLevel++; 
     return new DeferHelper<T>(this); 
    } 

    protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e) 
    { 
     if (deferLevel == 0) // Not in a defer just send events as normally 
     { 
      base.OnCollectionChanged(e); 
     } // Else notify on EndDefer 
    } 
} 
Cuestiones relacionadas