Recently, I participated in an MSDN Forum thread about problems with ComboBoxes, SelectedValue and SelectedIndex. It reminded me of a "gotcha" that I knew about a long time ago, but had forgotten about. I haven't been doing any development on Windows Forms apps in a long time (I have been doing only back-end server side stuff for more than a few years now), although many years ago I was responsible for a pretty extensive Windows Forms "Framework" for our developers to use for all of our company's applications. So, I knew about this then (and I'm sure that I built it into the Framework's base classes DataBinding methods). But, I digress ... let's get to a description of the problem and how to make sure it doesn't happen to you!
Say that in your application, you need to do something with the SelectedValue of a ComboBox. So, you create an event handler for either the .SelectedIndexChanged event or the .SelectedValueChanged event. But it doesn't work the way you expect it to and you're getting exceptions sometimes. What could be the problem? Let's look at the result of some debugging code placed in each of those event handlers to try and troubleshoot the problem:
Here's the debugging code:
private void cboCustomer_SelectedValueChanged(object sender, EventArgs e)
{
Console.WriteLine($"Value Changed: SelectedIndex is {cboCustomer.SelectedIndex}");
Console.WriteLine($"Value Changed: SelectedValue is {cboCustomer.SelectedValue}, is a {cboCustomer.SelectedValue.GetType()}");
Console.WriteLine("");
// the rest of your code is here
}
private void cboCustomer_SelectedIndexChanged(object sender, EventArgs e)
{
Console.WriteLine($"Index Changed: SelectedIndex is {cboCustomer.SelectedIndex}");
Console.WriteLine($"Index Changed: SelectedValue is {cboCustomer.SelectedValue}, is a {cboCustomer.SelectedValue.GetType()}");
Console.WriteLine("");
// the rest of your code is here
}
And here are the results:
Value Changed: SelectedIndex is 0
Value Changed: SelectedValue is System.Data.DataRowView, is a System.Data.DataRowView
Index Changed: SelectedIndex is 0
Index Changed: SelectedValue is System.Data.DataRowView, is a System.Data.DataRowView
Value Changed: SelectedIndex is 0
Value Changed: SelectedValue is System.Data.DataRowView, is a System.Data.DataRowView
Value Changed: SelectedIndex is 0
Value Changed: SelectedValue is System.Data.DataRowView, is a System.Data.DataRowView
Index Changed: SelectedIndex is 0
Index Changed: SelectedValue is System.Data.DataRowView, is a System.Data.DataRowView
Value Changed: SelectedIndex is 0
Value Changed: SelectedValue is 01, is a System.String
This is obviously, not a good thing ... the events are firing way too many times. And why is the SelectedValue a DataRowView at first (in my example I am using a DataTable as the Combo's DataSource)? In fact, these events should not be firing at all at this time ... we have simply set up the databinding for the ComboBox. Wow! What did we do wrong?
Here's the databinding code (remember that this is BAD CODE!)
this.bsCustomers = new BindingSource();
this.bsCustomers.DataSource = this.oDataFromXML.Tables["Customer"];
this.cboCustomer.SelectedIndexChanged += cboCustomer_SelectedIndexChanged;
this.cboCustomer.SelectedValueChanged += cboCustomer_SelectedValueChanged;
this.cboCustomer.DataSource = this.bsCustomers;
this.cboCustomer.DisplayMember = "Last Name";
this.cboCustomer.ValueMember = "CustomerID";
The problem exists because the DataSource is set first. The subsequent statements setting the DisplayMember and ValueMember each fire the events (because now the SelectedValue Properties are changed by setting those Members).
There is an easy way to fix this, and perhaps you've already guessed it! Set the DataSource *after* setting the DisplayMember and ValueMember.
this.bsCustomers = new BindingSource();
this.bsCustomers.DataSource = this.oDataFromXML.Tables["Customer"];
this.cboCustomer.SelectedIndexChanged += cboCustomer_SelectedIndexChanged;
this.cboCustomer.SelectedValueChanged += cboCustomer_SelectedValueChanged;
this.cboCustomer.DisplayMember = "Last Name";
this.cboCustomer.ValueMember = "CustomerID";
this.cboCustomer.DataSource = this.bsCustomers;
But wait ... what's going on? Now the code in the event handlers throws an exception!
Value Changed: SelectedIndex is -1
Exception thrown: 'System.NullReferenceException' in WindowsApplication1.exe
That's because, now that we've done the databinding *correctly*, initially nothing is selected (this only happens once) and consequently the SelectedIndex is -1 and the SelectedValue will be null. You *must* have code to check that SelectedIndex is not negative! Especially if you databind the ComboBox to a DataTable that initially has no data in it! When there *is* data, the first item will then be automatically selected and the SelectedIndex will be 0 ... but if there is *no* data, obviously nothing can be selected. The SelectedIndex will be -1 to indicate that.
private void cboCustomer_SelectedValueChanged(object sender, EventArgs e)
{
Console.WriteLine($"Value Changed: SelectedIndex is {cboCustomer.SelectedIndex}");
if (cboCustomer.SelectedIndex > -1)
{
Console.WriteLine($"Value Changed: SelectedValue is {cboCustomer.SelectedValue}, is a {cboCustomer.SelectedValue.GetType()}");
Console.WriteLine("");
// the rest of your code is here
}
}
private void cboCustomer_SelectedIndexChanged(object sender, EventArgs e)
{
Console.WriteLine($"Index Changed: SelectedIndex is {cboCustomer.SelectedIndex}");
if (cboCustomer.SelectedIndex > -1)
{
Console.WriteLine($"Index Changed: SelectedValue is {cboCustomer.SelectedValue}, is a {cboCustomer.SelectedValue.GetType()}");
Console.WriteLine("");
// the rest of your code is here
}
}
And the results now:
Value Changed: SelectedIndex is -1
Value Changed: SelectedIndex is 0
Value Changed: SelectedValue is 01, is a System.String
Index Changed: SelectedIndex is 0
Index Changed: SelectedValue is 01, is a System.String
In a similar vein, I wrote a blog post back in 2012 with the title "ComboBox Gotchas". But, it was about a totally different problem ... a ComboBox that is in sync with other controls on the Form, when it's not supposed to be. See it here, if you're interested: https://geek-goddess-bonnie.blogspot.com/2012/12/combobox-gotchas.html
Very beautiful and useful text, as well as everything from you always
ReplyDeleteVojislav
Thank you, Vojislav!! =0)
DeleteHi Bonnie,
ReplyDeleteIs there a way to contact you by mail.
The Google mail is no longer valid.
With best regards Markus
Markus --- my Google gmail account is still valid. Just to be sure, I tried contacting myself, the same way you would contact me (via the email in my Profile). I received the email just fine. Perhaps your outgoing email is blocking anything sent to a gmail account?
DeleteExcuse me Bonnie, but why you not move the lines of add events after to the assing datasources, DisplayMember, ValueMember?
ReplyDeleteA good question, elihĂș … you could do that, of course. However, often when setting up data sources and event handlers, it is often done before any data is retrieved. Consequently, the DataTable (or whatever you happen to be using for your DataSource) could be empty. In that case, you'd still get a SelectedIndex of -1 … so you should always check for that in your code.
Delete