Click here to Skip to main content
15,879,348 members
Please Sign up or sign in to vote.
0.00/5 (No votes)
See more:
I have two comboboxes

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

which I'm filling with entries in the C# code
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");


What I want to do now is, that the moment you select one entry in one ComboBox, the same entry in the other isn't selectable anymore. I try to achieve this by adding an eventhandler to them.

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


The problem I find myself facing is, that I don't find a single command that would let me deactivate single entries in the combobox. How would I go about achieving that?

What I have tried:

Searching different platforms, going through all options/commands given to me by the programm. Haven't explicitly tested anything since nothing seemed to suit what I want/need to do.
Posted
Updated 9-Jan-19 2:40am
v2

Here is a MVVM solution that disables items in the other Combobox when a selection is made in one...

First the Data Binding event plumbing base classes:
C#
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;
}

Next a Model representing the data:
C#
public class PersonModel : ObservableBase, IDataModel
{
    private string name;
    public string Name
    {
        get => name;
        set => Set(ref name, value);
    }
}

Next a wrapper ViewModel for the Model to represent the state of the item. This is used to enable/disable ComboBox items.
C#
public class ItemViewModel : ViewModelBase
{
    public IDataModel Model { get; set; }

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

Now the MainViewModel to handle the data and Selection/State changes...
C#
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;
            }
        }
    }
}

Last, the View (UI).
XML
<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>

UPDATE: It's beed suggested that the MVVM solution might be a bit complicated for the OP. So below I have adapted the solution as a simplified code-behind version.

As we are still using Data Binding, it is wise to implement the INotifyPropertyChanged interface required for notifying the data binding system of changes:
C#
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;
}

Next we need a data model to hold both the data and the Enabled state for each item in the ComboBox:
C#
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);
    }
}

Now we can add the code to the code-behind to initialse the data and to handle the SelectionChanged event for each ComboBox:
C#
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;
            }
        }
    }
}

Lastly, the XAML. Here I've named the ComboBoxes, like in the original post, for hooking up the SelectionChanged event. Apart from that, the XAML is identical to the MVVM solution above:
XML
<Window x:Class="ComboBoxDisableItem2.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="450" Width="800">
    <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 x:Name="ComboBoxA"
                  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 Name}"/>
                    </StackPanel>
                </DataTemplate>
            </ComboBox.ItemTemplate>
        </ComboBox>
        <ComboBox x:Name="ComboBoxB"
                  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 Name}"/>
                    </StackPanel>
                </DataTemplate>
            </ComboBox.ItemTemplate>
        </ComboBox>
    </Grid>
</Window>

Both solutions do the same thing.
 
Share this answer
 
v3
Comments
Maciej Los 9-Jan-19 9:04am    
Good job!
TheRealSteveJudge 9-Jan-19 9:50am    
5 stars. Hope the questioner is able to understand your code.
Graeme_Grant 9-Jan-19 20:13pm    
Fair comment... So I've updated the solution with a code-behind version using Data Binding.
First of all you must define a list of all possible ComboBox items.
Then you must implement event handlers for both ComboBoxes which set the available items according to the selection of the other ComboBox.

Please have a look at this example.

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
            });
        }
    }
}
 
Share this answer
 
v2
Comments
Maciej Los 9-Jan-19 7:41am    
5ed!
TheRealSteveJudge 9-Jan-19 8:07am    
Thank you, Maciej!
Graeme_Grant 9-Jan-19 8:54am    
How are you disabling items as per the scope of requirements in the question? You're physically removing the item and resetting the ItemSource. Have you checked if that deselects the selected item when changing the ItemSource?
TheRealSteveJudge 9-Jan-19 9:47am    
Did you try my solution?
Graeme_Grant 9-Jan-19 17:48pm    
No, I did not. Looking at your code you are removing items which is not meeting the criteria of "the same entry in the other isn't selectable anymore". Mind you, the question does lack clarity.

Also, your example does not wire up the events. You need to add the following to the constructor:
ComboBoxA.SelectionChanged += OnComboBoxASelectionChanged;
ComboBoxB.SelectionChanged += OnComboBoxBSelectionChanged;

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900