尝试将绑定的 ObservableCollection 过滤到基于另一个组合框值的组合框不起作用

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

我看到其他几篇关于此的帖子,但我似乎无法确切地理解如何让它正常工作以供我使用。

这是我的简述。

我有两个组合框——角色和位置。

我将这两个绑定到一个 ObservableCollection,它在实例化时将枚举值转换为字符串加载到其中。

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

在我的 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);
    }
}

我想要发生的是,当角色被 selected 时,基于那个 selection,应该只显示某些职位。例如,如果我 select "Customer Service" 作为一个角色,那么 Position 应该只包含 "Manager"、"CSS" 和 "None"。如果 Role 是 "Admin",那么 Position 应该只包含 "None",依此类推。

我遇到的问题是如何正确过滤它。我在使用 CollectionViewSource 时看到了一些东西,但我不确定如何让它与我的示例一起工作。
我有 5 个角色,每个角色都有不同的职位列表需要显示。

使用 MINIMAL 额外代码或 XAML 使其工作的最佳方法是什么?

我非常不喜欢 WPF 的一个原因是看似简单的事情需要大量的代码才能使它们多次正常工作。

首先,如果你觉得WPF很复杂。所以,你用错了。

我建议你使用 CollectionViewSourceFilter 作为流:

<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);
    };
}

过滤器不会更改您的 Collection,所有隐藏项都保留在您的 Observable 中Collection。

这一切都可以在您的 ViewModel 中完成,方法是在角色更改时更改位置 (Pos) 可观察集合的值。

class EmployeeMenuVM : EmployeeMenuVMBase
{
    public EmployeeMenuVM()
    {
        var emptyPositions = new List<Global.Positions>()
        { Global.Positions.None };

        _rolePositions.Add(Global.Roles.None, emptyPositions);

        var customerServicePositions = new List<Global.Positions>()
        { Global.Positions.None, Global.Positions.CSS, Global.Positions.Manager };

        _rolePositions.Add(Global.Roles.CustomerService, customerServicePositions);
    }

    private Dictionary<Global.Roles, List<Global.Positions>> _rolePositions = new Dictionary<Global.Roles, List<Global.Positions>>();

    private string _roleStr;
    private string _posStr;

    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;

        set
        {
            SetProperty(ref _pos, value);
        }
    }
    public ObservableCollection<string> Role
    {
        get => _role;
    }
    public string RoleStr
    {
        get => _roleStr;
        set
        {
            if (SetProperty(ref _roleStr, value))
            {
                Global.Roles role = (Global.Roles)Enum.Parse(typeof(Global.Roles), value);
                var positions = _rolePositions[role].Select(p => p.ToString());
                Pos = new ObservableCollection<string>(positions);
            }
        }
    }
    public string PosStr
    {
        get => _posStr;
        set => SetProperty(ref _posStr, value);
    }
}

这是一个工作测试代码,只是为了了解如何进行过滤的主要思想:

MainWindow.xaml

<Window x:Class="WpfApplication3.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApplication3"
        x:Name="ThisView"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="600">
    <StackPanel Orientation="Horizontal">
        <ComboBox ItemsSource="{Binding Path=Roles, ElementName=ThisView}" 
                  SelectedItem="{Binding Path=SelectedRole, ElementName=ThisView}"
                  Width="300" Height="60"/>
        <ComboBox ItemsSource="{Binding Path=PositionCollectionView, ElementName=ThisView}" Width="300" Height="60"/>
    </StackPanel>
</Window>

MainWindow.xaml.cs

public partial class MainWindow : Window, INotifyPropertyChanged
    {
        public ICollectionView PositionCollectionView { get; set; }

        public ObservableCollection<string> Roles { get; set; } = new ObservableCollection<string>();
        public ObservableCollection<string> Positions { get; set; } = new ObservableCollection<string>();


        private string _selectedRole = String.Empty;

        public string SelectedRole
        {
            get { return _selectedRole; }
            set
            {
                _selectedRole = value;
                OnPropertyChanged();

                //This Refresh activates the Filter again, so that every time you select a role, this property will call it.
                PositionCollectionView.Refresh();

            }
        }

        public MainWindow()
        {
            PositionCollectionView = CollectionViewSource.GetDefaultView(Positions);
            PositionCollectionView.Filter = PositionsFilter;

            //use you enums here
            Roles.Add("Role1");
            Roles.Add("Role2");
            Roles.Add("Role3");
            Roles.Add("Role4");

            Positions.Add("Position1");
            Positions.Add("Position2");
            Positions.Add("Position3");
            Positions.Add("Position4");

            InitializeComponent();
        }

        private bool PositionsFilter(object position)
        {
            bool result = true;

            //place your code according to the Role selected to decide wheather "position" should be in the position list or not

            return result;
        }

        public event PropertyChangedEventHandler PropertyChanged;

        [NotifyPropertyChangedInvocator]
        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }

希望对您有所帮助..