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
public partial class QuickColor : UserControl
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.
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.
public partial class QuickColor : DropDownControl
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:
- "No Color" button
- Color Grid
- "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
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
Click event to initiate the drop.
private void colorPanel1_Click(object sender, EventArgs e)
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.
private void colorGrid1_SelectedIndexChange(object sender, EventArgs e)
this.Color = colorGrid1.Color;
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:
private void btnMoreSolidColors_Click(object sender, EventArgs e)
ColorChooser frm = new ColorChooser(this.Color);
if (frm.ShowDialog() != DialogResult.Cancel && !frm.Color.Equals(this.Color))
this.Color = frm.Color;
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.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
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
Closing, we exit the handler.
private void listView1_SelectedIndexChanged(object sender, EventArgs e)
if (this.DropState == eDropState.Dropping || this.DropState == eDropState.Closing)
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
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
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!
- 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.
Richard Blythe is founder and CEO of Unity3 Software.
In his spare time he enjoys flying Cessna 172s, reading, playing his Taylor acoustic guitar and recording music. He's latest non-computer endeavor is to learn violin. (Ouch)