ComboBox is a common Windows Forms control, used to present user a set of acceptable values. User then selects one of the values at will or, depending on DropDownStyle property value, adds new value to select.
In particular, when DropDownList value is used for DropDownStyle, user can only select one of the items already added to ComboBox. User cannot alter the list, but must choose one value from a fixed list.
This article explains how to populate DropDownList-styled ComboBox control so that program remains easy to understand and maintain, still helping avoid bugs as much as possible.
Different Ways to Populate DropDownList ComboBox
Basically, a ComboBox is populated in one of two ways:
- By setting DataSource property to appropriate sequential structure (array, list, etc.) which becomes the list of items shown by the control, or
- By inserting items manually into the control's Items collection.
Generally unwelcomed methods of populating ComboBox are:
- Replacing objects with their string counterparts in such a way that SelectedIndex is the only information identifying which object has been selected.
- Adding objects and hoping that their string representations will be properly determined by each item's ToString() method.
The suggested method is to use DataSource and DisplayMember properties to populate ComboBox with desired objects, whenever applicable. Although attractive, this idea might sometimes be trouble, when no suitable DisplayMember can be determined. Two ways of dealing with the problem will be presented further.
Why Not Rely on ToString() Method?
ComboBox exposes collection of objects through its Items property. This may cause temptation to insert any kind of objects into it and let ComboBox show strings returned by ToString() method of each object.
This is not a desirable method because the result of ToString() is not well documented and structured in general case to be included in user interface. It is also susceptible to future changes with or without notice.
Even if you are the one to write the class at hand, you should not write ToString() method with ComboBox in mind. That method is intended to give user friendly information about the contents of an object, rather than specific string suitable for ComboBox - these two intentions may be quite different in practice.
Typical custom implementation of ToString() method returns too much information for a ComboBox. On the other end of the spectrum, default implementation returns almost no usable information, i.e. it returns just a class name which is not really applicable to ComboBox.
Solution #1 - Using Tag Property
When objects do not have a property that can be referred to by DisplayMember, then it is not simple to populate the ComboBox. In any case, strings that represent objects must be calculated using any appropriate algorithm. So we end up with having a collection of objects and a collection (typically array) of strings. The two collections are interrelated in the sense that string representation and reference to a particular item both occupy the same position in their respective collections.
In this situation, ComboBox can be populated by array of strings (Items collection), and original collection, which contains actual objects, can be stored in ComboBox's Tag property. Selected item is now calculated as object contained in Tag property which occupies SelectedIndex position.
Here is an example. Suppose that you wish to list all subdirectories of one directory, recursively, in a ComboBox. It is easy to list subdirectories using System.IO.Directory.GetDirectories() method. However, if you just populate ComboBox with directories listed in that way, you will notice that some of the directory paths are too long to fit into a ComboBox.
Now you may decide to compact paths that are too long using common technique of replacing middle section of the path with ellipsis. However, Directory and DirectoryInfo classes do not expose properties that contain compacted paths. This is because there is no information of how long a path can be - it depends on the rectangle in which it will be rendered and on the font used. To make the long story short, below is the function which compacts path, given size bounds and font used to render text.
(Function that follows would be much simpler without couple of bugs shipped with .NET Framework. Some of those bugs are explained in full detail, including explanation of workarounds, in article "Consequences of .NET String Immutability and Three Related .NET Framework Bugs". Affected places are just noted in the source code with only basic explanations.)
string CompactPath(string path, Size targetSize, Font font)
{
string compactedPath = null;
bool finished = false;
Size curSize = new Size(targetSize.Width, targetSize.Height);
while (!finished) // Function is possibly executed in
{ // more than one iteration to cope
// with a bug in MeasureText function, as described below
compactedPath = string.Copy(path); // Workaround for .NET bug -
// MeasureText would modify both
// compactedPath and path
// if simple compactedPath=path is done.
System.Drawing.Size effectiveSize =
System.Windows.Forms.TextRenderer.MeasureText(compactedPath, font, curSize,
TextFormatFlags.ModifyString |
TextFormatFlags.PathEllipsis);
if (effectiveSize.Width <= targetSize.Width)
{
finished = true; // Modified string fits into target size
// Function will exit after this
}
else
curSize.Width -= (effectiveSize.Width - targetSize.Width + 1) / 2;
// Workaround for a bug - MeasureText uses Windows API PathCompactPathEx
// function to compact path. This function uses GDI, so text measured in GDI+
// may be larger and breach the target bounds. If this happens,
// we will heuristically reduce target size by half of the error (rounded up)
// and then try again
}
int pos = compactedPath.IndexOf('\0'); // Workaround for a bug in MeasureText,
if (pos >= 0) // which leaves C-string terminator in
compactedPath = compactedPath.Substring(0, pos);// compactedPath character buffer and
// leaves incorrect length of compactedPath
return compactedPath;
}
This function will be used then to populate ComboBox (named combo) with compacted directory paths, still maintaining array of System.IO.DirectoryInfo objects in the Tag property. Here is the code:

Sam HobbsPosted Dec 15, 2010, 1:13 PM
Thank you, Zoran. Your article will be useful for me and many others when we need a ComboBox. I think it is unfortunate that the default implementation of ToString returns the fully qualified name of the type of the Object. The documentation of ToString states very clearly that ToString can be overridden "to return values that are meaningful for that type", and I think that is the intended purpose of the method.