Question

I've defined a ListView where each item is displayed with a read-only (i.e. selection-only, no TextBox) ComboBox (as per the ItemTemplate).

There is a given list of possible items that can be chosen from each of these combo boxes. The caveat, however, is that no item may be selected in two combo boxes at a time. In order to ensure this, once an item has been selected in one of the combo boxes, it must be removed from all other combo boxes (obviously except for the one where it is selected), and once it gets deselected, it must be added to all other combo boxes again.

Oh, and one more thing: The order of the visible items must not change compared to the complete list.

My question is: How can I achieve this behavior?

I have tried the three possible solutions that came to my mind:

  • I have written a new helper control class that takes the complete list of existing items and a list of excluded (used) items via bindings to the outside world, as well as a property for a selected item. I could include that control class in the ItemTemplate; the combo box within that template would then bind its ItemsSource property to an ItemsProvider property of the helper class that was supposed to tie together the list of existing items, the list of excluded items and the selected item and return a single enumeration of items for that particular combo box.
    However, I somehow got lost in all the update notifications about list changes; I fear I'd have to individually react to all combinations of NotifyCollectionChangedAction of the two input lists, and with the prospect of having a dozen update methods, I thought this cannot be the right way.
  • I changed the list of existing items to a list that stores a boolean along with each item, so I could flag each item as hidden or not. This would relieve me of the necessity to have a list of excluded items while maintaining the order of items, thereby reducing the aforementioned complexity of combined change notifications.
    Unfortunately, as the list itself doesn't change with that solution, I don't know how to have the dependency property infrastructure notify my ItemsSource property in my helper class.
  • I don't have to use the WPF with bindings way; I can do code-behind here, too. So, I tried iterating over all ListViewItems and retrieving the combo box for each of them to manually refresh the item lists. However, I couldn't find a good time to access the ListViewItems after their item template had been loaded. There seems to be no event for that situation, and ListView.ItemContainerGenerator is read-only, so even if ItemContainerGenerator were not a sealed class, I couldn't assign my own specialized ItemContainerGenerator that would create custom list view items where I could override OnApplyTemplate.
Was it helpful?

Solution

I would probably bind all ComboBoxes to different CollectionViews over the source collection which filter out the selected items of the other ComboBoxes. You also need to Refresh the views if the selections of the combo-boxes change.

OTHER TIPS

If you bind the Lists to different Lists in the ViewModel, and bind the selected item to trigger a method that changes those Lists, then you could get your result. Similar to the below.

Xaml of MainWindow.xaml:

<Window x:Class="ComboBox.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" >
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="180" />
            <ColumnDefinition Width="180" />
            <ColumnDefinition Width="180" />
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="26" />
        </Grid.RowDefinitions>
        <ComboBox Name="cboOne" Grid.Column="0" Grid.Row="0" ItemsSource="{Binding CboOneList, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" SelectedValue="{Binding CboOneValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"></ComboBox>
        <ComboBox Name="cboTwo" Grid.Column="1" Grid.Row="0" ItemsSource="{Binding CboTwoList, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" SelectedValue="{Binding CboTwoValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"></ComboBox>
        <ComboBox Name="cboThree" Grid.Column="2" Grid.Row="0" ItemsSource="{Binding CboThreeList, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" SelectedValue="{Binding CboThreeValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"></ComboBox>
    </Grid>
</Window>

Code-behind for MainWindow.xaml:

using System.Windows;
using System.Windows.Controls;

namespace ComboBox {
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window {
    public MainWindow() {
        InitializeComponent();

        this.DataContext = new ComboBoxViewModel();

    }

    private void cboOne_SelectionChanged(object sender, SelectionChangedEventArgs e) {

    }
}
}

ViewModel:

 using System;
 using System.Collections.Generic;
 using System.Linq;
 using System.Text;
 using System.ComponentModel;

 namespace ComboBox {
class ComboBoxViewModel : INotifyPropertyChanged {
    public event PropertyChangedEventHandler PropertyChanged;
    List<string> master = new List<string>() { "A", "B", "C", "D", "E", "F" };

    #region C'tor
    public ComboBoxViewModel() {
        RetrieveLists();
    }
    #endregion

    #region Methods
    protected void OnPropertyChanged(String propertyName) {
        PropertyChangedEventHandler handler = this.PropertyChanged;
        if(null != handler) {
            handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    public void RetrieveLists() {
        List<string> tempOne = (from a in master
                                where !a.Equals(CboTwoValue) && !a.Equals(CboThreeValue)
                                select a).ToList();
        CboOneList = tempOne;

        List<string> tempTwo = (from a in master
                                where !a.Equals(CboOneValue) && !a.Equals(CboThreeValue)
                                select a).ToList();
        CboTwoList = tempTwo;

        List<string> tempThree = (from a in master
                                where !a.Equals(CboTwoValue) && !a.Equals(CboOneValue)
                                select a).ToList();
        CboThreeList = tempThree;
    }
    #endregion

    #region Properties
    private string cboOneValue = string.Empty;
    public string CboOneValue {
        get {
            return cboOneValue;
        }
        set {
            if(!value.Equals(cboOneValue)) {
                cboOneValue = value;
                RetrieveLists();
                OnPropertyChanged("CboOneValue");
            }
        }
    }
    private string cboTwoValue = string.Empty;
    public string CboTwoValue {
        get {
            return cboTwoValue;
        }
        set {
            if(!value.Equals(cboTwoValue)) {
                cboTwoValue = value;
                RetrieveLists();
                OnPropertyChanged("CboTwoValue");
            }
        }
    }
    private string cboThreeValue = string.Empty;
    public string CboThreeValue {
        get {
            return cboThreeValue;
        }
        set {
            if(!value.Equals(cboThreeValue)) {
                cboThreeValue = value;
                RetrieveLists();
                OnPropertyChanged("CboThreeValue");
            }
        }
    }

    private List<string> cboOneList = new List<string>();
    public List<string> CboOneList {
        get {
            return cboOneList;
        }
        set {
            cboOneList = value;
            OnPropertyChanged("CboOneList");
        }
    }

    private List<string> cboTwoList = new List<string>();
    public List<string> CboTwoList {
        get {
            return cboTwoList;
        }
        set {
            cboTwoList = value;
            OnPropertyChanged("CboTwoList");
        }
    }

    private List<string> cboThreeList = new List<string>();
    public List<string> CboThreeList {
        get {
            return cboThreeList;
        }
        set {
            cboThreeList = value;
            OnPropertyChanged("CboThreeList");
        }
    }
    #endregion
}

}

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top