Попытка отфильтровать связанную коллекцию ObservableCollection в комбинированный список на основе другого значения ComboBox, не работающего - PullRequest
0 голосов
/ 29 октября 2018

Я вижу несколько других сообщений об этом, но я не могу точно понять, как заставить это работать должным образом для моего использования.

Вот что у меня в двух словах.

У меня есть два комбобокса - роль и положение.

У меня есть оба эти элемента, привязанные к коллекции ObservableCollection, значения Enum которой преобразованы в строки, загруженные в нее при создании экземпляра.

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

Я хочу, чтобы при выборе Роли на основе этого выбора отображались только определенные Позиции. Например, если я выбираю «Обслуживание клиентов» в качестве роли, то позиция должна содержать только «Менеджер», «CSS» и «Нет». Если роль - «Администратор», то позиция должна содержать только «Нет» и т. Д. И т. Д.

Моя борьба заключается в том, как правильно отфильтровать это. Я вижу что-то с использованием CollectionViewSource, но я не уверен, как заставить это работать с моим примером.
У меня есть 5 ролей, и у каждой роли будет свой список должностей, которые нужно показать.

Каков наилучший способ заставить эту работу работать с дополнительным кодом MINIMAL или XAML?

Одна из вещей, которые мне действительно не нравятся в WPF, это то, что, казалось бы, простые вещи требуют огромного количества кода, чтобы они много раз работали правильно.

Ответы [ 3 ]

0 голосов
/ 29 октября 2018

Все это можно сделать в вашей ViewModel, изменив значение наблюдаемой коллекции Positions (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);
    }
}
0 голосов
/ 29 октября 2018

Вот рабочий код тестера, чтобы увидеть основную идею о том, как выполнить фильтрацию:

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

Надеюсь, это поможет ..

0 голосов
/ 29 октября 2018

Во-первых, если вы считаете, что WPF сложен.Итак, вы используете его неправильно.

Я предлагаю вам использовать Фильтр из CollectionViewSource в качестве потока:

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

Фильтр не изменяет вашу коллекцию, все скрытые элементы остаются в вашей коллекции ObservableCollection.

...