2011-12-22 18 views
26

Tengo un ObservableCollection de elementos que están vinculados a un control de lista en mi opinión.Agregar un rango de valores a un ObservableCollection eficientemente

Tengo una situación en la que necesito agregar un trozo de valores al inicio de la colección. Collection<T>.Insert documentación especifica cada inserción como una operación O (n), y cada inserción también genera una notificación CollectionChanged.

Por lo tanto, idealmente me gustaría insertar toda la gama de elementos en un solo movimiento, lo que significa solo una mezcla de la lista subyacente, y con suerte una notificación CollectionChanged (presumiblemente un "reinicio").

Collection<T> no expone ningún método para hacer esto. List<T> tiene InsertRange(), pero IList<T>, que Collection<T> expone a través de su propiedad Items no.

¿Hay alguna manera de hacerlo?

+0

Si usted tiene un campo respaldo de propiedad de colección - Se puede asignar una nueva instancia a ella y luego subir 'OnPropertyChanged' para proeprty recogida manualmente – sll

+1

relacionados/posible duplicado: http: // stackoverflow.com/questions/670577/observablecollection-doesnt-support-addrange-method-so-i-get-notified-for-each – Adam

+2

+1 si 'ObservableCollection' te hace pensar en la mecánica cuántica y el experimento de doble rendija. – rfmodulator

Respuesta

50

ObservableCollection expone una propiedad Items protegida que es la colección subyacente sin la semántica de notificación. Esto significa que usted puede construir una colección que hace lo que quiere heredando ObservableCollection:

class RangeEnabledObservableCollection<T> : ObservableCollection<T> 
{ 
    public void InsertRange(IEnumerable<T> items) 
    { 
     this.CheckReentrancy(); 
     foreach(var item in items) 
      this.Items.Add(item); 
     this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); 
    } 
} 

Uso:

void Main() 
{ 
    var collection = new RangeEnabledObservableCollection<int>(); 
    collection.CollectionChanged += (s,e) => Console.WriteLine("Collection changed"); 
    collection.InsertRange(Enumerable.Range(0,100)); 
    Console.WriteLine("Collection contains {0} items.", collection.Count); 
} 
+6

Esta otra [respuesta] (http://stackoverflow.com/questions/13302933/how -to-avoid-despedir-observablecollection-collectionchanged-multiple-times-when-r) a una pregunta similar sugirió agregar el siguiente código para notificar los cambios en las propiedades del conteo y del indexador: 'this.OnPropertyChanged (new PropertyChangedEventArgs (" Count "))); ' ' this.OnPropertyChanged (new PropertyChangedEventArgs ("Item []")); ' – bouvierr

7

Para que la respuesta anterior útil w/o derivar una nueva clase base utilizando la reflexión, aquí está un ejemplo:

public static void InsertRange<T>(this ObservableCollection<T> collection, IEnumerable<T> items) 
{ 
    var enumerable = items as List<T> ?? items.ToList(); 
    if (collection == null || items == null || !enumerable.Any()) 
    { 
    return; 
    } 

    Type type = collection.GetType(); 

    type.InvokeMember("CheckReentrancy", BindingFlags.Instance | BindingFlags.InvokeMethod | BindingFlags.NonPublic, null, collection, null); 
    var itemsProp = type.BaseType.GetProperty("Items", BindingFlags.NonPublic | BindingFlags.FlattenHierarchy | BindingFlags.Instance); 
    var privateItems = itemsProp.GetValue(collection) as IList<T>; 
    foreach (var item in enumerable) 
    { 
    privateItems.Add(item); 
    } 

    type.InvokeMember("OnPropertyChanged", BindingFlags.Instance | BindingFlags.InvokeMethod | BindingFlags.NonPublic, null, 
    collection, new object[] { new PropertyChangedEventArgs("Count") }); 

    type.InvokeMember("OnPropertyChanged", BindingFlags.Instance | BindingFlags.InvokeMethod | BindingFlags.NonPublic, null, 
    collection, new object[] { new PropertyChangedEventArgs("Item[]") }); 

    type.InvokeMember("OnCollectionChanged", BindingFlags.Instance | BindingFlags.InvokeMethod | BindingFlags.NonPublic, null, 
    collection, new object[]{ new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)}); 
} 
+1

¿Qué pasa con inheritenance? ¿Por qué se usa el uso de la reflexión en lugar de derivar una nueva clase? – ventiseis

+3

No hay nada malo con la herencia, por supuesto. Esta es solo una alternativa a eso. Puede ser útil si tiene un montón de código heredado y su serialización (binaria) depende de un tipo ObservableCollection. Opción más sencilla y mucho más pragmática para extender ObservableCollection en ese caso. – outbred

+0

Hola @outbred. lo siento, pero ¿cómo "usas" tu ejemplo?Gracias – NevilleDastur

-2

ejemplo: pasos deseados 0,10,20,30,40,50,60,70,80,90,100 -> min = 0, max = 100, pasos = 11

static int min = 0; 
    static int max = 100; 
    static int steps = 11; 

    private ObservableCollection<string> restartDelayTimeList = new ObservableCollection<string> (
     Enumerable.Range(0, steps).Select(l1 => (min + (max - min) * ((double)l1/(steps - 1))).ToString()) 
    ); 
3

Este answer no me mostró las nuevas entradas en un DataGrid. Este OnCollectionChanged funciona para mí:

public class SilentObservableCollection<T> : ObservableCollection<T> 
{ 
    public void AddRange(IEnumerable<T> enumerable) 
    { 
     CheckReentrancy(); 

     int startIndex = Count; 

     foreach (var item in enumerable) 
      Items.Add(item); 

     OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, new List<T>(enumerable), startIndex)); 
     OnPropertyChanged(new PropertyChangedEventArgs("Count")); 
     OnPropertyChanged(new PropertyChangedEventArgs("Item[]")); 
    } 
} 
+0

Estas parecen ser las notificaciones de cambio más apropiadas. – OttPrime

Cuestiones relacionadas