ebpa
ebpa

Reputation: 1259

Remove an item from an ObservableCollection in a CollectionChanged event handler

I'm hoping to be able to reject some items after they have been added to an ObservableCollection. I am not able to subclass the ObservableCollection or use any sort of view, so I seem to be limited to using the one event handler defined (CollectionChanged) to perform a .Remove() on the prohibited items. It's fine if the items exist for the short period between the event being raised and then handled; the items should just not persist in the collection. Calling .Remove() within the CollectionChanged event handler doesn't appear to be allowed. At runtime .NET throws an InvalidOperationException:

"Cannot change ObservableCollection during a CollectionChanged event."

Personally I think .NET should allow me to. If I create an infinite loop, it's my own darn fault.

The code I would like to use would look like:

myCollection.CollectionChanged += (sender, args) =>
{
    if (args.Action == NotifyCollectionChangedAction.Remove)
        return;
    foreach (var itm in myCollection)
    {
        if (itm.name == "Fred")
            myCollection.Remove(itm);
    }
}

I'm not sure what options I have. Using a dispatcher doesn't seem to work. Triggering another event and placing the .Remove call in another handler is the only other option that comes to mind.

Upvotes: 16

Views: 55234

Answers (5)

Raj Ranjhan
Raj Ranjhan

Reputation: 3917

Check out Common Mistakes using Observable Collection.

Having said that, if you still want to go this route - you can spin a new Thread

Upvotes: 20

TonyM
TonyM

Reputation: 166

I had tried using setting a flag to request collection add/remove changes and then when the System.Windows.Interop.ComponentDispatcher.ThreadIdle called my handler, doing my add or remove. That works. However, using a try-finally in the collection changed handler to unwire and then rewire that same collection changed event handler also bypassed the re-entrance issue:

private void MyDataGrid_CollectionChanged( object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e )
{
    try
    {
        dgRows.CollectionChanged -= MyDataGrid_CollectionChanged;
        
        switch( e.Action )
        {
            case NotifyCollectionChangedAction.Add:
                if( SomeTestIsTrue() )
                dgRows.Add( new vmRowObject() );
                break;
        }
    }
    finally
    {
        dgRows.CollectionChanged += MyDataGrid_CollectionChanged;
    }
}

Upvotes: 1

Mark
Mark

Reputation: 419

Used this in the oncollectionchanged and it works (WPF and MVVM example):

new System.Threading.Thread(t =>
{
  Application.Current.Dispatcher.Invoke((Action)delegate
  {
    OnHoldMessages.Add(_selectedOnHoldMessage);
    RaisePropertyChanged(propertyName: "OnHoldMessages");
  });
}).Start();

Upvotes: 1

Gilad
Gilad

Reputation: 6575

use a ToList() in order to iterate over the list.

foreach(var item in collection.ToList())
{
    if(item.Name == "Fred")
    {
        collection.Remove(item);
    }
}

Upvotes: 0

tam
tam

Reputation: 1583

if you really want to modify a collection you are going to want to iterate through a copy of the collection. its because you are trying to modify the collection in the foreach loop thats causing you grief.

example

var copy = new ObservableCollection<YourType>(collection)
foreach(var item in copy)
{
    if(item.Name == "Fred")
    {
        collection.Remove(item);
    }

}

that said, I agree with Anurag that you shouldn't be doing this type of thing with an observablecollection and certainly not inside a CollectionChanged Event.

Upvotes: 12

Related Questions