couldn't find an answer to my issue. So hoped someone could help. I have to comboboxes on one form, and they are both filled with data from the same datatable. The issue is that when I select i.e. first value of combobox1, it automatically sets combobox2 the same value. I would like to be able to select two different values and don't know where is my issue.
private void CardWords_Load(object sender, EventArgs e)
{
try
{
this.comboBox1.DataSource = this.dataSet.Tables["Languages"];
//this.comboBox1.Items.AddRange(this.dataSet.Languages.ToArray());
this.comboBox1.DisplayMember = "Language";
this.comboBox1.ValueMember = "LanguageId";
this.comboBox1.BindingContext = this.BindingContext;
//this.comboBox2.Items.AddRange(this.dataSet.Languages.ToArray());
this.comboBox2.DataSource = this.dataSet.Tables["Languages"];
this.comboBox2.DisplayMember = "Language";
this.comboBox2.ValueMember = "LanguageId";
this.comboBox2.BindingContext = this.BindingContext;
}
catch (Exception)
{
throw;
}
}
When I set the items it works, but then I want to be able to use LanguageID column as value.
Well, maybe you shouldn't put everything in one procedure. Apart from that this would help you to understand the cause of your problem, it will also enable you to reuse the code, to unit test it, and if needed change it without having to change a lot of code.
First of all, apparently your Form has to display "an item selected from a list of similar items, together with a visual possibility to select another item". It has to display this twice. This means that you need two of these objects. One may have selected item X, the other object has selected item Y. That both objects have the same collection of items to choose from does not influence this.
The easy method is to use the common source data to create two BindingLists, and assign each BindingList to one ComboBox.
Advantages: Quick to implement, easy to use. Easy to change if you want to change it later (ComboBoxes show different collections).
Disadvantages: less efficient, you will have one collection containing the common source data per ComboBox. You have to unit test this once per ComboBox. You will have have a copy-paste solution, which might be error prone if in future you will add a third ComboBox.
Let's say these ComboBoxes will both show the same sequence of objects of type MyItem
. ComboBox1 will select Item1
; ComboBox2 will select Item2
. So both Item1 and Item2 will be of type MyItem.
The solution will have procedures like this:
// returns the sequence of items to display in the comboBoxes
private IEnumerable<MyItem> GetSourceData() {...}
// Get / Set the source data in ComboBox1
private BindingList<MyItem> DisplayData1
{
get => (BindingList<MyItem>)this.ComboBox1.DataSource;
set => this.ComboBox1.DataSource = value;
}
private MyItem SelectedItem1
{
get => (MyItem)this.ComboBox1.SelectedItem;
set => this.ComboBox1.SelectedItem = value;
}
You need similar methods for ComboBox2.
private void InitComboBoxes()
{
IEnumerable<MyItem> commonComboBoxData = this.GetSourceData();
this.DisplayData1 = new BindingList<MyItem>(commonComboBoxData.ToList());
this.DisplayData2 = new BindingList<MyItem>(commonComboBoxData.ToList());
}
If the operator indicates that he has finished selecting the items in the ComboBoxes, for instance by pressing the Apply Now button:
private void OnButtonApplyNow_Clicked(object sender, ...)
{
MyItem selected1 = this.SelectedItem1;
MyItem selected2 = this.SelectedItem2;
ProcessSelectedItems(selected1, selected2);
}
That's it. Only a few one-liner methods.
Although the previous solution would work. it would be a hell of a job if you need 10 such ComboBoxes in your form, or if you need to do this on five forms.
The neat solution would be to create a UserControl that contains a ComboBox, which allows you to set the common source data and get the selected item.
To be better reusable, this UserControl will be a generic class that will show items of type T
Use visual studio designer to create a UserControl that contains a Docked ComboBox.
You could derive from ComboBox instead of use composition, but that would give users of your class access to properties that you don't want them to. After all you don't want allow users of your class to put Customers
in the DataSource of a ComboBox that should show Products
.
class MyComboBox<T> : UserControl // TODO: invent a proper name
{
// constructor and comboBox1 added by designer
private MyComboBox() {...}
private ComboBox comboBox1 = ...
Add the methods above:
private BindingList<T> DisplayedItems
{
get => (BindingList<T>)this.ComboBox1.DataSource;
set => this.ComboBox1.DataSource = value;
}
private T SelectedItem
{
get => (T)this.ComboBox1.SelectedItem;
set => this.ComboBox1.SelectedItem = value;
}
You will also need to tell which property of type T should be displayed.
public string DisplayMember
{
get => this.comboBox1.DisplayMember;
set => this.comboBox1.DisplayMember = value;
}
After compiling you can use visual studio designer to add MyComboBox
to your Forms, similar to how you would add a Button
Usage: using visual studio designer you have added 10 ComboBoxes, who will all show items of type Product
. The first five ComboBoxes will show the product's Name
, four ComboBoxes will show the Id
of the Product, and one ComboBox will show the Price
.
public MyClass() // constructor
{
InitializeComponent();
// Comboboxes that show the name
this.ComboName1.DisplayMember = nameof(Product.Name);
this.ComboName2.DisplayMember = nameof(Product.Name);
this.ComboName3.DisplayMember = nameof(Product.Name);
this.ComboName4.DisplayMember = nameof(Product.Name);
this.ComboName5.DisplayMember = nameof(Product.Name);
// ComboBoxes that show the Id:
this.ComboId1.DisplayMember = nameof(Product.Id);
this.ComboId2.DisplayMember = nameof(Product.Id);
this.ComboId3.DisplayMember = nameof(Product.Id);
this.ComboId4.DisplayMember = nameof(Product.Id);
// ComboBox that shows the Price:
this.ComboPrice1.DisplayMember = nameof(Product.Price);
}
Initialize the ComboBoxes:
private void InitComboboxes
{
IEnumerable<Product> selectableProducts = GetSelectableProducts();
var comboBoxesToInit = new MyComboBox<Product>[]
{
this.ComboName1, this.ComboName2, ..., this.ComboPrice;
};
foreach (MyComboBox comboBoxToInit in comboBoxesToInit)
{
comboBoxToInit.DisplayedItems = new BindingList<Product>(selectableProducts.ToList());
};
Do some testing: what happens if you convert the selecatableProducts to a List only once:
List<Product> selectableProducts = GetSelectableProducts().ToList();
foreach(...)
{
comboBoxToInit.DisplayedItems = new BindingList<Product>(selectableProducts);
Maybe we are lucky and this works also.