Member 13783935 Ответов: 2

C# - условно деактивировать параметры из выпадающего списка


У меня есть два комбобокса

<ComboBox Name="ComboBoxA"/>
     <ComboBox Name="ComboBoxB"/>

который я заполняю записями в коде C#
ComboBoxA.SelectedIndex = 0;
     ComboBoxA.Items.Add("foo");
     ComboBoxA.Items.Add("123");
     ComboBoxA.Items.Add("abc");
     ComboBoxA.Items.Add("xyz");

     ComboBoxB.SelectedIndex = 0;
     ComboBoxB.Items.Add("foo");
     ComboBoxB.Items.Add("123");
     ComboBoxB.Items.Add("abc");
     ComboBoxB.Items.Add("xyz");


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

ComboBoxA.SelectionChanged += OnComboBoxASelectionChanged;
     ComboBoxB.SelectionChanged += OnComboBoxBSelectionChanged;


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

Что я уже пробовал:

Поиск на разных платформах, просмотр всех опций/команд, предоставленных мне программой. Я ничего явно не тестировал, так как ничто, казалось, не соответствовало тому, что я хочу/должен делать.

2 Ответов

Рейтинг:
18

Graeme_Grant

Вот решение MVVM, которое отключает элементы в другом Combobox, когда выбор сделан в одном...

Во первых привязка данных событие сантехника базовые классы:

public abstract class ObservableBase : INotifyPropertyChanged
{
    public void Set<TValue>(ref TValue field, TValue newValue,
                            [CallerMemberName] string propertyName = "")
    {
        if (!EqualityComparer<TValue>.Default.Equals(field, default(TValue))
            && field.Equals(newValue)) return;

        field = newValue;
        RaisePropertyChanged(propertyName);
    }

    public void RaisePropertyChanged(string propertyName)
        => PropertyChanged?.Invoke(this,
               new PropertyChangedEventArgs(propertyName));

    public event PropertyChangedEventHandler PropertyChanged;
}

public abstract class ViewModelBase : ObservableBase
{
    public bool IsInDesignMode
        => (bool)DesignerProperties.IsInDesignModeProperty
            .GetMetadata(typeof(DependencyObject))
            .DefaultValue;
}

Далее модель, представляющая данные:
public class PersonModel : ObservableBase, IDataModel
{
    private string name;
    public string Name
    {
        get => name;
        set => Set(ref name, value);
    }
}

Затем создается оболочка ViewModel для модели, представляющей состояние элемента. Это используется для включения/отключения элементов ComboBox.
public class ItemViewModel : ViewModelBase
{
    public IDataModel Model { get; set; }

    private bool isEnabled = true;
    public bool IsEnabled
    {
        get => isEnabled;
        set => Set(ref isEnabled, value);
    }
}

Теперь MainViewModel для обработки данных и выбора/изменения состояния...
public class MainViewModel : ViewModelBase
{
    public ObservableCollection<ItemViewModel> List1 { get; }
        = new ObservableCollection<ItemViewModel>();

    public ObservableCollection<ItemViewModel> List2 { get; }
        = new ObservableCollection<ItemViewModel>();

    private ItemViewModel list1SelectedItem;
    public ItemViewModel List1SelectedItem
    {
        get => list1SelectedItem;
        set
        {
            Set(ref list1SelectedItem, value);
            UpdateSelections(List1, value);
        }
    }

    private ItemViewModel list2SelectedItem;
    public ItemViewModel List2SelectedItem
    {
        get => list2SelectedItem;
        set
        {
            Set(ref list2SelectedItem, value);
            UpdateSelections(List2, value);
        }
    }

    public MainViewModel() => InitData();

    private void InitData()
    {
        for (int i = 0; i < 10; i++)
        {
            var model = new PersonModel()
            {
                Name = "Person " + i,
            };

            List1.Add(new ItemViewModel()
            {
                Model = model
            });
            List2.Add(new ItemViewModel()
            {
                Model = model
            });
        }
    }

    private void UpdateSelections(IList<ItemViewModel> list, ItemViewModel itemVM)
    {
        var dest = list.Equals(List1) ? List2 : List1;
        if (itemVM == null)
        {
            foreach (var item in dest)
            {
                item.IsEnabled = true;
            }
        }
        else
        {
            foreach (var item in dest)
            {
                item.IsEnabled = item.Model.Equals(itemVM.Model) != true;
            }
        }
    }
}

Наконец, представление (интерфейс).
<Window x:Class="ComboBoxDisableItem.MainWindow"

        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

        xmlns:l="clr-namespace:ComboBoxDisableItem"

        Title="MainWindow" Height="450" Width="800">
    <Window.DataContext>
        <l:MainViewModel/>
    </Window.DataContext>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        <Grid.Resources>
            <Style TargetType="ComboBox">
                <Setter Property="Width" Value="200"/>
                <Setter Property="Margin" Value="10"/>
            </Style>
        </Grid.Resources>
        <ComboBox ItemsSource="{Binding List1}"

                  SelectedItem="{Binding List1SelectedItem}">
            <ComboBox.ItemContainerStyle>
                <Style>
                    <Style.Triggers>
                        <DataTrigger Binding ="{Binding IsEnabled}" Value="False">
                            <Setter Property="ComboBoxItem.Focusable" Value="False"/>
                            <Setter Property="ComboBoxItem.IsEnabled" Value="False"/>
                        </DataTrigger>
                    </Style.Triggers>
                </Style>
            </ComboBox.ItemContainerStyle>
            <ComboBox.ItemTemplate>
                <DataTemplate>
                    <StackPanel Orientation="Horizontal">
                        <TextBlock Text="{Binding Model.Name }"/>
                    </StackPanel>
                </DataTemplate>
            </ComboBox.ItemTemplate>
        </ComboBox>
        <ComboBox ItemsSource="{Binding List2}"

                  SelectedItem="{Binding List2SelectedItem}"

                  Grid.Row="1">
            <ComboBox.ItemContainerStyle>
                <Style>
                    <Style.Triggers>
                        <DataTrigger Binding ="{Binding IsEnabled}" Value="False">
                            <Setter Property="ComboBoxItem.Focusable" Value="False"/>
                            <Setter Property="ComboBoxItem.IsEnabled" Value="False"/>
                        </DataTrigger>
                    </Style.Triggers>
                </Style>
            </ComboBox.ItemContainerStyle>
            <ComboBox.ItemTemplate>
                <DataTemplate>
                    <StackPanel Orientation="Horizontal">
                        <TextBlock Text="{Binding Model.Name }"/>
                    </StackPanel>
                </DataTemplate>
            </ComboBox.ItemTemplate>
        </ComboBox>
    </Grid>
</Window>

ОБНОВЛЕНИЕ: Было высказано предположение, что решение MVVM может быть немного сложным для OP. Поэтому ниже я адаптировал это решение как упрощенную версию кода.

Поскольку мы все еще используем привязку данных, разумно реализовать INotifyPropertyChanged интерфейс, необходимый для уведомления системы привязки данных об изменениях:
public abstract class ObservableBase : INotifyPropertyChanged
{
    public void Set<TValue>(ref TValue field, TValue newValue,
                            [CallerMemberName] string propertyName = "")
    {
        if (!EqualityComparer<TValue>.Default.Equals(field, default(TValue))
            && field.Equals(newValue)) return;

        field = newValue;
        RaisePropertyChanged(propertyName);
    }

    public void RaisePropertyChanged(string propertyName)
        => PropertyChanged?.Invoke(this,
               new PropertyChangedEventArgs(propertyName));

    public event PropertyChangedEventHandler PropertyChanged;
}

Далее нам нужна модель данных для хранения Как данных, так и включенного состояния для каждого элемента в выпадающем списке:
public class PersonModel : ObservableBase
{
    private string name;
    public string Name
    {
        get => name;
        set => Set(ref name, value);
    }

    private bool isEnabled = true;
    public bool IsEnabled
    {
        get => isEnabled;
        set => Set(ref isEnabled, value);
    }
}

Теперь мы можем добавить код в code-behind для инициализации данных и обработки события SelectionChanged для каждого ComboBox:
public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();

        InitData();
        DataContext = this;

        ComboBoxA.SelectionChanged += OnComboBoxSelectionChanged;
        ComboBoxB.SelectionChanged += OnComboBoxSelectionChanged;
    }

    public ObservableCollection<PersonModel> List1 { get; }
        = new ObservableCollection<PersonModel>();

    public ObservableCollection<PersonModel> List2 { get; }
        = new ObservableCollection<PersonModel>();

    private void InitData()
    {
        for (int i = 0; i < 10; i++)
        {
            var model = new PersonModel()
            {
                Name = "Person " + i,
            };

            List1.Add(model);
            List2.Add(model);
        }
    }

    private void OnComboBoxSelectionChanged(object sender,
        SelectionChangedEventArgs e)
    {
        var src = sender as ComboBox;
        var items = src.ItemsSource as IList<PersonModel>;
        var selected = src.SelectedItem as PersonModel;
        UpdateSelections(items, selected);
    }

    private void UpdateSelections(IList<PersonModel> list, PersonModel person)
    {
        var dest = list.Equals(List1) ? List2 : List1;
        if (person == null)
        {
            foreach (var item in dest)
            {
                item.IsEnabled = true;
            }
        }
        else
        {
            foreach (var item in dest)
            {
                item.IsEnabled = item.Equals(person) != true;
            }
        }
    }
}

Наконец, XAML. Здесь я назвал ComboBoxes, как и в оригинальном посте, для подключения события SelectionChanged. Кроме того, XAML идентичен приве


Maciej Los

Молодец!

TheRealSteveJudge

5 звезд. Надеюсь, что спрашивающий сможет понять ваш код.

Graeme_Grant

Справедливое замечание... Поэтому я обновил решение с помощью кодовой версии с использованием привязки данных.

Рейтинг:
11

TheRealSteveJudge

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

Пожалуйста, взгляните на этот пример.

Файл MainWindow.язык XAML

<Window x:Class="ComboBoxTest.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:ComboBoxTest"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <StackPanel Orientation="Horizontal">
            <ComboBox Name="ComboBoxA" Height="25" Width="150" Margin="5" SelectionChanged="OnComboBoxASelectionChanged"></ComboBox>
            <ComboBox Name="ComboBoxB" Height="25" Width="150" Margin="5" SelectionChanged="OnComboBoxBSelectionChanged"></ComboBox>
        </StackPanel>
    </Grid>
</Window>

Файл MainWindow.язык XAML.в CS
using System.Collections.Generic;
using System.Linq;
using System.Windows.Controls;

namespace ComboBoxTest
{
    public partial class MainWindow
    {
        readonly List<object> allItems = new List<object>
        {
            "foo",
            "123",
            "abc",
            "xyz"
        };        

        public MainWindow()
        {
            InitializeComponent();

            ComboBoxA.ItemsSource = allItems;
            ComboBoxB.ItemsSource = allItems;
        }

        private void OnComboBoxASelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            ComboBoxB.ItemsSource = allItems.Except(new List<object>
            {
                ComboBoxA.SelectedItem
            });
        }

        private void OnComboBoxBSelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            ComboBoxA.ItemsSource = allItems.Except(new List<object>
            {
                ComboBoxB.SelectedItem
            });
        }
    }
}


Maciej Los

5ed!

TheRealSteveJudge

Спасибо тебе, Мацей!

Graeme_Grant

Как вы отключаете элементы в соответствии с областью требований в вопросе? Вы физически удаляете элемент и сбрасываете ItemSource. Проверяли ли вы, отменяет ли это выбор выбранного элемента при изменении ItemSource?

TheRealSteveJudge

Вы пробовали мое решение?

Graeme_Grant

Нет, я этого не делал. Глядя на свой код, вы удаляете элементы, которые не соответствуют критериям "одна и та же запись в другом больше не выбирается". Имейте в виду, что вопрос действительно лишен ясности.

Кроме того, ваш пример не связывает события. Вам нужно добавить в конструктор следующее:

ComboBoxA.SelectionChanged += OnComboBoxASelectionChanged;
ComboBoxB.SelectionChanged += OnComboBoxBSelectionChanged;

TheRealSteveJudge

Вы почти правы.
Обработчики событий определены в файле MainWindow.xaml.
Я обновил решение.

Graeme_Grant

Это тоже можно сделать... ;)