3

I would like my own descendant of ObservableCollection to support AddRange method. Here is what I currently have:

public class ObservableCollectionPlus<T> : ObservableCollection<T>
{
    public void InsertRange(IEnumerable<T> items)
    {
        this.CheckReentrancy();
        foreach (var item in items) Items.Add(item);

        var type = NotifyCollectionChangedAction.Reset;
        var colChanged = new NotifyCollectionChangedEventArgs(type);
        var countChanged = new PropertyChangedEventArgs("Count");

        OnPropertyChanged(countChanged);
        OnCollectionChanged(colChanged);
    }
}

I don't have much knowledge of what's exactly going on here and why are these events get raised. This is a solutiom that I've assembled after doing some research on google and stackoverflow.

Now, if I bind an instance of my class to say LongListSelector then, after dynamically adding items via InsertRange to ObservableCollectionPlus, a binded LongListSelector's scroll position is sent to it's top.

If I add items in this standard way: foreach (var item in items) collection.Add(item); then LongListSelector's position does not get shifted. But of course this way I get DataBinding notifications overhead in which is undesired.

Apparently, something is done wrong in my current solution. How can I implement InsertRange that will behave exactly like foreach (var item in items) collection.Add(item); but will only fire DataBinding notification once and will not do strange things to binded object's scroll position?

src091
  • 2,807
  • 7
  • 44
  • 74
  • I think your problem is that you are using `NotifyCollectionChangedAction.Reset` which basically tells the binding engine to throw out everything and rebind the whole collection. This is appropriate because you've made a (potentially) large change to your collection, but it will lose the scroll position. – Matt Burland Jan 29 '13 at 21:14

4 Answers4

3

It could be because your sending the NotifyCollectionChangedAction.Reset notification, maybe just the NotifyCollectionChangedAction.Add will work, Maybe :)

public class ObservableRangeCollection<T> : ObservableCollection<T>
{
    public void AddRange(IEnumerable<T> collection)
    {
        foreach (var i in collection)
        {
            Items.Add(i);
        }
        OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, collection.ToList()));
    }
}
sa_ddam213
  • 42,848
  • 7
  • 101
  • 110
  • I was going to suggest the same. Without trying, I'm not sure if it will scroll to the end or stay at the same point, however. – Matt Burland Jan 29 '13 at 21:17
  • Well it does scrolls to the end with animation and also `LongListSelector`'s `ListHeader` contents are located on bottom of control now. That looks very weird. – src091 Jan 29 '13 at 21:43
  • In a way, it does a lot more strange things to `LongListSelector` now :) – src091 Jan 29 '13 at 21:46
  • The NotifyCollectionChangedEventArgs(..) will result in an Exception: This collection cannot work with indices larger than Int32.MaxValue - 1 (0x7FFFFFFF - 1). Parameter name: index And the list will not update correctly. Anybody got the same problem? No idea how to fix this yet. – sust86 Mar 17 '14 at 10:07
  • @sust86 did you fix it? I'm facing the same problem :( – Dushyant Bangal Jul 31 '15 at 07:20
  • I was a problem when I want to add a list. Exception details: `An exception of type 'System.NotSupportedException' occurred in PresentationFramework.dll but was not handled in user code` `Additional information: Range actions are not supported.` – Behzad Aug 05 '15 at 05:55
2

I've used this in a project recently...

public class RangeObservableCollection<T> : ObservableCollection<T>
{
    private bool _suppressNotification = false;

    protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
    {
        if (!_suppressNotification)
            base.OnCollectionChanged(e);
    }

    public void AddRange(IEnumerable<T> list)
    {
        if (list == null)
            throw new ArgumentNullException("list");

        _suppressNotification = true;

        foreach (T item in list)
        {
            Add(item);
        }
        _suppressNotification = false;
        OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
    }
}
Depechie
  • 6,102
  • 24
  • 46
  • It does not help... Still the same `NotifyCollectionChangedAction.Reset` that shifts scroll position. – src091 Jan 30 '13 at 11:05
  • The control has weird things going on indeed... on another project I'm also grouping the collection and when I use the `foreach... add(***)` the list would always position itself to the bottom and I needed to write some stupid code behind to force it back to the top. Maybe better to post your findings on the phone toolkit codeplex site? – Depechie Jan 30 '13 at 12:02
0

The problem you are experiencing with DataBinding might be connected to the fact that you don't raise PropertyChanged for indexer (property name "Item[]") as it happens in ObservableCollection according to the source code.

You can also have a look at a nice implementation of ObservableRangeCollection by James Montemagno here on GitHub, it inherits from ObservableColection and includes AddRange and ReplaceRange methods with all PropertyChaged and CollectionChanged notifications needed for DataBinding.

Stas Ivanov
  • 1,173
  • 1
  • 14
  • 24
-1

It took me ages, the trouble was always with the arguments to pass to the NotifyCollectionChangedEventArgs ctor. There are many different ctors that take different arguments, depending on the action. The following seems to finally work for me: https://github.com/lolluslollus/Utilz/blob/master/Utilz/SwitchableObservableCollection.cs

Lollus
  • 11
  • 2