Search code examples
wpfmvvmcomboboxitemssource

COMBOBOX filtering in WPF with MVVM


I am developing an application using WPF mvvm approach. I have a requirement where I have to show a list of items in a combo box for selection. Based on some flag I need to filter out few items from the combo box for selection.

I tried to use two different items sources one with full list and another with filtered list and based on the flag I wanted to change the items source. This does not seem to be working well. Is there any easy way to apply filters on the existing list based on some flag ?


Solution

  • There are lots of different ways to do this but my personal preference is to use a ListCollectionView as the ItemsSource of the control displaying the filtered list, to set a filter predicate on ListCollectionView.Filter and to call ListCollectionView.Refresh when the filter parameters change.

    The example below will filter a list of countries based on their continent.

    Code

    using System.Collections.ObjectModel;
    using System.ComponentModel;
    using System.Windows.Data;
    
    public class FilteringViewModel : INotifyPropertyChanged
    {
        private ObservableCollection<Country> _countries;
        private ContinentViewModel _selectedContinent;
    
        public ListCollectionView CountryView { get; set; }
        public event PropertyChangedEventHandler PropertyChanged;
        public ObservableCollection<ContinentViewModel> Continents { get; set; } 
    
        public FilteringViewModel()
        {
            _countries =
                new ObservableCollection<Country>(
                    new[]
                        {
                            new Country() { Continent = Continent.Africa, DisplayName = "Zimbabwe" },
                            new Country() { Continent = Continent.Africa, DisplayName = "Egypt" },
                            new Country() { Continent = Continent.Europe, DisplayName = "United Kingdom" }
                        });
            CountryView = new ListCollectionView(_countries);
            CountryView.Filter = o => _selectedContinent == null || ((Country)o).Continent == _selectedContinent.Model;
    
            Continents = new ObservableCollection<ContinentViewModel>(Enum.GetValues(typeof(Continent)).Cast<Continent>().Select(c => new ContinentViewModel { Model = c}));
        }
    
        public ContinentViewModel SelectedContinent
        {
            get
            {
                return _selectedContinent;
            }
            set
            {
                _selectedContinent = value;
                OnContinentChanged();
                this.OnPropertyChanged("SelectedContinent");
            }
        }
    
        private void OnContinentChanged()
        {
            CountryView.Refresh();
        }
    
        protected virtual void OnPropertyChanged(string propertyName)
        {
            PropertyChangedEventHandler handler = this.PropertyChanged;
            if (handler != null)
            {
                handler(this, new PropertyChangedEventArgs(propertyName));
            }
        }
    }
    
    public class Country
    {
        public string DisplayName { get; set; }
        public Continent Continent { get; set; }
    }
    
    public enum Continent
    {
        [Description("Africa")]
        Africa,
        Asia,
        Europe,
        America
    }
    
    public class ContinentViewModel
    {
        public Continent Model { get; set; }
        public string DisplayName
        {
            get
            {
                return Enum.GetName(typeof(Continent), Model);
            }
        }
    }
    

    XAML

    <StackPanel Orientation="Vertical">
        <ComboBox ItemsSource="{Binding Continents}" SelectedItem="{Binding SelectedContinent}" DisplayMemberPath="DisplayName" />
        <ListBox ItemsSource="{Binding CountryView}" DisplayMemberPath="DisplayName" />
    </StackPanel>