34

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?

GazTheDestroyer
  • 20,722
  • 9
  • 70
  • 103
  • If you have a backing field for collection property - you can assign a new instance to it and then raise `OnPropertyChanged` for collection proeprty manually – sll Dec 22 '11 at 16:41
  • 1
    related/possible duplicate: http://stackoverflow.com/questions/670577/observablecollection-doesnt-support-addrange-method-so-i-get-notified-for-each – Adam Dec 22 '11 at 16:42
  • 3
    +1 if `ObservableCollection` makes you think of quantum mechanics and the double-slit experiment. – rfmodulator Dec 22 '11 at 16:53
  • 1
    http://blogs.msdn.com/b/nathannesbit/archive/2009/04/20/addrange-and-observablecollection.aspx – Arsen Mkrtchyan Dec 22 '11 at 17:50

4 Answers4

59

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);  
}
driis
  • 161,458
  • 45
  • 265
  • 341
  • 8
    This other [answer](http://stackoverflow.com/questions/13302933/how-to-avoid-firing-observablecollection-collectionchanged-multiple-times-when-r) to a similar question suggested to add the following code to notify changes to the count and indexer properties: `this.OnPropertyChanged(new PropertyChangedEventArgs("Count"));` `this.OnPropertyChanged(new PropertyChangedEventArgs("Item[]"));` – bouvierr Dec 31 '13 at 18:28
10

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)});
}
outbred
  • 1,028
  • 8
  • 13
  • 1
    What's wrong with inheritenance? Why are use using reflection instead of deriving a new class? – ventiseis Oct 08 '15 at 06:20
  • 3
    There's nothing wrong with inheritance, of course. This is just an alternative to that. It may prove useful if you have a ton of legacy code and your serialization (binary) depends on an ObservableCollection type. Easier and a far more pragmatic choice to just extend ObservableCollection in that case. – outbred Oct 09 '15 at 13:24
  • Hi @outbred . sorry but how do you "use" your example. Thanks – NevilleDastur Jul 07 '17 at 20:10
  • @NevilleDastur - use it like this: var coll = new ObservableCollection(); coll.InsertRange(); It's an extension method so just make sure you are using the namespace where you declare the extension method and you should be good to go. – outbred Jul 19 '17 at 19:24
  • I voted for this as extension method allows me to use without having to change my collections to a new class. –  Sep 12 '17 at 10:57
3

This answer didn't show me the new entries in a DataGrid. This OnCollectionChanged works for 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[]"));
    }
}
Community
  • 1
  • 1
Bin4ry
  • 652
  • 9
  • 34
  • These seem like the more appropriate change notifications. – OttPrime Aug 15 '17 at 18:40
  • For me, this resulted in an exception saying "Range actions are not supported", whereas the Reset one from the other answer worked. This was for populating a grid. – Hammerite Apr 11 '22 at 14:45
-4

Example: Desired steps 0,10,20,30,40,50,60,70,80,90,100 --> min=0, max=100, steps=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())
    );
benoch
  • 59
  • 1
  • 1