views:

158

answers:

2

please note that I am trying to use NotifyCollectionChangedAction.Add action instead of .Reset. the latter does work, but it is not very efficient with large collections.

so i subclassed ObservableCollection:

public class SuspendableObservableCollection<T> : ObservableCollection<T>

for some reason, this code:

private List<T> _cachedItems;
...

    public void FlushCache() {
        if (_cachedItems.Count > 0) {

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

        OnCollectionChanged(new NotifyCollectionChangedEventArgs(
            NotifyCollectionChangedAction.Add, (IList<T>)_cachedItems));
        }
    }

is throwing A collection Add event refers to item that does not belong to collection

this appears to be a bug in BCL ?

I can step through and see prior to calling OnCollectionChanged that new items are added to this.Items

WOW

just made a staggering discovery. None of these approaches worked for me (flush, addrange), because the error appears to be triggered ONLY if this collection is bound to my Listview!!

TestObservableCollection<Trade> testCollection = new TestObservableCollection<Trade>();
List<Trade> testTrades = new List<Trade>();

for (int i = 0; i < 200000; i++) 
    testTrades.Add(t);

testCollection.AddRange(testTrades); // no problems here.. 
_trades.AddRange(testTrades); // this one is bound to ListView .. BOOOM!!!

In conclusion, ObservableCollection does support adding incremental lists, but a ListView doesn't. Andyp figured out a workaround to make it work with CollectionView below, but since .Refresh() is called, that is no different than just calling OnCollectionChanged( .Reset )..

A: 

I believe you need to cast it to an IList:

base.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, (IList)_cachedItems));

George Howarth
thanks, now i am back to "A collection Add event refers to item that does not belong to collection"
Sonic Soul
I adjusted my code, thanks
Sonic Soul
hmmm, how about instead of `Items.Add(item)`, `base.Add(item)`?
George Howarth
George, tried Add(item) and base.Add(item), still complains it is not part of the collection...
Sonic Soul
I actually meant `this.Add(item)`, sorry about that... I did get this error before when implementing an `ObservableCollection<T>`, but I don't have access to the code at the moment I'll into it tomorrow.
George Howarth
isnt this.Add same as Add ? anyways, i tried it just to be sure, and still same error.. i think this may be a bug :(
Sonic Soul
+2  A: 

Hi,

you can implement AddRange() for the ObservableCollection like this as shown here:

public class RangeObservableCollection<T> : ObservableCollection<T>
{
    private bool _SuppressNotification;

    public override event NotifyCollectionChangedEventHandler CollectionChanged;

    protected virtual void OnCollectionChangedMultiItem(
        NotifyCollectionChangedEventArgs e)
    {
        NotifyCollectionChangedEventHandler handlers = this.CollectionChanged;
        if (handlers != null)
        {
            foreach (NotifyCollectionChangedEventHandler handler in 
                handlers.GetInvocationList())
            {
                if (handler.Target is CollectionView)
                    ((CollectionView)handler.Target).Refresh();
                else
                    handler(this, e);
            }
        }
    }

    protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
    {
        if (!_SuppressNotification)
        {
            base.OnCollectionChanged(e);
            if (CollectionChanged != null)
                CollectionChanged.Invoke(this, 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;

        OnCollectionChangedMultiItem(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, list));
    }
}

UPDATE: After binding to ListBox I was seeing an InvalidOperationException too (same message you were seeing). According to this article that's because CollectionView doesn't support range actions. Luckily the article also supplies a solution (although it feels a little "hack-ish").

UPDATE 2: Added a fix that raises the overridden CollectionChanged event in the overridden implementation of OnCollectionChanged().

andyp
thanks, but i am trying to change away from .Reset action. the whole point here is that i want to add only new items. if my collection reaches large size, .reset is very slow as i am filtering it as well
Sonic Soul
Ah, I missed that - updated my code to use NotifyCollectionChangedAction.Add instead of Reset.
andyp
Added a link and code that resolves (avoids) the CollectionView's problem with range operations.
andyp
hmm... i plugged your collection in as is and AddRange works w/out throwing!!! :) .. but for some mysterious reason items are not being added from regular .Add(foo)..most likely the problem is on my side.. investigating..
Sonic Soul
Nope, sorry, it wasn't you. My implementation didn't raise the overridden CollectionChange event in the overridden OnCollectionChange method. Fixed that, now Add() works too.
andyp
thanks! any way you could elaborate on why GetInvocationList has to be called in this scenario and why CollectionView needs to be refreshed.. does that make it equivalent with just called Reset event?
Sonic Soul
yah.. looks like there is no performance benefit of doing it this way.. (same as just calling .Reset)
Sonic Soul