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

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

Related

Strange behavior of Windows Forms combobox control

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.

unbinding bindingsource

I am using a bindingsource in my windows forms application to populate some textboxes etc in my view. Binding works OK but how can I unsubscribe my bindingSource from from my object?
bindingSource.DataSource = new Foo();//OK
bindingSource.DataSource = null;//Not ok
If I try to unbind by setting data = null I get an exception:
System.ArgumentException : Cannot bind
to the property or column Bar on the
DataSource. Parameter name: dataMember
I don't want to remove all bindings to my controls (i have a lot) but would like to suspend binding as long as the bindingSource has no data....
I found a workaround like this bindingSource.DataSource = typeof(Foo); but is this THE way?
The typeof "workaround" is actually what the windows forms designer does when you set the BindingSource's DataSource in the PropertyGrid, and select a type from "Project data sources".
Look at the generated code in the *.designer.cs file for your form.
We use this "trick" in one of our products, and it has worked well for many years now.
Regards
I am not aware of a .Data property for the BindingSource object, but there is a .DataSource property, which can be set to null:
bindingSource.DataSource = null;
This releases the binding source from the data. However, looking at the reference for BindingSource.DataSource:
DataSource property List results
---------------------------- -------------------------------------------
null with DataMember set Not supported, raises ArgumentException.
If you're using a DataMember, you can't set the DataSource to null without an exception.
Unfortunately I don't know whether your workaround is a proper way of doing it, but at least now we know that you can't simply bind to null when a DataMember is set.
mrlucmorin gave you correct answer. It is working and it is the correct way of handling such situation.
However it won't quite work if your DataSource is of DataTable type. In such case you might want to play with bs.RaiseListChangedEvents = false; before nulling the BindingSource.DataSource, and set it to true after you assign new DataSource. Right after you set it to true, don't forget to reset the bindings with bs.ResetBindings(true);
Be aware that this might cause leaving your databound controls with 'old' data in them.
When using typeof as "empty" value for the DataSource, you can test for it like this:
private void BindingSource_DataSourceChanged(object sender, EventArgs e)
{
DataSource dataSource = ((BindingSource)sender).DataSource;
if (dataSource is Type t && t == typeof(MyModel))
{
lblEmpty.Visible = true;
pnlDetails.Visible = false;
}
else
{
lblEmpty.Visible = false;
pnlDetails.Visible = true;
}
}
This way you can conditionally hide or show an "empty" message in the UI, in a simple way.

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.

What is the best way to sort a data bound combo box?

I have done a bit of research into this and it seems that the only way to sort a data bound combo box is to sort the data source itself (a DataTable in a DataSet in this case).
If that is the case then the question becomes what is the best way to sort a DataTable?
The combo box bindings are set in the designer initialize using
myCombo.DataSource = this.typedDataSet;
myCombo.DataMember = "Table1";
myCombo.DisplayMember = "ColumnB";
myCombo.ValueMember = "ColumnA";
I have tried setting
this.typedDataSet.Table1.DefaultView.Sort = "ColumnB DESC";
But that makes no difference, I have tried setting this in the control constructor, before and after a typedDataSet.Merge call.
If you're using a DataTable, you can use the (DataTable.DefaultView) DataView.Sort property. For greater flexibility you can use the BindingSource component. BindingSource will be the DataSource of your combobox. Then you can change your data source from a DataTable to List without changing the DataSource of the combobox.
The BindingSource component serves
many purposes. First, it simplifies
binding controls on a form to data by
providing currency management, change
notification, and other services
between Windows Forms controls and
data sources.
You can actually sort the default view on a DataTable:
myDataTable.DefaultView.Sort = "Field1, Field2 DESC";
That'll sort any rows you retrieve directly from the DataTable.
Make sure you bind the DefaultView to the Controls Datasource, after you set the Sort property, and not the table:
myCombo.DataSource = this.typedDataSet.Tables["Table1"].DefaultView;
myCombo.DisplayMember = "ColumnB";
myCombo.ValueMember = "ColumnA";
Josh Smith has a blog post that answers this question, and does it all in XAML.
Does the data need to be in a DataTable?
Using a SortedList and binding that to a combo box would be a simpler way.
If you need to use a DataTable you can use the Select method to retrieve a DataView and pass in a sort parameter.
DataView dv = myDataTable.Select("filter expression", "sort");
The simplest way to sort a ComboBox is to use the ComboBox.Sorted property. However, that won't work if you're using data binding. In that case you'll have to sort the data source itself.
You can use either a SortedList or SortedDictionary (both sort by the Key), or a DataView.
The DataView has a Sort property that accepts a sort expression (string) for example:
view.Sort = "State, ZipCode DESC";
In the above example both State and ZipCode are columns in the DataTable used to create the DataView.
I realize that you've already chosen your answer to this question, but I would have suggested placing a DataView on your form, binding it to your DataSet/DataTable, and setting the sort on the View in the designer. You then bind your combobox to the DataView, rather than the DataSet/DataTable.

Categories

Resources