3

It appears that filtering an ObservableCollection with CollectionViewSource is not possible in WinRT:

See here!

I can filter using LINQ, but how do I get the UI to update if changes that affect the filtered data are made?

Community
  • 1
  • 1
c0D3l0g1c
  • 3,020
  • 5
  • 33
  • 71

2 Answers2

4

I ended up writing my own class to achieve the desired effect:

public class ObservableCollectionView<T> : ObservableCollection<T>
{
    private ObservableCollection<T> _view;
    private Predicate<T> _filter;

    public ObservableCollectionView(IComparer<T> comparer)
        : base(comparer)
    {

    }

    public ObservableCollectionView(IComparer<T> comparer, IEnumerable<T> collection)
        : base(comparer, collection)
    {

    }

    public ObservableCollectionView(IComparer<T> comparer, IEnumerable<T> collection, Predicate<T> filter)
        : base(comparer, collection == null ? new T[] { } : collection)
    {
        if (filter != null)
        {
            _filter = filter;

            if (collection == null)
                _view = new ObservableCollection<T>(comparer);
            else
                _view = new ObservableCollection<T>(comparer, collection);
        }
    }

    public ObservableCollection<T> View
    {
        get
        { 
            return (_filter == null ? this : _view);
        }
    }

    public Predicate<T> Filter
    {
        get
        {
            return _filter;
        }
        set
        {
            if (value == null)
            {
                _filter = null;
                _view = new ObservableCollection<T>(Comparer);
            }
            else
            {
                _filter = value;
                Fill();
            }
        }
    }

    private void Fill()
    {
        _view = new ObservableCollection<T>(Comparer);
        foreach (T item in this)
        {
            if (Filter(item))
                View.Add(item);
        }
    }

    private int this[T item]
    {
        get
        {
            int foundIndex = -1;
            for (int index = 0; index < View.Count; index++)
            {
                if (View[index].Equals(item))
                {
                    foundIndex = index;
                    break;
                }
            }
            return foundIndex;
        }
    }

    protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
    {
        base.OnCollectionChanged(e);

        if (_filter != null)
        {
            switch (e.Action)
            {
                case NotifyCollectionChangedAction.Add:
                    foreach (T item in e.NewItems)
                        if (Filter(item))
                            View.Add(item);
                    break;

                case NotifyCollectionChangedAction.Move:

                    break;

                case NotifyCollectionChangedAction.Remove:
                    foreach (T item in e.OldItems)
                        if (Filter(item))
                            View.Remove(item);
                    break;

                case NotifyCollectionChangedAction.Replace:
                    for (int index = 0; index < e.OldItems.Count; index++)
                    {
                        T item = (T)e.OldItems[index];
                        if (Filter(item))
                        {
                            int foundIndex = this[item];
                            if (foundIndex != -1)
                                View[foundIndex] = (T)e.NewItems[index];
                        }
                    }
                    break;

                case NotifyCollectionChangedAction.Reset:
                    Fill();
                    break;
            }
        }
    }

    protected override void OnPropertyChanged(PropertyChangedEventArgs e)
    {
        base.OnPropertyChanged(e);

        if (_filter != null)
        {
    // TODO: Implement code for property changes
        }
    }
}

Not yet perfect. So improvements/suggestions welcomed.

You can now bind this object, directly to a control using the View property.

c0D3l0g1c
  • 3,020
  • 5
  • 33
  • 71
  • not perfect... YET! can you show an simple usage example? It would be better to understand this class purpose/advantages. – letiagoalves Apr 30 '13 at 16:50
  • 1
    Purpose: A sortable and filtered ObservableCollection. Create an instance of this object, use as you would normally use an ObservableCollection. Has a filter property in the form of a Predicate, so you can filter the data. Then when binding to a control, bind to the View property. that's it! – c0D3l0g1c Apr 30 '13 at 17:24
  • Nice solution. I am going to use this in my project for the time being! – dub stylee Jan 27 '15 at 17:27
  • I don't understand the presence of the constructor overloads that take `IComparer` arguments. These overloads don't exist in `ObservableCollection`, so a) they don't seem to be needed in this view implementation, and b) the above won't compile, because the `base` calls aren't valid (the overload doesn't exist in the base class). – Peter Duniho May 04 '15 at 23:34
0

You need to make sure the filtering changes are observable, so you can set the source of the CollectionViewSource to an ObservableCollection and make the changes on that collection or assign a new Source of the CVS to a new, filtered collection.

Filip Skakun
  • 31,624
  • 6
  • 74
  • 100
  • I have a source ObservableCollection, which I filter and returns IEnumerable. Then I pass this to the constructor of an ObservableCollection and assign to CollectionViewSource. Updates happen against the source ObservableCollection. Now their is a disconnect between the source and filtered collections, so changes to the source are not picked up by the filter. How do I overcome this? – c0D3l0g1c Apr 27 '13 at 05:25
  • You need to filter and assign it again. Alternatively update the filtered collection when you update the original one. – Filip Skakun Apr 27 '13 at 19:38
  • I have... see my answer below. – c0D3l0g1c Apr 27 '13 at 19:54