C#: How to Load a Winform ComboBox or ListBox and have a Unique Value Associated with the Selected Item
When loading a list box or a combo box drop down in a Winform most developers can use the Items property of the Add method to get the individual textual items to be chosen. The problem comes in later when the developer needs to get certain information beyond the text of the item itself. They need to tag a value to the item, so that future processing can commence. (See my post C# Winforms and the Hidden Association Tag for more information on how to load those types of controls.)
That item to be associated could be an integer, a string or even an actual object. This article demonstrates how do that by loading a KeyValuePair object (or a Tuple for .Net 4) into the box where the display text will be shown but it will also carry a payload to allow us to process things in the future.
Loading ComboBox and ListBox
The following code shows how to load the boxes. The goal is to have Alpha, Beta and Gamma as the selectable text and the payload values they will carry to be 5, 6 and 7 respectfully. Here is the code to load.
List<string> displayValues = new List<string>() { "Alpha", "Beta", "Gamma" }; var dict = displayValues.Select( ( item, index ) => new KeyValuePair<string, string>( item, ( index + 5 ).ToString() ) ); cbOne.DisplayMember = lbOne.DisplayMember = "Key"; // Convert the objects to array and the controls // will extract the appropriate values to display // and use for a value. cbOne.Items.AddRange( dict.OfType<object>().ToArray()); lbOne.Items.AddRange( dict.OfType<object>().ToArray());
Explanation
Line 1: Contains our list items to show to the user.
Line 3: Thanks to Linq we enumerate the displayValues list and on each indexed item we add 5 to the that index value. That creates key value pairs of (“Alpha”, (0+5)), (“Beta”, (1 + 5)), (“Gamma”, (2+5)). Note one doesn’t have to use a string as a value it could be a whole new instance of a class.
Line 5-6: We inform the combobox and the listbox that when it processes and creates the items, use “Key” for the display text for the user.
Line 11-12 : We load our combo box an list box accordingly. Note how we have to change the type of the item return to be an array of objects thanks to the OfType<object>() extension. This is done because the boxes take objects and not a specific object. It allows us a greater flexibility.
That loads our controls. Build and run to see the controls with the selected values.
Value Extraction
Now we have a button on the screen which when clicked gets the current item and extracts our value data to use. It takes those values and displays them to labels next to the target controls so we can see it working. Here is the code
private void btShowSelectedValues_Click( object sender, EventArgs e ) { label1.Text = string.Format( "Value: {0}", ((KeyValuePair<string,string>)lbOne.SelectedItem).Value ); label2.Text = string.Format( "Value: {0}", ((KeyValuePair<string, string>)cbOne.SelectedItem ).Value ); }
What is happening is that we get the target selected items and cast them to the key value pair object. Once that is done we can extract the actual value, string in this case and write it to the display. Its that simple.
A Larger Trojan Horse
Ok you say, the example is great and you understand that instead of a string for the Value, you could use a class. But what if you need to have more than one value? If you are using .Net 4 use a Tuple. Here is the same code but with a Tuple<> and three separate objects used instead. Two of the objects will be another class and an enum.
public class OtherClass { public string Action { get; set; } } public enum OpValue { Condor, Eagle, Hawk }
Here is how we load our boxes and setup the Tuple:
List<Tuple<string, OtherClass, OpValue>> myList = new List<Tuple<string, OtherClass, OpValue>>() { { new Tuple<string, OtherClass, OpValue>( "Alpha", new OtherClass() { Action="Max" }, OpValue.Condor )}, { new Tuple<string, OtherClass, OpValue>( "Beta", new OtherClass() { Action="Move" }, OpValue.Hawk )}, { new Tuple<string, OtherClass, OpValue>( "Gamma", new OtherClass() { Action="Reset" }, OpValue.Eagle)} }; cbOne.DisplayMember = lbOne.DisplayMember = "Item1"; // Convert the objects to array and the controls // will extract the appropriate values to display // and use for a value. cbOne.Items.AddRange( myList.OfType<object>().ToArray() ); lbOne.Items.AddRange( myList.OfType<object>().ToArray() );
Here is the button click which will extract our tuple and use the the Action property of our class. Once we have the tuple casted we could use anything…but you know that.
private void btShowSelectedValues_Click( object sender, EventArgs e ) { label1.Text = string.Format( "Value: {0}", ( (Tuple<string, OtherClass, OpValue>)lbOne.SelectedItem ).Item2.Action ); label2.Text = string.Format( "Value: {0}", ( (Tuple<string, OtherClass, OpValue>)cbOne.SelectedItem ).Item2.Action ); }
Here is the final output when run: