Login


Creating a Color Picker with an Owner Draw ComboBox

By Jonathan Wood on 2/11/2011
Language: C#
Technology: .NETWinForms
Platform: Windows
License: CPOL
Views: 26,608
Desktop Development » Controls » User Controls » Creating a Color Picker with an Owner Draw ComboBox

Screenshot of Color Picker

Download Source Code Download Source Code

Introduction

A handy control to have is a color picker, which allows the user to select a color from a list. It's possible to get quite fancy with this type of control. However, it can also be accomplished quite simply.

In this article, I'll present a color picker using an owner-draw combo box. You can create an owner-draw combo box by placing a combo box on your form, setting the DrawMode property, and adding an event handler for the DrawItem event. However, to make the control easier to use in other projects, I'll place it within a custom control.

You could choose to create a user control and just drop a combo box on it. But since we are only modifying the behavior of a combo box and not adding any other components, inheriting from the ComboBox control just seems more straight forward.

The ColorPicker Control

Listing 1 shows my ColorPicker control. The code starts by declaring the ColorInfo class. This class defines the data for each color contained in the list. It contains two properties: the color text and the actual color value.

The ColorPicker constructor sets a couple of property of the base ComboBox class. Since we aren't placing the combo box on a design surface, I am setting these properties programmatically. I set the DropDownStyle property to ComboBoxStyle.DropDownList. This specifies that the user cannot edit text in the control. I also set the DrawMode property to DrawMode.OwnerDrawFixed. This specifies that we will be drawing the list items ourselves, and that each item will be the same height. Finally, I add my custom OnDrawItem handler to the DrawItem event.

The user of the control can add any colors they like, but the AddStandardColors() method can be used to quickly add a number of popular colors. Note that if the user accesses the Items collection directly, nothing will stop them from adding objects of types other than ColorInfo. Ultimately, this will lead to exceptions or undefined behavior. An enhancement to the control might be to replace the Items collection with a type-safe collection. Until then, the programmer bears the responsibility to ensure only objects of type ColorInfo are added to the list of items.

The OnDrawItem() is my DrawItem event handler. This handler is called for each list item that needs to be rendered, including the item that appears in the edit portion of the combo box control. Given that this is the key method for customizing the control, it's really quite simple. It starts by clearing the background and then drawing a small box filled with the color of the corresponding list item. Then it prints the name of the color to the right of the box. Care is taken to correctly handle highlighted items, and items that require the focus rectangle.

Finally, I overrode the SelectedItem, SelectedText, and SelectedValue properties to make them type-safe, and just generally easier to work with when getting or setting color values.

Listing 1: The ColorPicker Control

public partial class ColorPicker : ComboBox
{
    // Data for each color in the list
    public class ColorInfo
    {
        public string Text { get; set; }
        public Color Color { get; set; }

        public ColorInfo(string text, Color color)
        {
            Text = text;
            Color = color;
        }
    }

    public ColorPicker()
    {
        InitializeComponent();

        DropDownStyle = ComboBoxStyle.DropDownList;
        DrawMode = DrawMode.OwnerDrawFixed;
        DrawItem += OnDrawItem;
    }

    // Populate control with standard colors
    public void AddStandardColors()
    {
        Items.Clear();
        Items.Add(new ColorInfo("Black", Color.Black));
        Items.Add(new ColorInfo("Blue", Color.Blue));
        Items.Add(new ColorInfo("Lime", Color.Lime));
        Items.Add(new ColorInfo("Cyan", Color.Cyan));
        Items.Add(new ColorInfo("Red", Color.Red));
        Items.Add(new ColorInfo("Fuchsia", Color.Fuchsia));
        Items.Add(new ColorInfo("Yellow", Color.Yellow));
        Items.Add(new ColorInfo("White", Color.White));
        Items.Add(new ColorInfo("Navy", Color.Navy));
        Items.Add(new ColorInfo("Green", Color.Green));
        Items.Add(new ColorInfo("Teal", Color.Teal));
        Items.Add(new ColorInfo("Maroon", Color.Maroon));
        Items.Add(new ColorInfo("Purple", Color.Purple));
        Items.Add(new ColorInfo("Olive", Color.Olive));
        Items.Add(new ColorInfo("Gray", Color.Gray));
    }

    // Draw list item
    protected void OnDrawItem(object sender, DrawItemEventArgs e)
    {
        if (e.Index >= 0)
        {
            // Get this color
            ColorInfo color = (ColorInfo)Items[e.Index];

            // Fill background
            e.DrawBackground();

            // Draw color box
            Rectangle rect = new Rectangle();
            rect.X = e.Bounds.X + 2;
            rect.Y = e.Bounds.Y + 2;
            rect.Width = 18;
            rect.Height = e.Bounds.Height - 5;
            e.Graphics.FillRectangle(new SolidBrush(color.Color), rect);
            e.Graphics.DrawRectangle(SystemPens.WindowText, rect);

            // Write color name
            Brush brush;
            if ((e.State & DrawItemState.Selected) != DrawItemState.None)
                brush = SystemBrushes.HighlightText;
            else
                brush = SystemBrushes.WindowText;
            e.Graphics.DrawString(color.Text, Font, brush,
                e.Bounds.X + rect.X + rect.Width + 2,
                e.Bounds.Y + ((e.Bounds.Height - Font.Height) / 2));

            // Draw the focus rectangle if appropriate
            if ((e.State & DrawItemState.NoFocusRect) == DrawItemState.None)
                e.DrawFocusRectangle();
        }
    }

    /// <summary>
    /// Gets or sets the currently selected item.
    /// </summary>
    public new ColorInfo SelectedItem
    {
        get
        {
            return (ColorInfo)base.SelectedItem;
        }
        set
        {
            base.SelectedItem = value;
        }
    }

    /// <summary>
    /// Gets the text of the selected item, or sets the selection to
    /// the item with the specified text.
    /// </summary>
    public new string SelectedText
    {
        get
        {
            if (SelectedIndex >= 0)
                return SelectedItem.Text;
            return String.Empty;
        }
        set
        {
            for (int i = 0; i < Items.Count; i++)
            {
                if (((ColorInfo)Items[i]).Text == value)
                {
                    SelectedIndex = i;
                    break;
                }
            }
        }
    }

    /// <summary>
    /// Gets the value of the selected item, or sets the selection to
    /// the item with the specified value.
    /// </summary>
    public new Color SelectedValue
    {
        get
        {
            if (SelectedIndex >= 0)
                return SelectedItem.Color;
            return Color.White;
        }
        set
        {
            for (int i = 0; i < Items.Count; i++)
            {
                if (((ColorInfo)Items[i]).Color == value)
                {
                    SelectedIndex = i;
                    break;
                }
            }
        }
    }
}

My Test Project

The attached test project is a simple form with an instance of the ColorPicker control and a PictureBox control. The form calls the AddStandardColors() method to populate the list of colors. Then it initializes the selected color to Color.White.

When a different color is selected from the color picker control, the background color of the picture box control is updated to the selected color.

Conclusion

And that's all there is to it. Owner draw combo boxes (and list boxes) are quite easy in .NET. Perhaps I'll do a fancier color picker down the road where there are smaller color boxes aligned in some sort of grid. That won't fit within the structure of an owner-drawn combo box. As a result, it will be a lot more work.

But for many purposes, a combo box color picker should work just great.

End-User License

Use of this article and any related source code or other files is governed by the terms and conditions of The Code Project Open License.

Author Information

Jonathan Wood

I'm a software/website developer working out of the greater Salt Lake City area in Utah. I've developed many websites including Black Belt Coder, Insider Articles, and others.

I hike each week with my dogs Suki and Sasha. You can see my hiking blog at Hiking Salt Lake.