Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

RemoveAll for ObservableCollections?

I am looking for Linq way (like RemoveAll method for List) which can remove selected items from my ObservableCollection.

I am too new to create an extension method for myself. Is there any way I remove items from ObservableCollection passing a Lambda expression?

like image 636
Arpit Khandelwal Avatar asked Feb 25 '11 14:02

Arpit Khandelwal


6 Answers

I am not aware of a way to remove only the selected items. But creating an extension method is straight forward:

public static class ExtensionMethods
{
    public static int Remove<T>(
        this ObservableCollection<T> coll, Func<T, bool> condition)
    {
        var itemsToRemove = coll.Where(condition).ToList();

        foreach (var itemToRemove in itemsToRemove)
        {
            coll.Remove(itemToRemove);
        }

        return itemsToRemove.Count;
    }
}

This removes all items from the ObservableCollection that match the condition. You can call it like that:

var c = new ObservableCollection<SelectableItem>();
c.Remove(x => x.IsSelected);
like image 62
Daniel Hilgarth Avatar answered Nov 02 '22 00:11

Daniel Hilgarth


Iterating backwards should be more efficient than creating a temporary collection as in Daniel Hilgarth's example.

public static class ObservableCollectionExtensions
{
    public static void RemoveAll<T>(this ObservableCollection<T> collection,
                                                       Func<T, bool> condition)
    {
        for (int i = collection.Count - 1; i >= 0; i--)
        {
            if (condition(collection[i]))
            {
                collection.RemoveAt(i);
            }
        }
    }
}
like image 38
guams Avatar answered Nov 02 '22 01:11

guams


How about this implementation for a one-liner?

observableCollection.Where(l => l.type == invalid).ToList().All(i => observableCollection.Remove(i))

-- Edit --

Sorry, yes, you need a ToList() in the middle to force the first half to evaluate, as LINQ does lazy evaluation by default.

like image 15
simonalexander2005 Avatar answered Nov 02 '22 01:11

simonalexander2005


Each of solution proposed here which uses routine to remove item one by one has one fault. Imagine that you have many items in observable collection, lets say 10.000 items. Then you want to remove items which meets some condition.

If you use solution from Daniel Hilgarth and call: c.Remove(x => x.IsSelected); and there are for example 3000 items to be removed, proposed solution will notify about each item removal. This is due to fact that internal implementation of Remove(item) notify about that change. And this will be called for each of 3000 items in removal process.

So instead of this i created descendant of ObservableCollection and add new method RemoveAll(predicate)

[Serializable]
public class ObservableCollectionExt<T> : ObservableCollection<T>
{
    public void RemoveAll(Predicate<T> predicate)
    {
        CheckReentrancy();

        List<T> itemsToRemove = Items.Where(x => predicate(x)).ToList();
        itemsToRemove.ForEach(item => Items.Remove(item));

        OnPropertyChanged(new PropertyChangedEventArgs("Count"));
        OnPropertyChanged(new PropertyChangedEventArgs("Item[]"));
        OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
    }
}

Interesting line is itemsToRemove.ForEach(item => Items.Remove(item));. Calling directly Items.Remove(item) will not notify about item removed.

Instead after removal of required items, changes are notified at once by calls:

OnPropertyChanged(new PropertyChangedEventArgs("Count"));
OnPropertyChanged(new PropertyChangedEventArgs("Item[]"));
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
like image 10
psulek Avatar answered Nov 02 '22 01:11

psulek


This is my version of an extension method solution, which is only a slight variation on the accepted answer, but has the advantage that the count returned is based on confirmed removal of the item from the collection:

public static class ObservableCollectionExtensionMethods
{
    /// <summary>
    /// Extends ObservableCollection adding a RemoveAll method to remove elements based on a boolean condition function
    /// </summary>
    /// <typeparam name="T">The type contained by the collection</typeparam>
    /// <param name="observableCollection">The ObservableCollection</param>
    /// <param name="condition">A function that evaluates to true for elements that should be removed</param>
    /// <returns>The number of elements removed</returns>
    public static int RemoveAll<T>(this ObservableCollection<T> observableCollection, Func<T, bool> condition)
    {
        // Find all elements satisfying the condition, i.e. that will be removed
        var toRemove = observableCollection
            .Where(condition)
            .ToList();

        // Remove the elements from the original collection, using the Count method to iterate through the list, 
        // incrementing the count whenever there's a successful removal
        return toRemove.Count(observableCollection.Remove);
    }
}
like image 2
Chris Peacock Avatar answered Nov 02 '22 00:11

Chris Peacock


There is no way to pass an expression to the ObservableCollection to remove matching items, in the same way that a generic list has. ObservableCollection adds and removes one item at a time.

You will have to create your own implementation of INotifyCollectionChanged in order to do this, or as you mention create an extension method.

like image 1
jjrdk Avatar answered Nov 02 '22 00:11

jjrdk