Easily make owner-drawn ComboBoxes display lists of colors or images in C#

There are two kinds of owner-drawn ComboBoxes: first where every item has the same size and second where each item can have a different size.

Same Sized Items

To make all items have the same size, set the control's DrawMode property to OwnerDrawFixed. Then catch the control's DrawItem method and draw whatever you like in the space provided. The event handler's e parameter provides properties and methods to help in drawing the item. The most useful of that objects properties are:

  • BackColor - The item's background color.
  • Font - The item's font.
  • ForeColor - The item's foreground color.
  • Graphics - The Graphics object on which to draw.
  • Bounds - The bounds of the area that this item should occupy.
  • Index - The index of the item in the ComboBox's Items list.
  • State - The item's state. This can be Checked, Edit (the item is in the control's editing area), Default, Disabled, and so forth.

The e parameter's most useful methods are:

  • DrawBackground - Draws the item's background appropriately so it is highlighted if the mouse is over the item.
  • DrawFocusRectangle - Draws a dotted focus rectangle around the item if it should have the focus.

The program catches the DrawItem event and uses the e parameter's properties and methods to draw whatever you need to draw.

When the item size is fixed, you can't change the size of the items. Instead the size is determined by the size of the control on the form. Unfortunately you can't really control that size either. You can change the control's width but it sets its height automatically.

To change the control's height, change the size of its Font property and it will change its height accordingly.

Different Sized Items

To make the ComboBox's items have different sizes, set the control's DrawMode property to OwnerDrawVariable. Then catch the control's MeasureItem event and set each item's size in that event. Use the e parameter's Index property to figure out which item you're measuring. Use the parameter's Height and Width properties to set the desired size.

Then catch the DrawItem event and draw the item as described above.

Easily Displaying Colors

To make displaying colors samples easy, this example adds the following DisplayColorSamples extension method to the ComboBox class.

// Set up the ComboBox to display color samples and their names.
public static void DisplayColorSamples(this ComboBox cbo, Color[] colors)
{
    // Make the ComboBox owner-drawn.
    cbo.DrawMode = DrawMode.OwnerDrawFixed;

    // Add the colors to the ComboBox's items.
    cbo.Items.Clear();
    foreach (Color color in colors) cbo.Items.Add(color);

    // Subscribe to the DrawItem event.
    cbo.DrawItem += cboColorSample_DrawItem;
}

This code sets the ComboBox's DrawMode to OwnerDrawFixed. It clears the control's list of items and adds the desired colors to the list. It finishes by subscribing the following cboColorSample_DrawItem event handler to catch the control's DrawItem event.

// Margins around owner drawn ComboBoxes.
private const int MarginWidth = 4;
private const int MarginHeight = 4;

// Draw a ComboBox item that is displaying a color sample
private static void cboColorSample_DrawItem(object sender, DrawItemEventArgs e)
{
    if (e.Index < 0) return;

    // Clear the background appropriately.
    e.DrawBackground();

    // Draw the color sample.
    int hgt = e.Bounds.Height - 2 * MarginHeight;
    Rectangle rect = new Rectangle(
        e.Bounds.X + MarginWidth,
        e.Bounds.Y + MarginHeight,
        hgt, hgt);
    ComboBox cbo = sender as ComboBox;
    Color color = (Color)cbo.Items[e.Index];
    using (SolidBrush brush = new SolidBrush(color))
    {
        e.Graphics.FillRectangle(brush, rect);
    }

    // Outline the sample in black.
    e.Graphics.DrawRectangle(Pens.Black, rect);

    // Draw the color's name to the right.
    using (Font font = new Font(cbo.Font.FontFamily,
        cbo.Font.Size * 0.75f, FontStyle.Bold))
    {
        using (StringFormat sf = new StringFormat())
        {
            sf.Alignment = StringAlignment.Near;
            sf.LineAlignment = StringAlignment.Center;
            int x = hgt + 2 * MarginWidth;
            int y = e.Bounds.Y + e.Bounds.Height / 2;
            e.Graphics.TextRenderingHint = TextRenderingHint.AntiAliasGridFit;
            e.Graphics.DrawString(color.Name, font,
                Brushes.Black, x, y, sf);
        }
    }

    // Draw the focus rectangle if appropriate.
    e.DrawFocusRectangle();
}

This code is reasonably straightforward. It uses e.DrawBackground to draw an appropriate background for the item. It calculates the item's height minus a top and bottom margin and fills a square of that height with the item's color. It then outlines the square in black.

Next the code creates a font that is 75% as large as the ComboBox's font and draws the color's name to the right.

The code finishes by calling e.DrawFocusRectangle to draw a focus rectangle around the item if appropriate.

Easily Displaying Images

To make displaying images easy, this example adds the following DisplayImages extension method to the ComboBox class.

// Set up the ComboBox to display images.
public static void DisplayImages(this ComboBox cbo, Image[] images)
{
    // Make the ComboBox owner-drawn.
    cbo.DrawMode = DrawMode.OwnerDrawVariable;

    // Add the images to the ComboBox's items.
    cbo.Items.Clear();
    foreach (Image image in images) cbo.Items.Add(image);

    // Subscribe to the DrawItem event.
    cbo.MeasureItem += cboDrawImage_MeasureItem;
    cbo.DrawItem += cboDrawImage_DrawItem;
}

This code sets the ComboBox's DrawMode to OwnerDrawVariable. It clears the control's list of items and adds the desired images to the list. It finishes by subscribing to the control's MeasureItem and DrawItem events.

The following code shows the MeasureItem event handler.

// Measure a ComboBox item that is displaying an image.
private static void cboDrawImage_MeasureItem(object sender, MeasureItemEventArgs e)
{
    if (e.Index < 0) return;

    // Get this item's image.
    ComboBox cbo = sender as ComboBox;
    Image img = (Image)cbo.Items[e.Index];
    e.ItemHeight = img.Height + 2 * MarginHeight;
    e.ItemWidth = img.Width + 2 * MarginWidth;
}

This code gets the ComboBox, gets the image in the item being measured, and returns the image's size plus a margin.

The following code shows the DrawItem event handler.

// Draw a ComboBox item that is displaying an image.
private static void cboDrawImage_DrawItem(object sender, DrawItemEventArgs e)
{
    if (e.Index < 0) return;

    // Clear the background appropriately.
    e.DrawBackground();

    // Draw the image.
    ComboBox cbo = sender as ComboBox;
    Image img = (Image)cbo.Items[e.Index];
    float hgt = e.Bounds.Height - 2 * MarginHeight;
    float scale = hgt / img.Height;
    float wid = img.Width * scale;
    RectangleF rect = new RectangleF(
        e.Bounds.X + MarginWidth,
        e.Bounds.Y + MarginHeight,
        wid, hgt);
    e.Graphics.InterpolationMode = InterpolationMode.HighQualityBilinear;
    e.Graphics.DrawImage(img, rect);

    // Draw the focus rectangle if appropriate.
    e.DrawFocusRectangle();
}

This code uses e.DrawBackground to draw the item's background appropriately. It then gets the ComboBox and the image that it should draw for the item.

The code then makes a RectangleF of the right size to make the available area as determined by e.Bounds. Note that the code doesn't assume it has the size it requested in the MeasureItem event handler. When the code is drawing an item in the dropped-down list, it will have that much space. When the code is drawing the item displayed on the surface of the control, it will have whatever space the control gives it. Notice in the picture shown that the ComboBox on the right is displaying a small version of the highlighted image. (As is the case with a ComboBox with fixed-size items, you can change the control's height by changing its Font size.)

The code sets the Graphics InterpolationMode property to HighQualityBilinear so the image is scaled smoothly if necessary and then draws the image in the RectangleF.

The event handler finishes by using e.DrawFocusRectangle to draw a focus rectangle around the item if appropriate.

Using the Extension Methods

The following code shows how the program uses the extension methods. The bold lines show where the code is using the extension methods.

// Add colors and pictures to the ComboBoxes.
private void Form1_Load(object sender, EventArgs e)
{
    // Colors.
    Color[] colors =
    {
        Color.Red,
        Color.Orange,
        Color.Yellow,
        Color.Green,
        Color.Blue,
        Color.Indigo,
        Color.Purple,
    };
    cboColor.DisplayColorSamples(colors);
    cboColor.SelectedIndex = 0;

    // Faces.
    Image[] images = 
    {
        Properties.Resources.face1,
        Properties.Resources.face2,
        Properties.Resources.face3,
        Properties.Resources.face4,
    };
    cboFace.DisplayImages(images);
    cboFace.SelectedIndex = 0;
    cboFace.DropDownHeight = 200;
}

The code makes an array of Colors and passes it to the first ComboBox's DisplayColorSamples extension method. It then makes an array of Images and passes it to the second ComboBox's DisplayImages extension method. It's that simple.

You can modify the code used by this example to draw just about anything you want for a ComboBox's items. In my next post, I'll show one way you can draw an image and text on the right.

   

 

What did you think of this article?




Trackbacks
  • No trackbacks exist for this post.
Comments
  • No comments exist for this post.
Leave a comment

Submitted comments are subject to moderation before being displayed.

 Name

 Email (will not be published)

 Website

Your comment is 0 characters limited to 3000 characters.