CollectionViewSource Filter not refreshed when Source is changed
Asked Answered
R

5

12

I have a WPF ListView bound to a CollectionViewSource. The source of that is bound to a property, which can change if the user selects an option.

When the list view source is updated due to a property changed event, everything updates correctly, but the view is not refreshed to take into account any changes in the CollectionViewSource filter.

If I attach a handler to the Changed event that the Source property is bound to I can refresh the view, but this is still the old view, as the binding has not updated the list yet.

Is there a decent way to make the view refresh and re-evaluate the filters when the source changes?

Cheers

Reprography answered 19/3, 2009 at 10:2 Comment(1)
In case anyone finds this, it is a bit out of date now. In WPF 4.5, new features were added to allow "Live" sorting, filtering and grouping. See jonathanantoine.com/2011/10/05/…Schematize
S
12

Updating the CollectionView.Filter based on a PropertyChanged event is not supported by the framework. There are a number of solutions around this.

1) Implementing the IEditableObject interface on the objects inside your collection, and calling BeginEdit and EndEdit when changing the property on which the filter is based. You can read more about this on the Dr.WPF's excellent blog here : Editable Collections by Dr.WPF

2) Creating the following class and using the RefreshFilter function on the changed object.

public class FilteredObservableCollection<T> : ObservableCollection<T>
{
    public void RefreshFilter(T changedobject)
    {
        OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, changedobject, changedobject));
    }        
}

Example:

public class TestClass : INotifyPropertyChanged
{
    private string _TestProp;
    public string TestProp
    {
        get{ return _TestProp; }
        set
        { 
            _TestProp = value;
            RaisePropertyChanged("TestProp");
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    protected void RaisePropertyChanged(string propertyName)
    {
        var handler = this.PropertyChanged;
        if (handler != null)
            handler(this, new PropertyChangedEventArgs(propertyName));
    }
}


FilteredObservableCollection<TestClass> TestCollection = new FilteredObservableCollection<TestClass>();

void TestClass_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
    switch (e.PropertyName)
    {
        case "TestProp":
            TestCollection.RefreshFilter(sender as TestClass);
            break;
    }
}

Subscribe to the PropertyChanged event of the TestClass object when you create it, but don't forget to unhook the eventhandler when the object gets removed, otherwise this may lead to memory leaks

OR

Inject the TestCollection into the TestClass and use the RefreshFilter function inside the TestProp setter. Anyhow, the magic here is worked by the NotifyCollectionChangedAction.Replace which updates the item entirely.

Suellensuelo answered 1/4, 2011 at 15:6 Comment(2)
I am currently stuck with .NET 4.0 and the IEditableObject solution works like a charm.Conjoint
I couldn't get the IEditableObject to work. But the FilteredObservableCollection works great. Thanks for the solutionGleda
A
2

Are you changing the actual collection instance assigned to the CollectionViewSource.Source, or are you just firing PropertyChanged on the property that it's bound to?

If the Source property is set, the filter should be recalled for every item in the new source collection, so I'm thinking something else is happening. Have you tried setting Source manually instead of using a binding and seeing if you still get your behavior?

Edit:

Are you using CollectionViewSource.View.Filter property, or the CollectionViewSource.Filter event? The CollectionView will get blown away when you set a new Source, so if you had a Filter set on the CollectionView it won't be there anymore.

Ab answered 19/3, 2009 at 17:53 Comment(1)
Yes, I am changing the collection, and the items in the list view are updated reflecting the new collection. However filter is not re-evaluated. Doing it manually didn't help: ((CollectionViewSource)this.Resources["logEntryViewSource"]).Source = _application.CurrentLog.Entries.ObservableCollectionReprography
W
2

I found a specific solution for extending the ObservableCollection class to one that monitors changes in the properties of the objects it contains here.

Here's that code with a few modifications by me:

namespace Solution
{
public class ObservableCollectionEx<T> : ObservableCollection<T> where T : INotifyPropertyChanged
    {
        protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
        {
            if (e != null)  // There's been an addition or removal of items from the Collection
            {
                Unsubscribe(e.OldItems);
                Subscribe(e.NewItems);
                base.OnCollectionChanged(e);
            }
            else
            {
                // Just a property has changed, so reset the Collection.
                base.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));

            }

        }

        protected override void ClearItems()
        {
            foreach (T element in this)
                element.PropertyChanged -= ContainedElementChanged;

            base.ClearItems();
        }

        private void Subscribe(IList iList)
        {
            if (iList != null)
            {
                foreach (T element in iList)
                    element.PropertyChanged += ContainedElementChanged;
            }
        }

        private void Unsubscribe(IList iList)
        {
            if (iList != null)
            {
                foreach (T element in iList)
                    element.PropertyChanged -= ContainedElementChanged;
            }
        }

        private void ContainedElementChanged(object sender, PropertyChangedEventArgs e)
        {
            OnPropertyChanged(e);
            // Tell the Collection that the property has changed
            this.OnCollectionChanged(null);

        }
    }
}
Welkin answered 17/2, 2012 at 7:28 Comment(0)
A
2

Maybe a bit late to the party but just in case

You can also use CollectionViewSource.LiveSortingProperties I found it through this blog post.

public class Message : INotifyPropertyChanged
{
    public string Text { get; set; }
    public bool Read { get; set; }

    /* for simplicity left out implementation of INotifyPropertyChanged */
}
public ObservableCollection<Message> Messages {get; set}
ListCollectionView listColectionView = (ListCollectionView)CollectionViewSource.GetDefaultView(Messages);
listColectionView.IsLiveSorting = true;
listColectionView.LiveSortingProperties.Add(nameof(Message.Read));
listColectionView.SortDescriptions.Add(new SortDescription(nameof(Message.Read), ListSortDirection.Ascending));
Arbitrator answered 8/7, 2021 at 12:41 Comment(0)
S
0

I found a relatively simple method to do this. I changed the readonly ICollectionView property to get/set and added the raised property event:

   Property TypeFilteredCollection As ICollectionView
        Get
            Dim returnVal As ICollectionView = Me.TypeCollection.View
            returnVal.SortDescriptions.Add(New SortDescription("KeyName", ListSortDirection.Ascending))
            Return returnVal
        End Get
        Set(value As ICollectionView)

            RaisePropertyChanged(NameOf(TypeFilteredCollection))
        End Set
    End Property

Then to update, i just used:

    Me.TypeFilteredCollection = Me.TypeFilteredCollection

This clearly won't work if you don't have somewhere to trigger that update though.

Spreadeagle answered 13/10, 2021 at 2:49 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.