How to automatically update filter and/or sort order on CollectionViewSource, when an individual item's property changes?

你说的曾经没有我的故事 提交于 2019-12-03 12:30:11

I had to handle this problem and although the 'Refresh()' solution works well, it is quite long to execute because its refreshes the whole list just for one item property changed event. Not very good. And in a scenario of real time data entering the collection every 1 seconds, I let you imagine the result in user experience if you use this approach :)

I came up with a solution which base is : when adding an item to collection wrapped in a collectionview, then the item is evaluated by the filter predicate and, based on this result, displayed or not in the view.

So instead of calling refresh(), I came up simulating an insert of the object that got its property updated. By simulating the insert of the object, it is going to be automatically evaluated by the filter predicate without need to refresh the whole list with a refresh.

Here is the code in order to do that :

The derived observable collection :

namespace dotnetexplorer.blog.com
{
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;

/// <summary>
/// Derived class used to be able to manage filter application when a collection item property changed
///   whithout having to do a refresh
/// </summary>
internal sealed class CustomObservableCollection : ObservableCollection<object>
{
    /// <summary>
    ///   Initializes a new instance of the <see cref = "CustomObservableCollection " /> class.
    /// </summary>
    public CustomObservableCollection ()
    {
    }

    /// <summary>
    /// Initializes a new instance of the <see cref="CustomObservableCollection "/> class.
    /// </summary>
    /// <param name="source">
    /// The source.
    /// </param>
    public CustomObservableCollection (IEnumerable<object> source)
        : base(source)
    {
    }

    /// <summary>
    /// Custom Raise collection changed
    /// </summary>
    /// <param name="e">
    /// The notification action
    /// </param>
    public void RaiseCollectionChanged(NotifyCollectionChangedEventArgs e)
    {
        OnCollectionChanged(e);
    }
}
}

And there is the code to use when receiveing item property changed event where substitute source is a CustomObservableCollection :

        private void ItemPropertyChanged(object sender, PropertyChangedEventArgs e)
    {

                // To avoid doing a refresh on a property change which would end in a very hawful user experience
                // we simulate a replace to the collection because the filter is automatically applied in this case
                int index = _substituteSource.IndexOf(sender);

                var argsReplace = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace,
                                                                       new List<object> { sender },
                                                                       new List<object> { sender }, index);
                _substituteSource.RaiseCollectionChanged(argsReplace);
            }

        }
    }

Hope this will help !

Don't you just hate it when that happens, not 5 minutes gone since I posted the question, and I've figured out what the problem is - and it was something quite basic. On the CollectionViewSource object, there is a View property, which has a Refresh() function. Calling this function after a property on an underlying item contained in the ObservableCollection<T> changes, seems to have done it.

Basically, all I had to do was change the CollectionViewSource object into a member variable, and then save it when LoadData() is called:

private void MainPage_Loaded(object sender, RoutedEventArgs e)
{
    if (!App.ViewModel.IsDataLoaded)
    {
        App.ViewModel.LoadData();
        m_isSelectedListView = this.Resources["IsSelectedCollectionView"] as CollectionViewSource;
        if (m_isSelectedListView != null)
        {
            m_isSelectedListView.Source = App.ViewModel.Items;
        }
    }
}

Then, call Refresh() on the view, after any of the items in the underlying ObservableCollection<T> changes. So in MainPage.xaml.cs, just after changing the last item, add the call to refresh:

private void ApplicationBarIconButton_Click(object sender, EventArgs e)
{
    ItemViewModel item = App.ViewModel.Items[App.ViewModel.Items.Count - 1];
    item.IsSelected = !item.IsSelected;
    m_isSelectedListView.View.Refresh();
}

... and the second Pivot's ListBox is updated instantly. Such a short line of code, a whole world of difference!

In the time it took me to write up that question, there are a hundred things I could've done :-( Ah well, better late than never I guess - thought to post the answer here, if only to save someone else tearing out their hair like I did.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!