About Me

My photo
I'm a colonist who has declared war on machines and intend to conquer them some day. You'll often find me deep in the trenches fighting off bugs and ugly defects in code. When I'm not tappity-tapping at my WMD (also, known as keyboard), you'll find me chatting with friends, reading comics or playing a PC game.

Wednesday, April 15, 2009

Customizing the items in a WinForms ComboBox

The combo box is one of the favoured means by which users can select one from a group of pre-defined options. It provides a neat interface and reduces on-screen clutter. The items in a combo box are strings by default and for most cases, string items are sufficient for presenting data to the user. For example, a list of cities like "Wayanad", "Trivandrum" and "Ernakulam". However, there may be cases where strings are not enough.

What do we have?
We have an application that displays a demographic map of civil conflict. Areas on this map are categorised as high, medium and low risk. High risk areas are characterized by a red colour, medium risk areas are yellow and low risk areas are grey.
Now, these legend colours need to be presented to the user (There are only three colours here but there could be more). Further more, the user might want to select an area of risk and additional data will be presented to the user based on his/her selection.

What do we need?
We need more than just a standard combo box, we need a customized combo box! Let's call this type of combo box a LegendComboBox. A LegendComboBox will display a rectangle with a colour inside it along with a string. This can be accomplished by manually drawing the items inside the combo box instead of letting it be drawn for us.

How do we get what we need?
Before we dive into creating our LegendComboBox, we need to realize the item(s) that this combo box will hold. Obviously, a simple string won't do because we also need to specify the legend colour for that string. In order to store this information, let's create a new class named LegendItem.

//The type of item that can be added to a LegendComboBox.
public class LegendItem
{
public object Data { get; set; }
public Color Color { get; set; }
}

Now, that we know what type of items will be added to the LegendComboBox, let's create the combo box itself.

Step1: We start by deriving our LegendComboBox from System.Windows.Forms.ComboBox.
That way it looks and smells just like a regular combo box.

Step2: In the constructor of LegendComboBox we do two things.
First, we set the DrawMode of this combo box to OwnerDrawVariable. This indicates that we want to manually draw the items in the combo box. Secondly, we register a function with the DrawItem event. This function will be invoked whenever a particular item in the combo box is to be drawn on the screen.

public class LegendComboBox : System.Windows.Forms.ComboBox
{
public LegendComboBox()
{
this.DrawMode = System.Windows.Forms.DrawMode.OwnerDrawVariable;
this.DrawItem += new DrawItemEventHandler(LegendComboBox_DrawItem);
...
}
}

Step3: The next step is to actually define the function that we registered with the DrawItem event of our combo box. This function will have a prototype of:
void LegendComboBox_DrawItem(object sender, DrawItemEventArgs e);

DrawItemEventArgs provides data about the item to be drawn.
This data includes the index of the item to be drawn, the graphics surface on which to do the drawing, and the area within which we can do the drawing.

void LegendComboBox_DrawItem(object sender, DrawItemEventArgs e)
{
e.DrawBackground();
LegendItem currentItem = null;
Rectangle rect = new Rectangle(2, e.Bounds.Top + 2,
e.Bounds.Height, e.Bounds.Height - 4);
try
{
currentItem = (LegendItem)this.Items[e.Index];
}
catch (InvalidCastException)
{
//If the item in the combo box is not of type LegendItem,
//then we just draw the item without the legend colour.
e.Graphics.DrawString(this.Items[e.Index].ToString(), this.Font,
new SolidBrush(this.ForeColor), e.Bounds);

return;
}

//Draw rectangle with legend colour.
e.Graphics.FillRectangle(new SolidBrush(currentItem.Color), rect);
e.Graphics.DrawString(currentItem.Data.ToString(), this.Font,
new SolidBrush(this.ForeColor),
new Rectangle(e.Bounds.X + rect.Width + 2,
e.Bounds.Y, e.Bounds.Width, e.Bounds.Height));
}

That's it. We're done.

How do we use what we've got?
Using the LegendComboBox is pretty straightforward. Create an instance of this class and then add LegendItems to it.

LegendComboBox cboLegend = new LegendComboBox();
cboLegend.Items.Add(new LegendItem { Data = "High Risk", Color = Color.Red });
cboLegend.Items.Add(new LegendItem { Data = "Medium Risk", Color = Color.Yellow });
cboLegend.Items.Add(new LegendItem { Data = "Low Risk", Color = Color.Gray });

A better solution would be to make this LegendComboBox a control that can be used in any WinForms application.

What have we learnt?
Through an example, we've seen that a WinForms combo box can be customized to display more than just string data. As a matter of fact, the DrawItem event is available for the ListBox and TabControl controls as well. Also, we aren't restricted to just drawing shapes for our items, we could even draw images and more.