1

I am making a filter for a collection.
I know how to do it using CollectionViewSource.
But I wanted to do it without using CVS.
According to my ideas, there is a CollectionView in the ItemsControl.Items property and you can use the methods of this property.
The filter can be added without problems.
But after calling Items.Refresh() nothing changes.

Simple example:

    <UniformGrid Columns="2">
        <FrameworkElement.Resources>
            <sc:StringCollection
                x:Key="coll">
                <sys:String>112</sys:String>
                <sys:String>22</sys:String>
                <sys:String>33</sys:String>
                <sys:String>114</sys:String>
                <sys:String>411</sys:String>
            </sc:StringCollection>
            <CollectionViewSource
                x:Key="cvs"
                Source="{Binding Mode=OneWay, Source={StaticResource coll}}"
                Filter="OnFilterCV"/>
        </FrameworkElement.Resources>
        <TextBox x:Name="tBox"
                 Text="1"
                 TextChanged="OnTextChanged"
                 VerticalAlignment="Center"/>
        <TextBox x:Name="tBoxCV"
                 Text="1"
                 TextChanged="OnTextChangedCV"
                 VerticalAlignment="Center"/>
        <ItemsControl x:Name="iCtrl"
                      ItemsSource="{Binding Mode=OneWay, Source={StaticResource coll}}">
        </ItemsControl>
        <ItemsControl x:Name="iCtrlCV"
                      ItemsSource="{Binding Mode=OneWay, Source={StaticResource cvs}}">
        </ItemsControl>
    </UniformGrid>
    public partial class MainWindow : Window
    {
        private readonly CollectionViewSource cvs;
        public MainWindow()
        {
            InitializeComponent();
            iCtrl.Items.Filter = OnFilter;
            cvs = (CollectionViewSource)iCtrlCV.FindResource("cvs");
        }

        private bool OnFilter(object obj)
        {
            if (string.IsNullOrWhiteSpace(tBox.Text))
                return true;
            string item = (string)obj;
            return item.Contains(tBox.Text, StringComparison.OrdinalIgnoreCase);
        }

        private void OnTextChanged(object sender, TextChangedEventArgs e)
        {
            Debug.WriteLine($"OnTextChanged:\"{tBox.Text}\"");
            iCtrl?.Items.Refresh();
        }

        private void OnFilterCV(object sender, FilterEventArgs e)
        {
            e.Accepted = string.IsNullOrWhiteSpace(tBoxCV.Text) ||
                ((string)e.Item).Contains(tBoxCV.Text, StringComparison.OrdinalIgnoreCase);
        }

        private void OnTextChangedCV(object sender, TextChangedEventArgs e)
        {
            Debug.WriteLine($"OnTextChangedCV:\"{tBoxCV.Text}\"");
            cvs?.View.Refresh();
        }
    }

Am I misunderstanding something or doing something wrong?

Updated. Solution based on comment from @BionicCode.

        private void OnTextChanged(object sender, TextChangedEventArgs e)
        {
            Debug.WriteLine($"OnTextChanged:\"{tBox.Text}\"");
            //iCtrl?.Items.Refresh();
            if (iCtrl != null)
                iCtrl.Items.Filter = new Predicate<object>(OnFilter);
        }

EldHasp
  • 6,079
  • 2
  • 9
  • 24
  • The filter is applied the moment you assign the CollectionView.Filter property. It executes every time the collection changes. There is no need to call Refresh(). Calling Refresh when a TextBox changes does not make sense either unless the collection has changed. – BionicCode Jan 20 '22 at 10:50
  • If you want to reinvoke the filter delegate because its conditions have changed (while the coolection iteself has not changed), simply re-assign the Filter. – BionicCode Jan 20 '22 at 10:52
  • @BionicCode, Thanks for the recommendation! I added to my answer the code that implements the method you suggested. – EldHasp Jan 20 '22 at 11:23

1 Answers1

2

The ItemsControl.Items is of type ItemsCollection. ItemsCollection implements a different Refresh behavior. The Items property is basically intended for internal use. If you have to rely on CollectionView.Refresh you should use the CollectionView explicitly:

ItemsControl itemsControl;
itemsControl.Items.Filter = item => (item as string).Contains("A");
CollectionView collectionView = CollectionViewSource.GetDefaultView(itemsControl.ItemsSource);
collectionView.Refresh();
BionicCode
  • 1
  • 4
  • 28
  • 44
  • Thanks for the reply, especially for the clarification about the different implementation of Refresh(). But using a CollectionViewSource might not work for me. I'm making a Custom Control and the ItemsSource can be bound to a CollectionView and I'm afraid there might be a conflict with GetDefaultView. I will test it and post the result. – EldHasp Jan 20 '22 at 11:21
  • 1
    There should be no conflict. If the ItemsSource itself is a ICollectionView GetDefaultView will return it directly. As a control author you should not worry about the type of the ItemsSource. However, the code will work if ItemsSoucre is a CollectionView already. – BionicCode Jan 20 '22 at 11:47
  • According to the first tests, it seems that you are right - it works as you expected. In your opinion, which method is better, this one or the one you suggested with the filter update? – EldHasp Jan 20 '22 at 12:19
  • 1
    Because Refresh is implicitly called when setting Filter or SortDescriptions or GroupDescriptions it doesn't really matter. Setting Filter would communicate the true intention of your code better to readers: re-apply filters after a filter condition has changed. Just note that re-applying the filter/Refresh has a serious performance impact. So use it with care. – BionicCode Jan 20 '22 at 12:44