Search code examples
c#wpfribbon-control

Binding a RibbonComboBox to filtered ObservableCollection causing errors


Using C# I'm trying to filter a RibbonComboBox list using RibbonRadioButtons but I can't solve an error I keep receiving.

The list of countries is in an ObservableCollection which I'm filtering using ListCollectionView. With help from another user (C# WPF Filter ComboBox based on RadioButtons) I now have it partially working but if I click on a radio button the list in the ComboBox is updated but nothing is shown in the ComboBox; I expected the first item in the list to be displayed. If I select a country then click another continent from a RadioButton I receive the error shown below on the line 'public bool Africa {... CountryView.Refresh()}' or whichever button I clicked on. [Code updated 25 Sep to reflect comments.]

RibbonComboBox errors

Object reference not set to an instance of an object.
In VS Output window:
Error: 40 : BindingExpression path error: 'DisplayName' property not found on 'object'

When I changed the RibbonComboBox to a ComboBox in the XAML as shown below it does seem to work correctly though it generates another error. However I would prefer to use the RibbonComboBox but not sure how to resolve the problem. Any help you can give to get it working would be appreciated.

XAML

<Grid>
    <DockPanel>
        <r:Ribbon DockPanel.Dock="Top" x:Name="Ribbon">
            <r:RibbonGroup Header="Continent" Width="260">
                <!--<ComboBox x:Name="CountryList" Width="100" ItemsSource="{Binding CountryView}" SelectedItem="{Binding SelectedCountry}" DisplayMemberPath="DisplayName"/>-->
                <r:RibbonComboBox x:Name="CountryList" Height="Auto" SelectionBoxWidth="230" VerticalAlignment="Center">
                    <r:RibbonGallery x:Name="cbSelectedCountry" SelectedValue="{Binding SelectedCountry, Mode=TwoWay}" SelectedValuePath="DisplayName" >
                        <r:RibbonGalleryCategory x:Name="cbCountryList" ItemsSource="{Binding CountryView}" DisplayMemberPath="DisplayName" />
                    </r:RibbonGallery>
                </r:RibbonComboBox>
                <WrapPanel>
                    <r:RibbonRadioButton x:Name="All" Label="All" GroupName="ContinentGroup" Height="Auto" Width="Auto" HorizontalAlignment="Left" IsChecked="{Binding Path=All}">
                    </r:RibbonRadioButton>
                    <r:RibbonRadioButton x:Name="Africa" Label="Africa" GroupName="ContinentGroup" Height="Auto" Width="Auto" HorizontalAlignment="Left" IsChecked="{Binding Path=Africa}">
                    </r:RibbonRadioButton>
                    <r:RibbonRadioButton x:Name="America" Label="America" GroupName="ContinentGroup" Height="Auto" Width="Auto" HorizontalAlignment="Left" IsChecked="{Binding Path=America}">
                    </r:RibbonRadioButton>
                </WrapPanel>
            </r:RibbonGroup>
        </r:Ribbon>
    </DockPanel>
</Grid>

C# Code behind (DataContext):

using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Windows.Data;

public class MySettings : INotifyPropertyChanged
{
    private readonly ObservableCollection<Country> countries;
    private ContinentViewModel selectedContinent;
    private static string selectedCountry;
    private int selectedRadioGroup;
    private ObservableCollection<ContinentViewModel> continents;
    private ListCollectionView countryView;
    public event PropertyChangedEventHandler PropertyChanged;
    private bool _All;
    private bool _Africa;
    private bool _America;

    public bool All { get => _All; set { _All = value; CountryView.Refresh(); SelectedCountry = countries[0].ToString(); } }
    public bool Africa { get => _Africa; set { _Africa = value; CountryView.Refresh(); SelectedCountry = countries[0].ToString(); } }
    public bool America { get => _America; set { _America = value; CountryView.Refresh(); SelectedCountry = countries[0].ToString(); } }

    public MySettings()
    {
        countries = new ObservableCollection<Country>(
            new[]
            {
                new Country() { Continent = Continent.Africa, DisplayName = "Algeria" },
                new Country() { Continent = Continent.Africa, DisplayName = "Egypt" },
                new Country() { Continent = Continent.Africa, DisplayName = "Chad" },
                new Country() { Continent = Continent.Africa, DisplayName = "Ghana" },
                new Country() { Continent = Continent.America, DisplayName = "Canada" },
                new Country() { Continent = Continent.America, DisplayName = "Greenland" },
                new Country() { Continent = Continent.America, DisplayName = "Haiti" }
            });
        CountryView = (ListCollectionView)CollectionViewSource.GetDefaultView(countries);
        CountryView.Filter += CountryFilter;
        Continents = new ObservableCollection<ContinentViewModel>(Enum.GetValues(typeof(Continent)).Cast<Continent>().Select(c => new ContinentViewModel { Model = c }));
    }

    private bool CountryFilter(object obj)
    {
        var country = obj as Country;
        if (country == null) return false;
        if (All) return true;
        if (Africa) return country.Continent == Continent.Africa;
        if (America) return country.Continent == Continent.America;
        return true;
    }

    public ObservableCollection<ContinentViewModel> Continents
    {
        get { return continents; }
        set
        {
            continents = value;
        }
    }

    public ListCollectionView CountryView
    {
        get { return countryView; }
        set
        {
            countryView = value;
        }
    }

    public class Country
    {
        public string DisplayName { get; set; }
        public Continent Continent { get; set; }
    }

    public enum Continent
    {
        All,
        Africa,
        America
    }

    public class ContinentViewModel
    {
        public Continent Model { get; set; }
        public string DisplayName
        {
            get
            {
                return Enum.GetName(typeof(Continent), Model);
            }
        }
    }

    public ContinentViewModel SelectedContinent
    {
        get { return selectedContinent; }
        set
        {
            selectedContinent = value;
            OnContinentChanged();
            this.OnPropertyChanged("SelectedContinent");
        }
    }

    private void OnContinentChanged()
    {
        CountryView.Refresh();
    }

    public int SelectedRadioGroup
    {
        get { return selectedRadioGroup; }
        set
        {
            selectedRadioGroup = value;
        }
    }

    public string SelectedCountry
    {
        get { return selectedCountry; }
        set
        {
            if (selectedCountry == value) return;
            selectedCountry = value;
        }
    }

    protected virtual void OnPropertyChanged(string propertyName)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

Solution

  • I have modified your class to solve your issues. The changes in code is:

    1. Instead of CountryView = new ListCollectionView(countries) do CountryView = (ListCollectionView)CollectionViewSource.GetDefaultView(countries)
    2. On every refresh of your list i.e on checking of the CheckBox set the SelectedItem of your ComboBox i.e SelectedCountry in your case as following : SelectedCountry = _All ? countries.FirstOrDefault().DisplayName : SelectedCountry; SelectedCountry = _Africa ? countries.Where(_ => _.Continent == Continent.Africa).FirstOrDefault().DisplayName : SelectedCountry; SelectedCountry = _America ? countries.Where(_ => _.Continent == Continent.America).FirstOrDefault().DisplayName : SelectedCountry;
    3. Call OnPropertyChanged for all the properties.

      private readonly ObservableCollection<Country> countries;
      private ContinentViewModel selectedContinent;
      private static string selectedCountry;
      private int selectedRadioGroup;
      private ObservableCollection<ContinentViewModel> continents;
      private ListCollectionView countryView;
      public event PropertyChangedEventHandler PropertyChanged;
      private bool _All;
      private bool _Africa;
      private bool _America;
      public bool All
      {
          get
          {
              return _All;
          }
          set
          {
              _All = value;
              CountryView.Refresh();
              SelectedCountry = _All ? countries.FirstOrDefault().DisplayName : SelectedCountry;
              OnPropertyChanged("All");
          }
      }
      
      public bool Africa
      {
          get
          {
              return _Africa;
          }
          set
          {
              _Africa = value;
              CountryView.Refresh();
              SelectedCountry = _Africa ? countries.Where(_ => _.Continent == Continent.Africa).FirstOrDefault().DisplayName : SelectedCountry;
              OnPropertyChanged("Africa");
          }
      }
      
      public bool America
      {
          get
          {
              return _America;
          }
          set
          {
              _America = value;
              CountryView.Refresh();
              SelectedCountry = _America ? countries.Where(_ => _.Continent == Continent.America).FirstOrDefault().DisplayName : SelectedCountry;
              OnPropertyChanged("America");
          }
      }
      
      public MySettings()
      {
          countries = new ObservableCollection<Country>(
              new[]
              {
              new Country() { Continent = Continent.Africa, DisplayName = "Algeria" },
              new Country() { Continent = Continent.Africa, DisplayName = "Egypt" },
              new Country() { Continent = Continent.Africa, DisplayName = "Chad" },
              new Country() { Continent = Continent.Africa, DisplayName = "Ghana" },
              new Country() { Continent = Continent.America, DisplayName = "Canada" },
              new Country() { Continent = Continent.America, DisplayName = "Greenland" },
              new Country() { Continent = Continent.America, DisplayName = "Haiti" }
              });
          CountryView = (ListCollectionView)CollectionViewSource.GetDefaultView(countries);
          CountryView.Filter += CountryFilter;
          Continents = new ObservableCollection<ContinentViewModel>(Enum.GetValues(typeof(Continent)).Cast<Continent>().Select(c => new ContinentViewModel { Model = c }));
      }
      
      private bool CountryFilter(object obj)
      {
          var country = obj as Country;
          if (country == null) return false;
          if (All && !Africa && !America) return true;
          else if (!All && Africa && !America) return country.Continent == Continent.Africa;
          else if (!All && !Africa && America) return country.Continent == Continent.America;
          return true;
      }
      
      public ObservableCollection<ContinentViewModel> Continents
      {
          get { return continents; }
          set
          {
              continents = value;
              OnPropertyChanged("Continents");
          }
      }
      
      public ListCollectionView CountryView
      {
          get { return countryView; }
          set
          {
              countryView = value;
              OnPropertyChanged("CountryView");
          }
      }
      
      public class Country
      {
          public string DisplayName { get; set; }
          public Continent Continent { get; set; }
      }
      
      public enum Continent
      {
          All,
          Africa,
          America
      }
      
      public class ContinentViewModel
      {
          public Continent Model { get; set; }
          public string DisplayName
          {
              get
              {
                  return Enum.GetName(typeof(Continent), Model);
              }
          }
      }
      
      public ContinentViewModel SelectedContinent
      {
          get { return selectedContinent; }
          set
          {
              selectedContinent = value;
              OnContinentChanged();
              this.OnPropertyChanged("SelectedContinent");
          }
      }
      
      private void OnContinentChanged()
      {
          CountryView.Refresh();
      }
      
      public int SelectedRadioGroup
      {
          get { return selectedRadioGroup; }
          set
          {
              selectedRadioGroup = value;
              OnPropertyChanged("SelectedRadioGroup");
          }
      }
      
      public string SelectedCountry
      {
          get { return selectedCountry; }
          set
          {
              if (selectedCountry == value) return;
              selectedCountry = value;
              OnPropertyChanged("SelectedCountry");
          }
      }
      
      protected virtual void OnPropertyChanged(string propertyName)
      {
          PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
      }