如果在另一个组合框中选择,则隐藏特定的组合框项目

Hiding a specific Combobox Item if selected in another Combobox

我正在尝试实现,如果用户在 Combobox 1 中选择(例如)Value 1,则 Value 1 不应该是可见的,或者至少不能在 Combobox 2/3/4/etc. 中被选中 如果 Value 1 已从 组合框 1 中取消选择 它应该可以再次选择。

我有一个自定义组合框项目 class,如下所示:

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

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

我的这部分代码从 DataGridView 单元格 中选取值并将它们插入多个 组合框 :

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

然后可以在我的界面中选择它们,然后由程序读取,然后将选定的值导出到生成的 .txt 文件.

使用 DataSource 将数据绑定到组合框,然后在没有给定项目的情况下重新绑定它。

根据评论中的讨论,我给你完整的解决方案。

您需要更改某些部分以与您的代码兼容,因此请在测试之前进行更改。 我没有测试过任何一个,都是从头开始写的,因为我没有你的数据,但它应该都可以。


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

我在这种特殊情况下看到的一个问题最好用一个例子来解释。假设有四 (4) 个 ComboBoxes,每个组合框有四 (4) 个项目 select 来自(“Spalte 1”、“Spalte 2”、“Spalte 3”和“Spalte 4”)。此外......让我们还假设用户在组合框 1 中输入了“Spalte 1”,在组合框 2 中输入了“Spalte 2”,在组合框 3 中输入了“Spalte 3”,在组合框中输入了“Spalte 4” 4…

在那种情况下,如果用户单击任何组合框……那么……每个 ComboBox 将只包含一 (1) 个项目到 select 来自……即……当前 selected 项。那么……如果使用了所有组合框项目,用户将如何能够“更改”其中一个组合框 selected 项目?我的意思是人们确实会犯错误。例如,用户犯了一个错误,想要交换两个组合框的值。这种方法可能会导致死胡同,用户最终会陷入困境。

授予...“以编程方式”我们可以将组合框“Selected”索引设置为 -1 并删除当前 selected 项目并将其添加回其他组合框项目列表......但是......我们将不知道何时执行此操作。用户必须单击按钮或使用其他机制来“发出信号”,表示他们想要“删除”当前 selected 项目并将组合框值设置为“空”值。

幸运的是,有一个简单而通用的解决方案。在大多数情况下,在处理组合框时,我倾向于将“空”项添加到组合框项列表中,并且通常将其设置为列表中的第一项。我喜欢将这个“blank/empty”组合框项目视为用户说 “我不想 select 此组合框的任何项目的一种方式。” 这对用户来说是直观的,将解决前面描述的问题。下面的解决方案使用了这种方法。

此外,考虑到每个组合框在其项目列表中都有不同的项目……那么……每个组合框都必须有自己的数据源。对所有组合框使用“相同”的数据源……是行不通的。因此,每当用户更改任何组合框值时,我们将为每个组合框创建一个新的数据源。

我相信可能有其他更好的方法来实现这一点,因此这可能被认为是一种“hacky”方法,但是,它会起作用。这个想法是这样的……当组合框值发生变化时,我们想要获得当前未 selected 的组合框值列表。换句话说......我们可以 select 的“可用”项目。一旦我们有了这个列表……然后我们就可以遍历每个 ComboBox 并检查其当前的 selected 值。如果 selected 值不是“空”值,那么我们只需将该项目添加到我们的可用项目列表中,然后将该组合框数据源设置为此列表。

例如,如果组合框有 `Spalte 1" selected...那么我们会知道该项目不在我们的“可用项目”列表中,因为它已经 selected在当前组合框中。因此,我们需要将它添加到“可用项目”列表中,只是为了那个特定的组合框。我们继续以这种方式处理所有组合框。一种特殊情况是,如果用户 select 从项目列表中编辑了“空白”项目。在那种情况下,我们将在“可用项目”列表中已经有一个“空白”项目,我们将简单地忽略这些组合框值,因为我们不想添加重复的“空白”值。

明智的做法是为组合框数据源设置一个特殊的 class 并使用您当前的 ComboboxItem Class...我冒昧地添加了一些更改以简化主要代码。一个变化是考虑到我们要对 List<ComboboxItem> 项进行“排序”,以便所有组合框项都以相同的排序方式显示。由于代码将 adding/removing 个项目更改为每个组合框,因此我们希望对所有组合框项目保持一致的排序,并且每次对列表进行排序是一个简单的解决方案。

因此,我们会让ComboboxItem Class实现IComparable接口,然后我们可以通过简单地调用它的Sort()来对List<ComboboxItem>进行排序方法。此外,我们希望重写 Equals 方法以使代码能够使用 List.Contains 方法来查看特定的 ComboboxItem 是否已在列表中。这用于排除重复项。

最后,因为我们想要一个“空白”ComboboxItem...我们将实现一个“静态”BlankComboItem 属性 将 return 一个“空白” ” ComboboxItem 这样它的 Value 属性 被设置为零 (0) 而它的 Text 属性 将被设置为一个空字符串。这个修改后的 Class 可能看起来像……

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 = "" };
    }
  }
}

接下来,我们将创建三个简单的方法来帮助管理组合框并帮助我们确定哪些值属于哪个组合框。第一种方法 GetComboList() return 是我们默认的 List<ComboboxItem> 组合框项目列表。由于每个组合框都需要自己的数据源,我们将调用此方法一次以设置一个全局 AllItems 列表,我们将在接下来的方法中使用该列表,然后我们将为每个组合框调用一次以初始设置每个组合框到相同的值,但它们在技术上将是“不同的”列表。这个方法可能看起来像……

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

接下来,我们需要一个方法 GetCurrentSelectedItems() 循环遍历所有 ComboBoxes 和 returns a List<ComboboxItem> 所有当前“selected”组合框项目。我们将使用此列表来获取我们可以使用的所有组合框项目。换句话说,这个方法会给我们一个我们不能使用的项目列表,因为组合框已经有那个项目 selected。应该注意的是,在调用此代码之前,我们已经设置了一个名为 AllCombos 的全局变量 List<ComboBox>,它将保存所有使用的 ComboBoxes。这个 GetCurrentSelectedItems 方法可能看起来像……

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

请记住,此方法 MAY/Will return 一个“空白”组合框项目,因为组合框可能包含“空白”项目 selected。

最后,我们将创建一个方法 GetAvailableComboItems,它将 return 一个 List<ComboboxItem> 包含所有尚未 select 的 ComboboxItems在任何组合框中编辑。代码简单地遍历 AllItems 列表并检查该项目是否已经在上述方法的 usedItems 列表中。如果该项目不是已用列表,那么我们会将其添加到此“可用项目”列表中。如果该项目已被使用,那么显然我们不想将其添加到“可用项目”列表中。这个方法可能看起来像……

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

为了将所有这些放在一起,我们首先需要设置全局变量和所有组合框。这是在表单加载事件中完成的,可能看起来像……

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

SetComboBoxesSelectedIndexChanged(bool)方法用于subscribe/unsubscribe每个组合框SelectedIndexChanged事件。当组合框加载数据时,设置 DataSource 并将默认的“空白”项设置为每个组合框中的默认 selected 项。加载数据时,我们不需要触发 ComboBox_SelectedIndexChanged 事件,此方法将 on/off 事件变为。这同样适用于 SelectedIndexChanged 事件本身。该代码将更改数据源并重新设置 selected 索引,在这种情况下,这将重新触发我们不需要也不想要的事件。

全局AllItems变量填充了所有ComboboxItems,将被前面描述的方法使用。此外,全局 AllCombos 列表中填充了所有 ComboBoxes,以便更轻松地遍历所有不同的组合框。最后一个方法 SetComboBoxProperties 将设置每个组合框属性。除了 ComboBox 本身、它的 NameDataSource.

之外,所有属性都相同

最后 event/method 将所有这些组合在一起是组合框 SelectedIndexChanged 事件。我们将为所有组合框使用相同的事件。遍历此方法会在每个 ComboBox 中启动一个 foreach 循环。首先,我们获得一个新的可用项目列表,并为该组合框获取当前 selected ComboboxItem。如果当前 selected 组合框索引不是 -1 那么这意味着“某物”已经 selected 并且我们需要将此项目添加到可用项目列表中。接下来进行检查以确保“空白”项目在可用项目列表中,因为我们始终希望“空白”项目位于可用项目列表中。最后更新组合框数据源,并将 selection 设置为最初的 selected 项目。当我们更新其 DataSource 时,我们将丢失组合框“selected”项,我们希望组合框保持其当前 selected 项,因此我们需要将其设置回它的原始值。这个 ComboBox_SelectedIndexChanged 事件可能看起来像……

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

下面是上面代码的一个小例子。

我希望这是有道理的并且有所帮助。