2011-12-22 11 views
26

Ho un ObservableCollection di elementi associati a un controllo di elenco nella vista personale.Aggiunta di un intervallo di valori a ObservableCollection in modo efficiente

Ho una situazione in cui è necessario aggiungere un blocco di valori all'inizio della raccolta. Collection<T>.Insert documentazione specifica ogni inserto come operazione O (n) e ogni inserto genera anche una notifica CollectionChanged.

Pertanto, preferirei inserire l'intera gamma di elementi in una sola mossa, ovvero solo uno shuffle dell'elenco sottostante e, si spera, una notifica CollectionChanged (presumibilmente un "reset").

Collection<T> non espone alcun metodo per farlo. List<T> ha InsertRange(), ma IList<T>, che Collection<T> espone tramite la sua proprietà Items no.

Esiste un modo per farlo?

+0

Se si dispone di un campo di supporto per la proprietà di raccolta - è possibile assegnare una nuova istanza ad esso e poi alzare 'OnPropertyChanged' per la raccolta manuale proeprty – sll

+1

correlati/Eventuali duplicati: http: // stackoverflow.com/questions/670577/observablecollection-doesnt-support-addrange-method-so-i-get-notified-for-each – Adam

+2

+1 se 'ObservableCollection' ti fa pensare alla meccanica quantistica e all'esperimento a doppia fenditura. – rfmodulator

risposta

50

ObservableCollection espone una proprietà protetta Items che è la raccolta sottostante senza la semantica di notifica. Ciò significa che è possibile costruire una collezione che fa quello che si vuole da ObservableCollection ereditando:

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

Usage:

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

Questa altra [risposta] (http://stackoverflow.com/questions/13302933/how -to-avoid-firing-observablecollection-collectionchanged-multiple-times-when-r) a una domanda simile suggerita per aggiungere il seguente codice per notificare le modifiche alle proprietà count e indexer: 'this.OnPropertyChanged (new PropertyChangedEventArgs (" Count ")); ' ' this.OnPropertyChanged (new PropertyChangedEventArgs ("Item []")); ' – bouvierr

7

per rendere la risposta di cui sopra utile w/o derivare una nuova classe di base utilizzando la riflessione, ecco un esempio:

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

Cosa c'è di sbagliato con l'ereditarietà? Perché utilizzare la riflessione anziché ricavarne una nuova classe? – ventiseis

+3

Non c'è niente di sbagliato nell'ereditarietà, ovviamente. Questa è solo un'alternativa a questo. Potrebbe rivelarsi utile se si dispone di un sacco di codice legacy e la serializzazione (binaria) dipende da un tipo ObservableCollection. Più facile e una scelta molto più pragmatica per estendere solo ObservableCollection in quel caso. – outbred

+0

Ciao @ outbred. scusa ma come si "usa" il tuo esempio.Grazie – NevilleDastur

-2

esempio: passi desiderati 0,10,20,30,40,50,60,70,80,90,100 -> min = 0, max = 100, posizioni = 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

Questo answer non mi ha mostrato le nuove voci in un DataGrid. Questo OnCollectionChanged opere per me:

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

Queste sembrano le notifiche di modifica più appropriate. – OttPrime