Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Adding a range of values to an ObservableCollection efficiently

I have an ObservableCollection of items that is bound to a list control in my view.

I have a situation where I need to add a chunk of values to the start of the collection. Collection<T>.Insert documentation specifies each insert as an O(n) operation, and each insert also generates a CollectionChanged notification.

Therefore I would ideally like to insert the whole range of items in one move, meaning only one shuffle of the underlying list, and hopefully one CollectionChanged notification (presumably a "reset").

Collection<T> does not expose any method for doing this. List<T> has InsertRange(), but IList<T>, that Collection<T> exposes via its Items property does not.

Is there any way at all to do this?

like image 380
GazTheDestroyer Avatar asked Dec 22 '11 16:12

GazTheDestroyer


People also ask

What is ObservableCollection in WPF?

WPF ObservableCollection An ObservableCollection is a dynamic collection of objects of a given type. Objects can be added, removed or be updated with an automatic notification of actions. When an object is added to or removed from an observable collection, the UI is automatically updated.


2 Answers

The ObservableCollection exposes an protected Items property which is the underlying collection without the notification semantics. This means you can build a collection that does what you want by inheriting 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));     } } 

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);   } 
like image 107
driis Avatar answered Sep 29 '22 23:09

driis


To make the above answer useful w/o deriving a new base class using reflection, here's an example:

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)}); } 
like image 20
outbred Avatar answered Sep 29 '22 22:09

outbred