5

I want to filter an ObservableCollection of Person object by name for my Xamarin Form application. The goal is to filter this ObservableCollection to just display a part of it.

Here is my Person object class :

public class Person
{
    public string Name { get; set; }
    public string Address { get; set; }
    public string Phone { get; set; }
}

I tried to make a filter like this :

private ObservableCollection<Person> personItems = new ObservableCollection<Person>();

public ObservableCollection<Person> PersonItems
{
    get { return personItems; }
    set { personItems = value; OnPropertyChanged(); }
}

public void FilterPerson(string filter)
{
    if (string.IsNullOrWhiteSpace(filter))
    {
        PersonItems = personItems;
    }
    else 
    {
        PersonItems = personItems.Where((person) => person. Name.ToLower().Contains(filter));
        // Error here
    }
}

I have this error :

Cannot not explicitly convert type : 'System.Collections.Generic.IEnumerable' to 'System.Collections.ObjectModel.ObservableCollection

fandro
  • 4,833
  • 7
  • 41
  • 62

3 Answers3

3

Basically, there are two solutions:

  1. If your PersonsItems list is not huge, you may recreate a whole collection each time a new filter string arrives. You don't even need an ObservableCollection in this case (due to the fact that you don't change the collection itself, you change a reference to a collection). All the UI elements will be recreated in this case

    PersonItems = originalItems.Where((person) => person. Name.ToLower().Contains(filter)).ToList();
    
  2. If your PersonsItems list is big enough, the first solution is not an option. In this case you need to manually call Add/Remove methods on the objects that should be added/removed. ObservableCollection has an imperative API and it fires an event each time Add/Remove is called. This event, in turn, can be observed by the ItemsControl that will make corresponding UI changes. Linq has a declarative API. That's why u need to sync a list to show with a filtered list manually.

2
PersonItems = personItems.Where((person) => person. Name.ToLower().Contains(filter));

is returning IEnumerable.

replace it with PersonItems = new ObservableCollection<Person>(personItems.Where((person) => person.Name.ToLower().Contains(filter)));

You have to recreate the observable using the filtered results.

Dimitri
  • 6,923
  • 4
  • 35
  • 49
  • 4
    This completely beats the whole purpose of using ObservableCollection i.e. getting events out of it when things change so UI can react to the exact set of changes. – Matti Virkkunen Feb 09 '17 at 12:39
  • @Matti Virkkunen care to share your insights on alternative method instead of just downvoting an accepted answer? – Dimitri Feb 13 '17 at 14:23
  • 1
    You should never have to recreate your observable collection – Sam Debruyn Sep 28 '17 at 09:22
  • 1
    Sometimes updating the ObservableCollection is too expensive (high number of items bound to UI, UI flickers and/or becomes unresponsive) and recreation is the right approach. Besides, noone has provided a better way of filtering ObservableCollection (short of reverting to using ICollectionView) – Dimitri Oct 02 '17 at 12:26
1

To do this in the past I've used James Montemagno's ObservableRangeCollection and Grouping helper functions. You can find them in this plugin https://github.com/jamesmontemagno/mvvm-helpers

Richard Pike
  • 679
  • 1
  • 8
  • 22