0

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));
    }
}
Leo371
  • 3
  • 4
  • Try creating `ListCollectionView` as `CountryView = CollectionViewSource.GetDefaultView(countries);` – Aakanksha Sep 24 '19 at 11:42
  • I had to cast it as `CountryView = (ListCollectionView)CollectionViewSource.GetDefaultView(countries);` and it does prevent the main error. However I now get error `System.Windows.Data Error: 4 : Cannot find source for binding with reference 'RelativeSource FindAncestor, AncestorType='System.Windows.Controls.ItemsControl', AncestorLevel='1''. BindingExpression:Path=HorizontalContentAlignment; DataItem=null; target element is 'RibbonGalleryItem' (Name=''); target property is 'HorizontalContentAlignment' (type 'HorizontalAlignment')`. This is the same message I get when using a ComboBox. – Leo371 Sep 24 '19 at 12:56
  • Also when I click a radio button it displays `MyNamespace.MySettings+Country` instead of the first option though the countries do appear in the drop-down menu. – Leo371 Sep 24 '19 at 13:17
  • You have binded SelectedCountry to the SelectedItem of the ComboBox, cant see the property in Code Behind. Regarding one more error you are getting i cannot see any code for it, please ask it as a separate question. – Aakanksha Sep 24 '19 at 14:20
  • I had `private string selectedCountry;` in my original code. I've now included it in the code above. – Leo371 Sep 24 '19 at 14:31
  • Your `SelectedCountry` property is string, so you should set the `SelectedValue` property of `ComboBox` to `SelectedCountry` and `SelectedValuePath` to `DisplayName`. – Aakanksha Sep 24 '19 at 14:39
  • I changed the line to read `` but it hasn't made any difference. – Leo371 Sep 25 '19 at 09:00
  • When you refresh your Collection, just update the SelectedCountry to the First item – Aakanksha Sep 25 '19 at 09:17
  • Can you explain how? I tried `public bool Africa { ... set {... CountryView.Refresh(); SelectedCountry = countries[0].ToString(); } }` but it made no difference. – Leo371 Sep 25 '19 at 09:41
  • Ideally it should work, maybe something else is wrong in the your code – Aakanksha Sep 25 '19 at 10:10
  • I've updated the original code with my latest code to enable this to be copied into a new project to reproduce the problem. If I select a country in the combobox then click on another radio button the combobox displays `namespace.MySettings+Country` unless the selected country exists in the selected list. – Leo371 Sep 25 '19 at 13:15

1 Answers1

0

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));
    }
    
Aakanksha
  • 329
  • 2
  • 7
  • Thank you for your help. There's still a problem though with the combobox still showing `namespace.MySettings+Country`. I copied your code into my project and deleted all other code. If you run the code, select a country (say Algeria), click Africa (it correctly shows Algeria), click America then it displays `namespace.MySettings+Country`. It seems that if the selected country isn't in the filtered list the combobox doesn't show the first item on the filtered list. However if I click America again it correctly show Canada. – Leo371 Sep 26 '19 at 09:51
  • Can you try the updated answer. Just changed logic in filter condition – Aakanksha Sep 26 '19 at 10:50
  • That worked perfectly. Thank you very much for all your help and patience, it's much appreciated. – Leo371 Sep 26 '19 at 12:51