Filtering elements in a collection

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

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

Consider the following scenario:

public ObservableCollection<Dragon> Items { get; set; }

public ICollectionView ItemsView
{
    get { return CollectionViewSource.GetDefaultView(Items); }
}
<DataGrid ItemsSource="{Binding ItemsView}" />

In the above 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 having Items populated).

ItemsView.Filter = new Predicate<object>(o => Filter(o as Dragon));

I’ve set it so when the view calls filter it is going to call my filter method passing each object in the collection. And then I filter the results I want with my custom logic.

private bool Filter(Dragon dragon)
{
    return Search == null
        || dragon.Name.IndexOf(Search, StringComparison.OrdinalIgnoreCase) != -1
        || dragon.OriginalName.IndexOf(Search, StringComparison.OrdinalIgnoreCase) != -1
        || dragon.RomajiName.IndexOf(Search, StringComparison.OrdinalIgnoreCase) != -1;
}

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.

<TextBox Text="{Binding Search, UpdateSourceTrigger=PropertyChanged}" />
private string search;

public string Search
{
    get { return search; }
    set
    {
        search = value;
        NotifyPropertyChanged("Search");
        ItemsView.Refresh(); // required
    }
}

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.

2015-19-05 02-19-25-842

2015-20-05 02-20-46-590

It is possible to have the collection view refresh automatically, to achieve that you need to inherit from a collection view and change the logic there. In the example bellow I’m saying that if my model implements INotifyPropertyChanged and a property named “Search” triggers a change then it will refresh itself.

public class NotifiableCollectionView : ListCollectionView
{
    public NotifiableCollectionView(IList sourceCollection, object model)
        : base(sourceCollection)
    {
        if (model is INotifyPropertyChanged)
            (model as INotifyPropertyChanged).PropertyChanged += NotifiableCollectionView_PropertyChanged;
    }

    void NotifiableCollectionView_PropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        if (e.PropertyName == "Search")
            this.Refresh();
    }
}

Our ICollectionView will be a NotifiableCollectionView instead of the default.

private ICollectionView itemsView;

public ICollectionView ItemsView
{
    get
    {
        if (itemsView == null)
        {
            itemsView = new NotifiableCollectionView(Items, this);
        }
        return itemsView;
    }
}

So we can remove the refresh call:

private string search;

public string Search
{
    get { return search; }
    set
    {
        search = value;
        NotifyPropertyChanged("Search");
        // ItemsView.Refresh(); // no longer required
    }
}

And if we add Fody then we can simplify to:

public string Search { get; set; }
Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s