Home > Blockchain >  Hiding a specific Combobox Item if selected in another Combobox
Hiding a specific Combobox Item if selected in another Combobox

Time:09-30

I'm trying to implement that if a user selects (for exapmle) Value 1 in Combobox 1 then Value 1 shouldn't be visible, or at least not able to be picked inside Combobox 2/3/4/etc. If Value 1 is unselected from Combobox 1 it should be pickable again.

I have a custom class for the Combobox Items that looks like this:

    public class ComboboxItem
    {
        public string Text  { get; set; }
        public int    Value { get; set; }

        public override string ToString()
        {
            return Text;
        }
    }

This Part of my code picks values out of DataGridView Cells and inserts them into multiple comboboxes:

    private void button1_Click(object sender, EventArgs e)
         int numberofcolumns = dataGridView1.Columns.Count   1, numberofrows = dataGridView1.Rows.Count;
                for (int i = 1; i < numberofcolumns; i  )
                {
                    ComboboxItem item = new ComboboxItem();
                    item.Text = "Spalte "   i;
                    item.Value = i - 1;

                    CBKaNr.Items.Add(item);
                    CBKaLa.Items.Add(item);
                    CBLeTy.Items.Add(item);
                    CBKaMe.Items.Add(item);
                    CBRoVe.Items.Add(item);
                    CBLeNr.Items.Add(item);
                    CBFlNr.Items.Add(item);
                    CBChNr.Items.Add(item);
                    CBAnf1.Items.Add(item);
                    CBAnf2.Items.Add(item);
                    CBEnd1.Items.Add(item);
                    CBEnd2.Items.Add(item);

They can then be picked in my Interface which is then read by the Program which then exports the selected Values into a generated .txt File.

Cheers, DieserTBZ

CodePudding user response:

Bind data to comboboxes using DataSource and then rebind it without given item.

Based on dicussion in comments I am giving you full solution.

Some parts you need to change to be compatible to your code so please do so before testing it. I haven't tested any of it and wrote all from head since i do not have your data but it should all work.


public class ComboboxItem
{
    public string Text  { get; set; }
    public int Value { get; set; }

    public override string ToString()
    {
        return Text;
    }
}


public class YourForm : Form
{
    private List<ComboboxItem> baseCmbItems = new List<ComboboxItem>();
    
    // In second part of tuple we will store item selected inside given combobox.
    // I could use dictionary but i think this will be easier for you
    private List<Tuple<ComboBox, ComboboxItem>> comboBoxes = new List<Tuple<ComboBox, ComboboxItem>>(); 

    public YourForm()
    {
        InitializeComponents();

        // Populate your datagridview

        // Populate list with items that will be displayed in comboboxes
        // Usually I add first item as < Select Value > so I will do it here but remove it if not needed
        
        baseCmbItems.Add(new ComboboxItem() {
            Text = " < Select Value >",
            Value = -1
        });
        
        for(int i = 0; i < dataGridView1.Columns.Count(); i  )
        {
            baseCmbItems.Add(new ComboboxItem() {
                Text = "Spalte"   (i   1),
                Value = i
            });
        }
        
        // Add all combobox controls which shares data to this list
        comboBoxes.Add(new Tuple<ComboBox, ComboboxItem>("Combobox which shares data with others", null));
        comboBoxes.Add(new Tuple<ComboBox, ComboboxItem>("Another combobox which shares data with others", null));
        comboBoxes.Add(new Tuple<ComboBox, ComboboxItem>("Another combobox which shares data with others", null));
        comboBoxes.Add(new Tuple<ComboBox, ComboboxItem>("Another combobox which shares data with others", null));
        
        // Bind base data to all comboboxes inside this list
        foreach(Tuple<ComboBox, ComboboxItem> cb in comboBoxes)
        {
            cb.Item1.DataSource = baseCmbItems;
            cb.Item1.DisplayMember = "Text";
            cb.Item1.ValueMember = "Value";
            
            // Binding event
            cb.Item1.SelectedIndexChanged  = ComboBox_SelectedIndexChanged;
        }
    }

    private void ComboBox_SelectedIndexChanged(object sender, EventArgs e)
    {
        // Duplicate base combobox items (all)
        List<ComboboxItem> copyItems = new List<ComboboxItem>(baseCmbItems);
        
        // Getting combobox which started this event
        ComboBox sender_cmb = sender as ComboBox;
        
        // Getting newly selected item from combobox which started this event
        ComboboxItem selectedItem = sender_cmb.SelectedItem as ComboboxItem;
        
        // Removing all already selected items from duplicated list of items
        // Also assigning newly selected comboboxitem to sender combobox inside our comboBoxes list
        foreach(Tuple<ComboBox, ComboboxItem> cb in comboBoxes)
        {
            if(cb.Item1 == sender_cmb)
                cb.Item2 = selectedItem;
                
            if(cb.Item2 != null)
                copyItems.RemoveAll(x => x.Value == cb.Item2.Value);
        }
        
        // Now we have filtered copyItems without any already selected item
        // Now we rebind this data to all comboboxes with addition of that combobox already selected item
        
        foreach(Tuple<ComboBox, ComboboxItem> b in comboBoxes)
        {
            if(b.Item2 != null)
            {
                List<ComboboxItem> cItems = new List<ComboBoxItem>(copyItems);
                cItems.Add(b.Item2);
                b.Item1.DataSource = cItems;
                b.Item1.SelectedValue = b.Item2.Value;
            }
            else
            {
                b.Item1.DataSource = copyItems;
                b.Item1.SelectedValue = -1;
            }
        }
    }
}

CodePudding user response:

One problem I can see in this particular situation is best explained with an example. Let us say that there are four (4) ComboBoxes and each combo box has four (4) items to select from (“Spalte 1”, “Spalte 2”, “Spalte 3” and “Spalte 4”). Further… let us also assume that the user has selected “Spalte 1” in combo box 1, “Spalte 2” in combo box 2, “Spalte 3” in combo box 3 and “Spalte 4” in combo box 4…

In that situation, if the user clicked on any of the combo boxes… then… each ComboBox would ONLY contain ONE (1) item to select from… i.e. … the currently selected item. So… how is the user going to be able to “change” one of the combo boxes selected items if ALL the combo box items are used? I mean people DO make mistakes. Example, the user made a mistake and wanted to swap two combo box values. This approach is going to possibly lead to a dead end where the user will end up stuck.

Granted… “programmatically” we could set the combo boxes `Selected” index to -1 and remove the currently selected item and add it back to the other combo boxes list of items… however… we will NOT KNOW WHEN to do this. The user would have to click a button or use some other mechanism to “signal” that they wanted to “remove” the currently selected item and set the combo boxes value to an “empty” value.

Fortunately, there is a simple and common solution to this. In most cases, when dealing with combo boxes, I tend to add an “empty” item to the combo boxes list of items and usually will set it as the first item in the list. I like to think of this “blank/empty” combo box item as a way for the user to say “I do not want to select any item for this combo box.” This is intuitive for the user and will solve the previously described problem. The solution below uses this approach.

In addition, considering that each combo box will have different items in its list of items… then… each combo box will have to have its OWN data source. Using the “same” data source for ALL the combo boxes… will not work. Therefore, we will create a new data source for each combo box whenever the user changes ANY combo box value.

I am confident there may be other and better ways to achieve this, so this may be considered a “hacky” approach, however, it will work. The idea works like this… when a combo box value changes, we want to get a list of combo box values that are NOT currently selected. In other words… the “available” items we CAN select. Once we have this list… then we can loop though each ComboBox and check its currently selected value. If the selected value is NOT the “empty” value, then we simply add that item to our list of available items and then set that combo boxes data source to this list.

Example, if the combo box has `Spalte 1” selected… then we would KNOW that this item is NOT in our list of “available items” since it is already selected in the current combo box. Therefore we need to add it to the “available items” list JUST FOR THAT particular combo box. We continue in this fashion for all the combo boxes. A special case is if the user selected the “blank” item from the list of items. In that case we will already have a “blank” item in the “available items” list and we will simply ignore those combo box values as we do not want to add a duplicate “blank” value.

It is wise to set up a special class for the combo boxes data source and using your current ComboboxItem Class… I took the liberty to add a few changes to simplify the main code. One change is considering that we will want to “sort” the List<ComboboxItem> items so that ALL the combo box items are displayed in the same ordered fashion. Since the code will be adding/removing items to each combo box when one changes, we want to maintain a consistent ordering for ALL the combo boxes items and sorting the list each time is a simple solution.

Therefore, we will have the ComboboxItem Class implement the IComparable interface, and then we can sort the List<ComboboxItem> by simply calling its Sort() method. In addition we want to override the Equals method to enable the code to use the List.Contains method to see if a particular ComboboxItem is already in the list. This is used to keep out duplicate items.

And lastly, since we want to have a “blank” ComboboxItem… we will implement a “static” BlankComboItem property that will return a “blank” ComboboxItem such that its Value property is set to zero (0) and its Text property would be set to an empty string. This modified Class may look something like…

public class ComboboxItem : IComparable {

  public int Value { get; set; }
  public string Text { get; set; }

  public override bool Equals(object obj) {
    if (obj == DBNull.Value)
      return false;
    ComboboxItem that = (ComboboxItem)obj;
    if (this.Value.Equals(that.Value) && this.Text.Equals(that.Text)) {
      return true;
    }
    return false;
  }

  public override int GetHashCode() {
    return (Value.ToString()   Text).GetHashCode();
  }

  public int CompareTo(object obj) {
    ComboboxItem that = (ComboboxItem)obj;
    return this.Value.CompareTo(that.Value);
  }

  public static ComboboxItem BlankComboItem {
    get {
      return new ComboboxItem() { Value = 0, Text = "" };
    }
  }
}

Next, we will create three simple methods that will help in managing the combo boxes and help us decide what values belong to which combo box. The first method GetComboList() returns our default List<ComboboxItem> list of combo box items. Since each combo box needs its own data source, we will call this method once to set a global AllItems list which we will use in the next methods, then we will call it once for each combo box to initially set each combo box to the same values yet they will technically be “different” lists. This method may look something like…

private List<ComboboxItem> GetComboList() {
  int numberofcolumns = dataGridView1.Columns.Count;
  List<ComboboxItem> items = new List<ComboboxItem>();
  ComboboxItem item = ComboboxItem.BlankComboItem;
  items.Add(item);
  for (int i = 1; i <= numberofcolumns; i  ) {
    item = new ComboboxItem();
    item.Text = "Spalte "   i;
    item.Value = i;
    items.Add(item);
  }
  return items;
}

Next, we want a method GetCurrentSelectedItems() that loops through all the ComboBoxes and returns a List<ComboboxItem> of all the currently “selected” combo box items. We will use this list to get all the combo box items we CAN use. In other words, this method will give us a list of the items that we CAN NOT use since a combo box already has that item selected. It should be noted that before this code is called, we have set up a global variable List<ComboBox> called AllCombos that will hold ALL the ComboBoxes used. This GetCurrentSelectedItems method may look something like…

private List<ComboboxItem> GetCurrentSelectedItems() {
  List<ComboboxItem> selectedItems = new List<ComboboxItem>();
  foreach (ComboBox combo in AllCombos) {
    if (combo.SelectedIndex != -1) {
      if (!selectedItems.Contains((ComboboxItem)combo.SelectedItem)) {
        selectedItems.Add((ComboboxItem)combo.SelectedItem);
      }
    }
  }
  return selectedItems;
}

Bear in mind, this method MAY/Will return a “blank” combo box item since it is possible for a combo box to have the “blank” item selected.

And finally, we will create a method GetAvailableComboItems that will return a List<ComboboxItem> that contains ALL the ComboboxItems that have NOT yet been selected in any of the combo boxes. The code simply loops through the AllItems list and checks to see if the item is already in the usedItems list from the above method. If the item is NOT is the used list, then we will add it to this “available items” list. If the item is already used, then obviously we do not want to add it to the “available items” list. This method may look something like…

private List<ComboboxItem> GetAvailableComboItems() {
  List<ComboboxItem> availableItems = new List<ComboboxItem>();
  List<ComboboxItem> usedItems = GetCurrentSelectedItems();
  foreach (ComboboxItem item in AllItems) {
    if (!usedItems.Contains(item)) {
      availableItems.Add(item);
    }
  }
  return availableItems;
}

To put all this together, we first need to set up the global variables and all the combo boxes. This is done in the forms load event and may look something like…

private List<ComboboxItem> AllItems = new List<ComboboxItem>();
private List<ComboBox> AllCombos;

public Form1() {
  InitializeComponent();
}


private void Form1_Load(object sender, EventArgs e) {
  AllCombos = new List<ComboBox>();
  AllCombos.Add(comboBox1);
  AllCombos.Add(comboBox2);
  AllCombos.Add(comboBox3);
  AllCombos.Add(comboBox4);
  AllItems = GetComboList();
  SetComboBoxesSelectedIndexChangedEvent(false);
  SetComboBoxProperties(comboBox1, "Combo1", GetComboList());
  SetComboBoxProperties(comboBox2, "Combo2", GetComboList());
  SetComboBoxProperties(comboBox3, "Combo3", GetComboList());
  SetComboBoxProperties(comboBox4, "Combo4", GetComboList());
  SetComboBoxesSelectedIndexChangedEvent(true);
}

private void SetComboBoxesSelectedIndexChangedEvent(bool Subscribed) {
  foreach (ComboBox combo in AllCombos) {
    if (Subscribed) {
      combo.SelectedIndexChanged  = new EventHandler(ComboBox_SelectedIndexChanged);
    }
    else {
      combo.SelectedIndexChanged -= new EventHandler(ComboBox_SelectedIndexChanged);
    }
  }
}


private void SetComboBoxProperties(ComboBox combo, string name, List<ComboboxItem> data) {
  combo.Name = name;
  combo.DisplayMember = "Text";
  combo.ValueMember = "Value";
  combo.DataSource = data;
  combo.SelectedIndex = 0;
}

The SetComboBoxesSelectedIndexChanged(bool) method is used to subscribe/unsubscribe each combo boxes SelectedIndexChanged event. When the combo boxes are loaded with data, the DataSource is set AND a default “blank” item is set as the default selected item in each combo box. When the data is loading we do not need the ComboBox_SelectedIndexChanged event to fire and this method turns on/off the event. The same will apply in the SelectedIndexChanged event itself. The code will change the data source AND re-set the selected index in which case this will re-fire the event which we do not need nor want.

The global AllItems variable is filled with all the ComboboxItems and will be used by the previously described methods. In addition the global AllCombos list is filled with all the ComboBoxes to make it easier to loop through all the different combo boxes. And finally a method SetComboBoxProperties which will set up each combo boxes properties. All the properties are identical with the exception of the ComboBox itself, its Name and DataSource.

And finally the event/method that puts all this together is the combo boxes SelectedIndexChanged event. We will use this same event for ALL the combo boxes. Walking through this method starts a foreach loop through each ComboBox. First we get a new list of available items and grab the currently selected ComboboxItem for that combo box. If the currently selected combo boxes index is not -1 then this means that “something” is already selected and we need to add this item to the available items list. Next a check is made to make sure that the “blank” item is IN the available items list as we always want the “blank” item in the available items list. And finally the combo boxes data source is updated and the selection is set to the originally selected item. We will lose the combo boxes “selected” item when we update its DataSource and we want the combo box to maintain its currently selected item, therefore we need to set it back to its original value. This ComboBox_SelectedIndexChanged event may look something like…

int sic = 0;
private void ComboBox_SelectedIndexChanged(object sender, EventArgs e) {
  Debug.WriteLine("ComboBox_SelectedIndexChanged <- Enter "     sic);
  List<ComboboxItem> AvailableItems;
  ComboboxItem curItem;
  foreach (ComboBox combo in AllCombos) {
    AvailableItems = GetAvailableComboItems();
    curItem = (ComboboxItem)combo.SelectedItem;
    if (combo.SelectedIndex != -1) {
      if (combo.SelectedItem != ComboboxItem.BlankComboItem) {
        AvailableItems.Add((ComboboxItem)combo.SelectedItem);
      }
    }
    // since we ignore the blank items we need to make sure at least one blank item is available
    if (!AvailableItems.Contains(ComboboxItem.BlankComboItem)) {
      AvailableItems.Add(ComboboxItem.BlankComboItem);
    }
    AvailableItems.Sort();
    combo.SelectedIndexChanged -= new EventHandler(ComboBox_SelectedIndexChanged);
    combo.DataSource = AvailableItems;
    combo.SelectedItem = curItem;
    combo.SelectedIndexChanged  = new EventHandler(ComboBox_SelectedIndexChanged);
  }
  Debug.WriteLine("ComboBox_SelectedIndexChanged -> Leave ");
}

Below a small example of the above code in action.

enter image description here

I hope this makes sense and helps.

  • Related