Too Many Items In Combo Box: When One Is Just One Too Many

I got to troubleshoot a dumb error message today.  The error was "Too many items in combo box."  The situation was anything but.  I was only adding one item.

So I got it working and I wanted to find out why it happened in the first place.  The error it should have returned was "Value cannot be NULL" because that was the root of the problem.  So here’s a distilled piece of code to illustrate the problem.  Create a form and put a combo box on it.  The code for the form should look like:

Public Class Form1

    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) _ 
        Handles MyBase.Load

        Dim d As New DisplayItem
        ComboBox1.Items.Add(d)

    End Sub

End Class

Public Class DisplayItem
    Public Name As String

    Public Overrides Function ToString() As String
        Return Name
    End Function

End Class

The problem is the combo box is trying to display DisplayItem.Name, because that is what the ToString says to do, but the value of Name is Nothing.  You can fix this by setting the value of Name to String.Empty or something else.  The odd thing is that you can also fix the problem by commenting out the ToString override.  To figure this out, I fired up Reflector and went to see what was going on behind the scenes.

This particular situation is basically bypassing all the safe value checks done when adding an item to a list control.  I suppose Microsoft should add a test case for this scenario, but really, if the programmer is attentive, this shouldn’t happen.  I, naturally, happen to be inattentive.

Behind the scenes of the Add method of the Items collection, the first check is in the AddInternal method.  Since we’re passing in an instance of DisplayItem, it passes that check.  The next step is in the NativeAdd method.  At this point, we’ve done our NULL checks and it is assumed we can convert the object to a string.  This method now calls GetItemText.

GetItemText parses the properties of the object passed in and gets the string value.  If the DisplayMember property is not set, the control uses the ToString value of the object itself.  Because we overrode ToString, the control trusts us and returns the value from ToString, the Name value.  This turns out to be Nothing – Oops!  We’ve already passed the check for Nothing, so this sends bad data to the Win32 API, bubbling a failure error code back to NativeAdd.  If NativeAdd gets anything but a success, it always returns the message “Too Many Items In Combo Box”.  But the real reason is that you snuck a Nothing past the initial validation.

Interestingly, if the DisplayMember is set, and the value of the property is Nothing, it is handled properly in GetItemText.  If we converted Name to a private field and made a public property, then set the DisplayMember of  ComboBox1, it would work.  If your display member is another object that overrides the ToString function, you can get around that check as well and return Nothing, causing a failure.

The simple solution for this error message is to avoid NULL values.  The bottom line is to have .ToString always return a string, never Nothing.