Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / WinForms

Creating a Custom DropDown Control

4.93/5 (72 votes)
20 Jul 2010CPOL7 min read 213.3K   9.2K  
Explains how to effectively create virtually any type of dropdown control
QuickColor_Preview.JPG

Introduction

This article demonstrates how you can effectively create your own dropdown control with much greater flexibility than by trapping the Paint event of the Windows standard ComboBox. Most of the free custom drop-down controls I've seen are not what they're cracked up to be. Many of them freeze on the screen without closing properly, and some are very bloated with code.

I finally bit the bullet, and decided to create my own control from scratch. Due to time constraints in my schedule, I didn't spend a lot of time developing design-time support, but this current approach works just fine. We will walk through an example of creating QuickColor, the control that you see above. For the scope of this article, I will assume that you are familiar with the concepts of inheritance.

Using the Code

I have designed a base class for inherited controls, appropriately called DropDownControl. This class handles all drop down related functions so we can concentrate on the appearance of our custom control.

The first step in creating our new control is to add a new UserControl to our project, switch to Code View, and set it to inherit the DropDownControl.

C#
public partial class QuickColor : UserControl //<-- replace with DropDownControl { ... } 

After switching back to Design View, the first thing you'll notice is that the control now has a combo box painted at the top. The remaining area below is where you'll design the drop down content.

QuickColor_Blank_Preview.JPG

Figure 1

Let's go under the hood and see how to make it work. In order for the drop down control to work at run time, we must call the InitializeDropDown() method. This should be done immediately after the InitializeComponent() method. The InitializeDropDown method accepts an argument of type Control, which determines the appearance and size of the drop down window. For example, if you pass in a Label control that is 150 pixels wide and 50 pixels tall, the DropDownControl's window size would be set to 150 x 50, and the Label control would be loaded into the dropdown window, as in Figure 2.

C#
public partial class QuickColor : DropDownControl
{
     public QuickColor()
     {
         InitializeComponent();
         InitializeDropDown(label1);
     }
}

Parameter_Example.JPG

Figure 2

Obviously, this simple example wouldn't do much good in a real world scenario, but it should clearly demonstrate how the dropdown window can be initialized. For the example of QuickColor, we need three controls to be loaded into the dropdown window:

  1. "No Color" button
  2. Color Grid
  3. "More Solid Colors" button

Multiple Drop-down Items

How can we have multiple items in the dropdown window if it only accepts one parameter? For multiple controls, you should use a Panel control as a container item. This is what is used to contain the three controls in the QuickColor dropdown window. As you know, the Panel class extends the Control class so it is accepted as a parameter in the InitializeDropDown() method.

Painting the Anchor

It's always a good idea to let the user know what is selected in the drop window. For QuickColor, the user needs to know what color has been selected. There's a DropDownControl property called AnchorClientBounds that contains the coordinates for painting the contents inside the anchor. If the standard ClientRectangle property is used, then the combobox rendering will be covered up. Using AnchorClientBounds ensures that the anchor content will be rendered directly inside the combo box. For QuickColor, I designed a control called ColorPanel that's used in the anchor area. ColorPanel checks the alpha component (transparency) of the color, and (if necessary) draws a PhotoShop like grid in the background so the alpha component will be more obvious. The ColorPanel's bounds is set by the AnchorClientBounds property which, as you can see in the very first image, fits inside the anchor perfectly.

Performing the Drop

When the user clicks anywhere on the anchor, the window automatically drops down. However, if you have a control positioned over the anchor such as the ColorPanel, you must trap its mouse event and force the window to drop programmatically. In the case of QuickColor, we need to override the ColorPanel's Click event to initiate the drop.

C#
private void colorPanel1_Click(object sender, EventArgs e)
{
     OpenDropDown();
}

Closing the Drop Window

If the user clicks anywhere outside the drop window's bounds, the window will automatically close. However, if the mouse click was inside the drop window's bounds, you must determine what action should close the window, and call the CloseDropDown() method. In the case of QuickColor, selecting a color from the color grid closes the drop window.

C#
private void colorGrid1_SelectedIndexChange(object sender, EventArgs e)
{
    this.Color = colorGrid1.Color;
    this.CloseDropDown();
    ...
}

Freezing the Drop Window

There are times when it is necessary to freeze the drop window in its dropped state. Remember the button in the QuickColor drop window called "More Solid Colors"? This button launches a new dialog that allows the user more options in creating a custom color. Let's take a look at the code:

C#
private void btnMoreSolidColors_Click(object sender, EventArgs e)
{
    this.FreezeDropDown(false); // false=drop visibility
    ColorChooser frm = new ColorChooser(this.Color);
    if (frm.ShowDialog() != DialogResult.Cancel && !frm.Color.Equals(this.Color))
    {
        this.Color = frm.Color;
        this.CloseDropDown();
        ...
    }
    this.UnFreezeDropDown();
}

Notice the FreezeDropDown() method. What is this all about? If FreezeDropDown() is not called when opening a new form from the drop window, the drop window will close and kill any form initiated from it. If you're a techno junky and want to understand the details of why you must call this method, read the section below. The rest of you can just skip this (boring) section.

The drop window implements IMessageFilter, and listens in on all application messages that are sent while it is dropped. When a message is created, the drop window uses the static Form method Form.ActiveForm.Equals() to ensure that it is the active form. When another form becomes active, the drop window closes and removes itself as a Windows message listener. Calling the FreezeDropDown() method causes the drop window to suspend active form checking, and forces it to remain open. (Please note that I discourage the IMessageFilter from being used in a control that has a longer lifespan than the dropdown window.)

The DropState Property

In designing complex drop down controls, you may encounter situations where you need to know the state of the dropdown window. There is a property called DropState that you can check at any given time to determine the state of the dropdown window. Note: DropState is read-only, so you cannot change the dropdown window's state with this property.

Below are some thumbnails of more custom drop down controls that are included in the demo project. The DropState property comes in handy when closing these two drop controls. EmployeePicker (Figure 3) closes when the ListView's SelectedIndexChange event fires. However, a problem occurs as the dropdown window is closing and removes the ListView from its control list. The SelectedIndexChange event fires again! This in turn causes the CloseDropDown() method to be called recursively, resulting in an Exception being thrown. To avoid this, we simply check the DropState property in the SelectedIndexChange handler. If the window is Dropping or Closing, we exit the handler.

C#
private void listView1_SelectedIndexChanged(object sender, EventArgs e)
{
    if (this.DropState == eDropState.Dropping || this.DropState == eDropState.Closing)
        return;
    this.CloseDropDown();
    ...
}

AddtionalControls_Preview.JPG

Figure 3

The above controls have been quickly created as demonstrations. Their only purpose is to stimulate your imagination on the possibilities that are at your disposal.

Creating a Property Change Event

The Windows ComboBox control fires the SelectedIndexChanged event to notify listeners that a selection has changed. We also need to fire some kind of event to notify listeners of a selection change. The DropDownControl comes with a generic event, PropertyChanged, but I highly recommend that you create a more meaningful event for your custom control. For QuickColor, I've created a ColorChanged event. If other developers were looking through the events to trap on QuickColor, they would spot the ColorChanged event much faster than the PropertyChanged event. However, if you decide to stick with the PropertyChanged event, you can fire it via the protected method, OnPropertyChanged().

Final Remarks

I trust this article will give you a jumpstart in creating your own drop down controls. If you see any features that could be improved, please let me know. I would also love to hear about any controls that you have created from this article.

Concepts for the PhotoShop like color dialog comes from Danny Blanchard. I made several revisions to the original code to make it more compact and efficient. Great work Danny!

History

  • 7/19/2010: Changed the protected method "GetDropDownLocation()" to "GetDropDownBounds()". This method now validates the drop window's location with a check of its pending Screen bounds. Special thanks to Marco Mastropaolo for the suggestion.
    The drop window now automatically updates its position if the ParentForm is moved. Great suggestion Dan Randolph
  • 11/06/2009: Fixed combo-box rendering problem when the OS does not support VisualStyles. Thanks Mathiyazhagan for bringing this to our attention.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)