Strange behavior of Windows Forms combobox control - c#

I am developing a small desktop app, and there are several drop-down lists (combobox-es) on my form. I populate a list of strings, which will be used as data source for all of them. Here is example from my Form.cs class:
List<string> datasource = new List<string>();
datasource.Add("string 1");
datasource.Add("string 2");
Then I set this list as a data source to several comboboxes:
cmbDataType1.DataSource = datasource;
cmbDataType2.DataSource = datasource;
This all happens in same method, which is called from the Form constructor.
Here is the strange part: after I change a selected value in one of them, the same value will be set in the other one. There are no SelectedIndexChange events set. I have messed up somewhere, but I cant put my finger where...

The behavior that you see is by design. When you bind the same object as the data source for multiple controls, all the controls share the same binding source.
If you explicitly assign a new binding source to each control, even while using the same data source, all controls will be unbound and will act independent of each other:
cmbDataType1.DataSource = new BindingSource(datasource, "");
cmbDataType2.DataSource = new BindingSource(datasource, "");

You should set a new BindingContext for the control before binding the dataSource the next time:
cmbDataType1.BindingContext = new BindingContext();
cmbDataType1.DataSource = datasource;
cmbDataType2.BindingContext = new BindingContext();
cmbDataType2.DataSource = datasource;

Since you are binding to the same exact datasource that is the expected behavior. You will want to change your binding to be a OneWay binding or use different objects if you don't want the selecteditem to change.

Related

C# Trying to find an unbound binding source [duplicate]

I am creating a ComboBox array dynamically and the DataSource for all the ComboBox is a single integer list that contains some integers. But when I change a value say X in any one combo box then all other combo values get reset to value X.
So here is the situation:
All combo box controls are bound to a single list
When I change selected item of a combo box, selected item of all other combo box controls also change.
How can I stop these behavior?
Since you are binding all combo boxes to the same data source - a single list - they are using a single BindingManagerBase.
So when you choose an item from one of combo boxes, the current Position of the shared binding manager base changes and all combo boxes goes to that position of their shared data source.
To solve the problem you can bind them to different data source:
You can bind them to yourList.ToList() or any other list for example different BindingList<T>.
combo1.DataSource = yourList.ToList();
combo2.DataSource = yourList.ToList();
You can use different BindingSource for them and set your list as DataSource of BindingSource
combo1.DataSource = new BindingSource { DataSource= yourList};
combo2.DataSource = new BindingSource { DataSource= yourList};
Also as another option:
You can use different BindingContext for your combo boxes. This way even when you bind them to a single list, they are not sync anymore.
combo1.BindingContext = new BindingContext();
combo1.DataSource = yourList;
combo2.BindingContext = new BindingContext();
combo2.DataSource = yourList;
In fact all controls of the form use a shared BindingContext. When you bind 2 controls to a same data source, then they also use the same BindingManagerBase this way, when you for example move to next record, all controls move to next record an show value from bound property of next record. This is the same behavior that you are seeing from your combo boxes. Being sync for controls which are using the same BindingManagerBase is a desired behavior. Anyway sometimes we don't need such behavior. The post shares the reason and the solution.

One DataSource for multiple controls

I have two ListBox in my winforms application, I assigne a datasource for both of them as follow:
private void MakeMeasurementUnits()
{
var units = new List<MeasurementUnit>
{
new MeasurementUnit {Name = "Current", SiUnit = "A"},
new MeasurementUnit {Name = "Voltage", SiUnit = "V"},
new MeasurementUnit {Name = "Time", SiUnit = "s"},
new MeasurementUnit {Name = "Temprature", SiUnit = "°C"}
};
lbxXunit.DataSource = units;
lbxYunit.DataSource = units;
}
The strange thing is (or maybe because it is my first time!!), in the form when I click on items of one of these lisboxes, the same item in the second listbox gets selected as well. Is this a default behaviour? how to prevent this? If this is default behaviour, what is useful about it?
I found the quick remedy to be making two different datasources (same thing with another name)
The listbox seems to cache the binding source. This is default behavior. If you want to avoid this, the easy way is to create a copy of the list to bind to the second data source:
lbxXunit.DataSource = units;
lbxYunit.DataSource = units.ToList();
This is useful when you have multiple views of the same data and want to synchronize the selection of these items.
Yes, this is normal behaviour. It happens because the ListView control uses a BindingSource object to track the currently selected item. (A List has no way to track a selected item without a BindingSource.)
By default, a DataSource in a WinForms control uses a BindingSource created for it by the WinForms system itself.
You can read more about the BindingSource at:
http://msdn.microsoft.com/en-us/library/system.windows.forms.bindingsource.aspx
There is an article here which might help too:
http://blogs.msdn.com/b/bethmassi/archive/2007/09/19/binding-multiple-comboboxes-to-the-same-datasource.aspx
The behavior you have noted is the default/correct behavior for winforms controls. You can achieve what you are after by setting a new BindingContext for your second listbox control without creating a copy of your data source.
BindingContext
This is correct behaviour. The datasource management in WindowsForms keeps track of the selected item on control and manipulates binded data too.
The resolution you've found already: is assign 2 different data sources objects to these controls.

WinForms - changing the bindingsource/datasource of a control at runtime?

I have a Winforms app with a BindingSource component and its DataSource is set to a DataSource that I created for a custom data object. Also on the form are several controls that are bound to various properties of the object exposed through the BindingSource. Many of these controls are comboboxes and will display values with a backing enum so I'm setting the DataSource for these controls like this:
comboBox1.DataSource = new BindingSource(Utility.ToList(typeof(DataObject.EnumValues)), null);
comboBox1.DisplayMember = "Value";
comboBox1.ValueMember = "Key";
This is all working well enough but I have two comboboxes which I need to be able to change at runtime to display other values (with a different back enum). In these two cases, I'm creating the initial bindings and datasource in code like this:
comboBox2.DataBindings.Add(new Binding("SelectedValue", this.bindingSource, "PropertyName1", true));
comboBox2.DataSource = new BindingSource(Utility.ToList(typeof(DataObject.FirstSetOfEnumValues)), null);
comboBox2.DisplayMember = "Value";
comboBox2.ValueMember = "Key";
...and then when I need comboBox2 to bind to and display different values, I do this:
comboBox2.DataBindings.Clear();
comboBox2.DataBindings.Add(new Binding("SelectedValue", this.bindingSource, "PropertyName2", true));
comboBox2.DataSource = null;
comboBox2.DataSource = new BindingSource(Utility.ToList(typeof(DataObject.SecondSetOfEnumValues)), null);
comboBox2.DisplayMember = "Value";
comboBox2.ValueMember = "Key";
As best I can tell, this is working properly, but it's ugly and there has got to be a much better way to do this, right? If you know what it is, I'd love to hear it! Thanks very much!
If this was a web form, I might suggest having ComboBox2 as two separate ComboBoxes and hide/show the one you need. Although I appreciate this isn't quite so easy for a WinForm project, unless you're using a flowing layout?
You could add a function to return your data source, based on your enumeration type... I don't think you need to re-set your DisplayMember and ValueMember properties after calling Clear() (but I may be wrong).
Other than that, I don't think you can simplify it much more. Although I'd be happy to hear if someone has a better solution :)
You don't need to bind the ComboBoxes to a new instance of BindingSource.
Bind your ComboBoxes to their respective BindingSources. This can be done either through the Windows Forms Designer, or manually in your own code. Make sure to keep a reference to the BindingSources if you do it in your code. If you use the Designer, then a member is added for you to your Form class.
Then when you want to display a different set of values, all you need to do is change the DataSource on the BindingSources, and the ComboBoxes will update accordingly.
Cheers

Refreshing ComboBox Data Binding in C# and .NET 4.0

I have a ComboBox (Windows Forms) that is bound to a List. It is created at design time. When the List contents are changed my code calls a function to refresh the data binding. This works fine for .NET 3.5:
BindingData.SuspendBinding();
DataSource = null;
DataSource = BindingData;
BindingData.ResumeBinding();
I have switched to .NET 4.0 and it has stopped working. Specifically after stepping through this code the VS debugger shows BindingData.DataSource refers to a list with 127 items, but the ComboBox Items property contains zero items.
See this SO question along a similar theme: ComboBox Items Count Doesn't Match DataSource.
I have tried everything I can think of. Currently my code looks like the following and still doesn't work:
BindingData.SuspendBinding();
DataSource = null;
DataSource = BindingData;
BindingData.ResumeBinding();
BindingContext Dummy = this.BindingContext;
Invalidate();
PerformLayout();
I tried switching from List to BindingList and that didn't help. I had to switch from .NET 3.5 to .NET 4.0 against my will so this is pretty frustrating. I'm sure there is a specific sequence that works. Any ideas?
This is how I am attaching the data source to the ComboBox:
private BindingSource BindingData = new BindingSource();
BindingData.DataSource = Nodes;
DataSource = BindingData;
thanks, Andy
I solved it. I guess at some point I made what I thought was a minor change but actually wasn't. This code was moved from being called when the ComboBox is being displayed to when it was being created. It didn't yet have a handle and so the data binding cannot be refreshed.
I added another refresh of the databinding again in a ComboBox.HandleCreated event and it works.
thanks, Andy
Why you're suspending and resuming the BindingSource? If you just change your DataSource there will be no performance pitfalls.
According to How to: Bind a Windows Forms ComboBox or ListBox Control to Data you can use the ComboBox's DisplayMember property:
//Sample for C++ .NET:
List<String^>^ options = gcnew List<String^>();
options->Add("Option 1");
options->Add("Option 2");
comboBox.DataSource = options;
comboBox.DisplayMember = "Length";//this causes an DataSource update but the ComboBox would
//show an item's length instead of the item itself
comboBox.DisplayMember = ""; //reset -> the ComboBox calls each List item's ToString
//member
"Length" refers to a public property of the String class. Better would be a property that refers directly to the string's characters. The only remaining public property of String is Chars but I couldn't make it work. So we reset DisplayMember by comboBox.DisplayMember = "", causing the ComboBox to call each List item's (a String) ToString method => problem solved.
Other List entries than Strings can be handled by the ComboBox's properties DisplayMember and ValueMember (they also apply to other controls):
DisplayMember & ValueMember

Binding a DataGridView to a DataTable but also including a ComboBox as the last column

Right now I have a DataGridView that's bound to a DataTable that works all well and good. What I need to do on the DataGridView is include a new column at the very end that is a ComboBox that will dynamically filly based on the key value of the row.
My columns are ID, Name and Count. The 4th column will be a ComboBox that takes an ID and creates the values in the drop down based on that. I currently have a custom ComboBox that takes an ID in its constructor and fills it that way, but I couldn't figure out a way to put that into the DataGridView. So, I created a copy of that custom ComboBox control and made it into a DataGridViewComboBoxCell but I STILL can't figure out how to dynamically bind it to the form. I've scoured the internet and found some examples but not exactly what I want to do.
This link shows kind of what I want to do except I'm using C# not VB. Any ideas?
Here is what I just tried:
DataTable dt = new DataTable();
dt.Columns.Add("Col");
dt.Rows.Add();
dt.Rows.Add();
dt.Rows.Add();
dt.Rows[0][0] = "1";
dt.Rows[1][0] = "2";
dt.Rows[2][0] = "3";
dataGridView1.DataSource = dt;
dataGridView1.Columns.Add(new DataGridViewComboBoxColumn());
List<string> lstStr = new List<string>();
lstStr.Add("1");
lstStr.Add("2");
lstStr.Add("3");
lstStr.Add("4");
((DataGridViewComboBoxCell)(dataGridView1.Rows[0].Cells[1])).DataSource = lstStr;
Is this what you are looking for?
Try this:
// Create a new Combo Box Column
DataGridViewComboBoxColumn EmpIdColumn = new DataGridViewComboBoxColumn();
// Set the DataSource of EmpIdColumn as bellow
EmpIdColumn.DataSource = myDataSet.Tables[0];
// Set the ValueMember property as done bellow
EmpIdColumn.ValueMember = myDataSet.Tables[0].Columns[0].ColumnName.ToString();
// Set the DisplayMember property as follow
EmpIdColumn.DisplayMember = EmpIdColumn.ValueMember;
You can take or leave this way, but the way I've handled it (in VB) is to bind the DGV to a collection of objects which contains some extra members above and beyond my actual bound data. I use these additional members as placeholders for extra stuff.
I populate the elements from the data source (a DB in my case) into the collection, populate the other members as I need them, and then bind the collection to the DGV. Since the extra members are not members of the original table, I can change their contents. Then I don't really have to deal with "partial binding" of the control as you want to do.
Anyway, another way to skin the cat.

Categories

Resources