The Windows ComboBox is the most passive aggressive control in the .NET. For very simple basic uses, it's simple to use. However, things get challenging quickly when you go beyond the basics. 

For example, let's say you needed a dropdown offering users one of two ways to ship a package. The following would load a ComboBox control and keep track of the selected value:

comboboxShipping.Items.Add("Fed Ex")
comboboxShipping.Items.Add("UPS")

...
 
DclFld ShippingMethod Type(*String)

// ShippingMethod will container either "Fed Ex" or "UPS"
ShippingMethod = comboboxShipping.text

Upshift to aggressive behavior

But, if you want to track an associated value with the text displayed in a ComboBox, things quickly go south and it shows its aggressive side.

Consider needing to present a list of US state names with corresponding state abbreviations as the associated value. The first challenge is how to assign display text and an associated value to the ComboBox. ASP.NET offers a ListItem class that works well for this purpose, but to use it you have to reference the entire System.Web.UI.WebControls namespace. To avoid that issue, we'll create our own little class that essentially does most of what ASP.NET's ListItem class does:

BegClass ComboboxItem Access(*Public)  
    DclProp Text Type(*String) Access(*Public) 
    DclProp Value Type(*String) Access(*Public) 

    BegConstructor Access(*Public) 
        DclSrParm Text Type(*String) 
        DclSrParm Value Type(*String) 

        *This.Text = Text
        *This.Value = Value         
    EndConstructor 
EndClass

With our ComboBoxItem class available, we can easily load states and abbreviations into a ComboBox like this:

comboboxStates.Items.Add(*New ComboboxItem("Alabama", "AL"))
comboboxStates.Items.Add(*New ComboboxItem("Colorado", "CO"))
comboboxStates.Items.Add(*New ComboboxItem("Georgia", "GA"))
comboboxStates.Items.Add(*New ComboboxItem("Illinois", "IL"))
comboboxStates.Items.Add(*New ComboboxItem("Nevada", "NV"))

Whoops

Alas, it's not all puppies and rainbows when you realize that at runtime the ComboBox displays:

The ComboBox quite happily accepted ComboBox but doesn't know what to display as the ComboBox text. The ComboBox needs to know what properties of the ComboBoxItem class it should use as the display text and the associated value.

The ComboBox has two properties for just this purpose. So it seems that by using this code you should be good to go:

comboboxStates.Items.Add(*New ComboboxItem("Alabama", "AL"))
comboboxStates.Items.Add(*New ComboboxItem("Colorado", "CO"))
comboboxStates.Items.Add(*New ComboboxItem("Georgia", "GA"))
comboboxStates.Items.Add(*New ComboboxItem("Illinois", "IL"))
comboboxStates.Items.Add(*New ComboboxItem("Nevada", "NV"))

// Identify ComboboxItem property to use as display text.
comboboxStates.DisplayMember = "Text"
// Identify ComboboxItem property to use as combobox item value.
comboboxStates.ValueMember = "Value"

Once again, though, at runtime you see this:

Frustrating, right?

Close, but no cigar

The help tells us what's wrong, but it's very subtle and easy to miss. When you look up the help for the DisplayMember property, it says:

The `DisplayMember` property is a string specifying the name of an object property that is contained in the collection specified by the DataSource property. The default is an empty string ("").

Look closely. "DataSource" is in uppercase. That means, that the DisplayMember and ValueMember properties work only when you assign data to the ComboBox's DataSource property, not when you've simply assigned it directly (like we've done so far.) Let's fix it.

The following code loads a List(*of T) strongly typed collection that contains ComboBoxItem instances. This list is assigned to the ComboBox's DataSource property and then the DisplayMember and ValueMember properties are set.

// Needed for the List(*of T) class
Using System.Collections.Generic

...

// Load comboboxStates. 
DclFld States Type(List(*Of ComboboxItem)) New()

States.Add(*New ComboboxItem("Alabama", "AL"))
States.Add(*New ComboboxItem("Colorado", "CO"))
States.Add(*New ComboboxItem("Georgia", "GA"))
States.Add(*New ComboboxItem("Illinois", "IL"))
States.Add(*New ComboboxItem("Nevada", "NV"))

comboboxStates.DataSource = States
// Identify ComboboxItem property to use as display text.
comboboxStates.DisplayMember = "Text"
// Identify ComboboxItem property to use as combobox item value.
comboboxStates.ValueMember = "Value"

Now, finally, we're on the right track.

Five example states are hardcoded in the code above. In production code you'd probably load the list from a file and use a loop over that file to load the list.

With the list of states bound to the ComboBox, it's also easy to get the selected value with this code:

DclFld cbi Type(ComboboxItem)

// Get current selected item as a ComboboxItem. Note the 
// casting (using the *As operator) is important.
cbi = comboboxStates.SelectedItem *As ComboboxItem

// Show the current object's Text property.
MsgBox cbi.Value

This line from above may give you trouble:

cbi = comboboxStates.SelectedItem *As ComboboxItem

*As is AVR's casting operator. The ComboBox's SelectedItem property is of type *Object so to assign a ComboboxItem instance to it the assigned value needs to be cast as a ComboboxItem.

If you're familiar with C#, this line of AVR:

cbi = comboboxStates.SelectedItem *As ComboboxItem

does what this line of C# does:

cbi = (ComboboxItem)comboboxStates.SelectedItem;

What have we learned

To get the most out of the Windows ComboBox, create a ComboboxItem class (or one similar to it) and bind a list of those objects to the ComboBox's DataSource property. Assign properties from the bound objects to govern the display text and its associated value. Then, use the ComboBox's SelectedValue to fetch the selected value.



Please login or create an account to post comments.