Filtering Elements in a Collection in WPF

In WPF we have the CollectionView that is the instance type bound to the Items controls. The CollectionView allows the use of filters, sorting and other features.

To filter the results shown in a items control we can use the collection view and add a Filter method to it.

Consider the following scenario:

  1. public ObservableCollection<Dragon> Items { getset; }    
  2.      
  3. public ICollectionView ItemsView    
  4. {    
  5.     get { return CollectionViewSource.GetDefaultView(Items); }    
  6. }   
  1. <DataGrid ItemsSource="{Binding ItemsView}" />   
In the preceding code we are binding a collection view to the items control so we can add a filter to it. The next step is to create the filter. For that we have to assign a Predicate<object> (after populating the items).
  1. ItemsView.Filter = new Predicate<object>(o => Filter(o as Dragon));   
I've set it so when the view calls the filter it will call my filter methods, passing each object in the collection. And then I filter the results I want with my custom logic.
  1. private bool Filter(Dragon dragon)    
  2. {    
  3.     return Search == null    
  4.         || dragon.Name.IndexOf(Search, StringComparison.OrdinalIgnoreCase) != -1    
  5.         || dragon.OriginalName.IndexOf(Search, StringComparison.OrdinalIgnoreCase) != -1    
  6.         || dragon.RomajiName.IndexOf(Search, StringComparison.OrdinalIgnoreCase) != -1;    
  7. }  
In the method above I'm using Search, that is a property I will be binding to the screen to grab text from a TextBox.
  1. <TextBox Text="{Binding Search, UpdateSourceTrigger=PropertyChanged}" />   
  1. private string search;    
  2.      
  3. public string Search    
  4. {    
  5.     get { return search; }    
  6.     set    
  7.     {    
  8.         search = value;    
  9.         NotifyPropertyChanged("Search");    
  10.         ItemsView.Refresh(); // required    
  11.     }    
  12. }   
When I set the Search property I'm telling the collection view to refresh. That causes the filter to be applied. If you don't call it then the list will remain the same.

Now if we change the text to be searched the data grid will automatically filter the results.

search

results

It is possible to have the collection view refresh automatically. To do that you need to inherit from a collection view and change the logic there. In the following example I'm saying that if my model implements the INotifyPropertyChanged and a property named “Search” triggers a change then it will refresh itself.
  1. public class NotifiableCollectionView : ListCollectionView    
  2. {    
  3.     public NotifiableCollectionView(IList sourceCollection, object model)    
  4.         : base(sourceCollection)    
  5.     {    
  6.         if (model is INotifyPropertyChanged)    
  7.             (model as INotifyPropertyChanged).PropertyChanged += NotifiableCollectionView_PropertyChanged;    
  8.     }    
  9.      
  10.     void NotifiableCollectionView_PropertyChanged(object sender, PropertyChangedEventArgs e)    
  11.     {    
  12.         if (e.PropertyName == "Search")    
  13.             this.Refresh();    
  14.     }    
  15. }   
Our ICollectionView will be a NotifiableCollectionView instead of the default.
  1. private ICollectionView itemsView;    
  2.      
  3. public ICollectionView ItemsView    
  4. {    
  5.     get    
  6.     {    
  7.         if (itemsView == null)    
  8.         {    
  9.             itemsView = new NotifiableCollectionView(Items, this);    
  10.         }    
  11.         return itemsView;    
  12.     }    
  13. }  
So we can remove the refresh call as in the following:
  1. private string search;    
  2.      
  3. public string Search    
  4. {    
  5.     get { return search; }    
  6.     set    
  7.     {    
  8.         search = value;    
  9.         NotifyPropertyChanged("Search");    
  10.         // ItemsView.Refresh(); // no longer required    
  11.     }    
  12. }  
And if we add Fody then we can simplify it to:
  1. public string Search { getset; }