Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

DataBind ComboBox Failures and More

5.00/5 (12 votes)
4 Feb 2010CPOL7 min read 66.9K   370  
When using DisplayMember / ValueMember on a ComboBox, this problem often occurs.

Introduction

This article takes a deep look at a commonly used .NET construct (DataBinding) step by step. First, the article sets up a simple program that binds an array of objects to a ComboBox using the DisplayMember and ValueMember properties. Next, it shows how this can often fail. After that, we will see ways to fix the problem, and see why there is probably a bug in the control, in Visual Studio, in something.

Background

I discovered this problem quite some time ago, but was never able to determine why it occurred, because it was part of a larger program. So, I decided to write a specific program and article which provides specific details.

  1. How to bind an object's values directly to a ComboBox.
  2. What problems, errors, and failures occur.
  3. Why those problems often occur.

Using the Code

While reading the article, you will see that the code goes through many slight modifications to show you along the way what happens. The attached code is the one which implements the Array and the ToString() override on the Animal class. This will be clearer after reading the article.

The final code is a basic Windows Forms project with one additional class, kept very simple in an effort to create a clear sample of what is happening:

Here is the Animal class:

C#
class Animal
{
    private string commonName;
    private string species;
    private int speciesId;
    // Creates a class (global) randomizer used
    // to generate a unique id for each species
    static Random rnd = new Random();

    public Animal(string inSpecies, string inCommonName)
    {
        // original code
        this.speciesId = rnd.Next();
        species = inSpecies;
        commonName = inCommonName;
    }

    public int SpeciesId
    {
        get
        {
            return speciesId;
        }
    }
    public string Species
    {
        get
        {
            return species;
        }
    }
    public string CommonName
    {
        get
        {
            return commonName;
        }
    }
}

Animal Class Details

The Animal class makes it easy to bind our list of animals to the ComboBox. In our form code where we have the ComboBox, we will create an array of Animals and then bind them to our ComboBox. It will look like the following:

C#
public partial class Form1 : Form
{
    // set up an array to hold some animals
    Animal[] allAnimals = new Animal[10];
    public Form1()
    {
        InitializeComponent();
        // initialize some animals
        InitAnimals();

        
        comboBox1.DisplayMember = "CommonName";
        comboBox1.ValueMember = "SpeciesId";
        comboBox1.DataSource = allAnimals;
    }

    private void InitAnimals()
    {
        //create 3 animals and add them to the array.
        allAnimals[0] = new Animal("Fidelis Caninus", "Dog");
        allAnimals[1] = new Animal("Felinus Catticus", "Cat");
        allAnimals[2] = new Animal("Elephantos Largus", "Elephant");

    }
}

This code will produce an application that binds the animal objects to the ComboBox, and looks like the following:

Image 1Image 2

Second Image: Shows an Empty Item

Please also note that the second image shows the program as it looks when it first starts. The nice thing is that this ComboBox has an empty item. In other words, the user hasn't selected a choice yet. This may not seem important yet, but it will when we attempt to solve the problem below. So, for now, just keep it in mind.

Now, let's add an event handler so that choosing one of those items does something. In Visual Studio, make sure the ComboBox is selected, then switch over to the Events view on the Properties dialog of your form, and add the SelectedIndexChanged event. You'll get a method that looks something like:

C#
private void comboBox1_SelectedIndexChanged_1(object sender, EventArgs e)
{
    Animal currentAnimal = (Animal)comboBox1.SelectedItem;
    MessageBox.Show(this,currentAnimal.Species);
}

When you run the program and choose an item in the combo box, the event handler fires, gets the currently selected item, casts it to an Animal object, and displays the species name of the animal. It'll look like the following, depending upon which item you select:

Image 3Image 4

Why do We Cast the Item?

We cast the item that comes back from the ComboBox, because we told it to store that item in the ComboBox's list, but the ComboBox simply stores it as an object - the mother of all objects. Since we have specific properties that we want from our specific (Animal) object, we cast it back to its real type.

Seems to Work Fine

So, you may notice that it seems to work fine. But there is a problem that some of you may have noticed. The problem: there is an array with some empty elements. I defined the array as a size 10, but I only newed up three Animal objects.

Works: Unless You Change the Order

Okay, you see that the code really does work, so does it matter? For now, let's suppose that it does not matter. But, let's change one line of code and show that it will cause the code to break.

C#
public Form1()
{
    InitializeComponent();
    
    //My private method to create some animals
    InitAnimals();

    comboBox1.DataSource = allAnimals;
    // set the Animal property which will be used as the DisplayMember
    comboBox1.DisplayMember = "CommonName";
    // set the Animal property which will be used as the ValueMember
    comboBox1.ValueMember = "SpeciesId";
}

The Crash

Notice that the only change I made was in moving the line where I set the DataSource: comboBox1.DataSource = allAnimals;. Previously, I set it after I set the DisplayMember and ValueMember. Now, I set it before. Now, when you attempt to run the program, it will instantly crash. You will see something like the following:

Image 5

Why Does it Crash?

To find out why the program crashes, I stepped through the code with the debugger. The following exception gets thrown:

Image 6

But, that doesn't make much sense. Why is it telling me that the value cannot be null? My value isn't null. It's a perfectly good string, which points to the ValueMember SpeciesId of my Animal class. Besides, it works if I put this line later.

Microsoft Documentation On Databinding?

Maybe Microsoft even claims that you have to do this in this order? Actually, they don't. As far as any documentation is concerned, what I've created should be fine. But, it's not.

1st Workaround Solution

The first and most obvious solution is: Don't do that. In other words, just set the DataSource member last and forget about it. But that sounds like a totally magical solution, so let's look for another.

2nd Workaround Solution: Handle (Throw Away) the Exception

Yeah, let's just throw the exception away. I simply wrap the line in a try...catch... which handles the ArgumentNullException. It looks like the following:

C#
try
{
    comboBox1.ValueMember = "SpeciesId";
}
catch (ArgumentNullException ex)
{

}

Does Try...Catch...Work?

It does work. The application stars and everything looks just fine, until I click the ComboBox. See the next two images:

Image 7Image 8

Odd Display Values

What is going on? Why do I now have those odd display values? Where could those odd display values be coming from?

Closer Look at the Animal Class

Hey, remember the ID that I generate in the Animal class? I did that to emulate a value you might get back from a database or something like that. The code that does that looks like this:

C#
public Animal(string inSpecies, string inCommonName)
{
    this.speciesId = rnd.Next();
    species = inSpecies;
    commonName = inCommonName;
}

I do some more analysis and determine that, yes, that is where those values are coming from. But why do they get set as the DisplayValue? And, what happens if I do choose one of those values? The program works as you would (possibly) expect. It looks like this:

Image 9Image 10

3rd Workaround Solution

Okay, how about we try something else? Let's not even set the stupid (yes, I said stupid) comboBox1.ValueMember. Let's just ignore it. Let's just comment that line of code out and try again. Here's the code and the results:

C#
//comboBox1.ValueMember = "SpeciesId";

Again, everything looks fine at first. Then I drop the list and...what? Now, it lists my namespace.className as the DisplayMember. Crazy! But, it still works, if you can call that working.

Image 11Image 12

Image 13

A Clue and ToString()

Well, at least this leads me to a clue. Now, I'm thinking, hey, this is using the default value of my Animal class ToString() method. Let's override the ToString() method and see what I can do. So, I go the Animal class and add the following override method. Notice that the auto-robot-coder (Visual Studio Helper) attempts to add the return base.ToString(); line to my code, but I comment it out. I don't want to do the default behavior.

Instead, I tell it to return the commonName property of my Animal class.

C#
public override string ToString()
{
    return this.commonName;
    //return base.ToString();
}

It Works, But Wait! Should it?

Here's what it looks like now, when I run it.

Image 14Image 15

Image 16

So it works, but I'm not convinced it should. Microsoft tells you that you need to set all three items:

  • comboBox1.DisplayMember = "CommonName";
  • comboBox1.ValueMember = "SpeciesId";
  • comboBox1.DataSource = allAnimals;

But, remember, now I am only setting the DisplayMember. I never even set the ValueMember, because I commented it out. Why don't I need to any more? How can the control distinguish the correct item / value?

Yet Another Workaround Solution

Lose the Array, Use a List (Collection)

So, maybe it's all related to using this array, which has some elements which are still null? Okay, I'll change my code so it uses a Collection. Here's the code:

C#
// set up a collection to hold some animals
List<animal> allAnimals = new List<animal>();
public Form1()
{
    InitializeComponent();
    
    //My private method to create some animals
    InitAnimals();
    // Please note that in this example,
    // the DataSource is set first.
    // However, with a list it will work.
    comboBox1.DataSource = allAnimals;    
    // set the Animal property which will be used as the DisplayMember
    comboBox1.DisplayMember = "CommonName";
    // set the Animal property which will be used as the ValueMember
    comboBox1.ValueMember = "SpeciesId";

}

private void InitAnimals()
{
    //create 3 animals and add them to the array.
    allAnimals.Add(new Animal("Fidelis Caninus", "Dog"));
    allAnimals.Add(new Animal("Felinus Catticus", "Cat"));
    allAnimals.Add(new Animal("Elephantos Largus", "Elephant"));
}

I also altered the Animal class by removing the previously added ToString().

So, now I run the thing and the first thing I see is:

Image 17

Yes, I see that before I see the main form. Obviously, now the SelectedItemChanged event of the combobox is running immediately after I start the program. Why? Where else is this explained? Anywhere? Anyone?

Right after that is displayed, the main form appears and looks like the following:

Image 18

ComboBox Blank Item: It's Gone!

Remember when I asked you to notice that there was a blank item in the ComboBox? I did. Up at the top of this article, I said, "Hey, look, there's a blank item in the combobox, since the user hasn't selected one yet." Well, I had you notice that, so we cold talk about it now. You see, it is gone now.

Hey, you may think it is no big deal. We can get around that. I know, but why isn't anything consistent with this whole binding thing? Isn't this supposed to be easy?

Hoping to Help

I'm hoping to help someone who bumps up against this problem in various places.

History

  • 02.04.2010 - Posted first version of this article and code.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)