Search code examples
c#wpfobservablecollectioncollectionviewsource

Trying to Filter a bound ObservableCollection to a combobox based on another ComboBox Value not working


I see several other posts about this but I cannot seem to understand exactly how to get this working properly for my usage.

Here is what I have in a nutshell.

I have two Comboboxes--Role and Position.

I have both of these bound to an ObservableCollection which has Enum Values Converted to strings loaded into it on instantiation.

<ComboBox  x:Name="empRoleCB" ItemsSource="{Binding Role}" SelectedItem="{Binding RoleStr}"/>
<ComboBox  x:Name="empPositionCB" ItemsSource="{Binding Pos}" SelectedItem="{Binding PosStr}"/>

In my ViewModel:

public abstract class EmployeeMenuVMBase : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    protected bool SetProperty<T>(ref T field, T newValue, [CallerMemberName] string propertyName = null)
    {
        if(!EqualityComparer<T>.Default.Equals(field, newValue))
        {
            field = newValue;
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
            return true;
        }
        return false;
    }
}

class EmployeeMenuVM : EmployeeMenuVMBase
{
    private ObservableCollection<string> _pos = new ObservableCollection<string>(Enum.GetNames(typeof(Global.Positions)));
    private ObservableCollection<string> _role = new ObservableCollection<string>(Enum.GetNames(typeof(Global.Roles)));
    public ObservableCollection<string> Pos { get => _pos; }
    public ObservableCollection<string> Role { get => _role; }
    public string RoleStr
    {
        get => _roleStr;
        set => SetProperty(ref _roleStr, value);
    }
    public string PosStr
    {
        get => _posStr;
        set => SetProperty(ref _posStr, value);
    }
}

What I want to happen is when a Role is selected, based on that selection, only certain Positions should be shown. For instance if I select "Customer Service" as a Role, then Position should only contain "Manager", "CSS" and "None". If Role is "Admin" then Position should only contain "None", and so on and so forth.

The struggle I have is how to filter this properly. I see something with using CollectionViewSource but I am unsure how to get this to work with my example.
I have 5 roles and each role will have a different list of positions that need to be shown.

What is the best way to make this work with MINIMAL extra code or XAML?

One of the things I really dislike about WPF is seemingly simple things need huge amounts of code to make them work properly many times.


Solution

  • First, if you think that WPF is complicated. So, you are using it wrongly.

    I suggest you to use the Filter of CollectionViewSource as flow:

    <ComboBox  x:Name="empPositionCB" ItemsSource="{Binding MyPositionFilter}" SelectionChanged="RoleComboBox_SelectionChanged" ....../>
    
    
    public ICollectionView MyPositionFilter { get; set; }
    
    //ctor
    public MyUserControlOrWindow()
    {
        //Before InitComponent()
        this.MyPositionFilter = new CollectionViewSource { Source = MyPosObservableCollection }.View;
    
    
        InitComponent();
    }
    
    public void RoleComboBox_SelectionChanged(object sender,EventArgs e)
    {
        //Get the selected Role (the ? is to prevent NullException (VS 2015 >))
        Role r = empRoleCB.SelectedItem as Role;
    
        //Apply the filter
        this.MyPositionFilter.Filter = item =>
        {
            //Make you sure to convert correcteley your Enumeration, I used it here like a class
            Position p = item as Position;
    
            //Put your condition here. For example:
            return r.ToLowers().Contains(p.ToLower());
    
            //Or
    
            return (r != null && r.Length >= p.Length);
        };
    }
    

    The filter does not change your Collection, All hidden item stay in your ObservableCollection.