Click here to Skip to main content
15,890,506 members
Articles / Multimedia / GDI+

Customizable ComboBox Drop-Down

Rate me:
Please Sign up or sign in to vote.
4.54/5 (59 votes)
20 Nov 2014MIT3 min read 354.4K   20.2K   199   95
A combobox control with a customizable drop-down

Sample Image

Introduction

This article presents an extension of the .NET ComboBox which provides custom drop-downs. The control can automatically add a resizer grip at the bottom of a drop-down.

Design

Custom drop-down functionality can be achieved using the .NET ToolStripDropDown and ToolStripControlHost classes. These two classes are great because they prevent application forms from becoming inactive during drop-down. The custom popup functionality required by this control has been encapsulated within the class PopupControl (a new addition since the original article posting).

In addition to facilitating this control, the PopupControl class can be used to provide drop-down support in your own controls. The PopupControl also integrates the drop-down resize support (now without the need for a nested resizable container, see below for details).

Previously the drop-down control was resizable by nesting a resizable container. This was an okay solution (because it worked) but it caused some redraw errors during resize operations. The resize functionality has now been significantly improved, and it is now also possible to define which sides of the drop-down are resizable (if any). Resizing can now also be done by dragging the resizable edges of the drop-down. The new resize functionality is achieved by intercepting Win32 hit testing messages, and responding appropriately based upon the set PopupResizeMode property.

The following UML class diagram provides an overview of the presented classes:

CustomDDComboBox/uml-overview.png

Using the Code

As with most controls, this control can be created dynamically at runtime, or by using the Visual C# IDE designer's drag and drop features. During runtime, the property DropDownControl must be assigned to an instance of another control. The assigned control must not be contained elsewhere as this will cause problems.

Most drop-down controls appear better when their borders are removed.

C#
namespace CustomComboDemo
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            // Dynamically created controls.

            // Create grid view control.
            DataGridView gridView = new DataGridView();
            gridView.BorderStyle = BorderStyle.None;
            gridView.Columns.Add("Column1", "Column 1");
            gridView.Columns.Add("Column2", "Column 2");
            gridView.Columns.Add("Column3", "Column 3");
            gridView.Columns.Add("Column4", "Column 4");
            gridView.Columns.Add("Column5", "Column 5");
            this.customComboBox1.DropDownControl = gridView;

            // Create user control.
            UserControl1 userControl = new UserControl1();
            userControl.BorderStyle = BorderStyle.None;
            this.customComboBox2.DropDownControl = userControl;

            // Create rich textbox control.
            RichTextBox richTextBox = new RichTextBox();
            richTextBox.BorderStyle = BorderStyle.None;
            this.customComboBox3.DropDownControl = richTextBox;
        }
    }
}

Points of Interest

When writing the code, I found that dropped-down controls were not retrieving immediate input focus. This problem occurs because even though the drop-down code is ignored during the Win32 message handler, the standard win32 drop-down still appears (1x1 pixels). To avoid this, a timer is created which forces the dropped-down control to be focused after the default win32 drop-down has been focused. Once the timer has done its job, it disables itself.

History

  • 22nd April, 2008: Original version posted
  • 29th April, 2008: Updated download files
    • Fixed a bug that was pointed out by Adam Hearn
  • 17th June, 2008: Various changes
    • Improved design
    • Odd drawing effects during drop-down resize have now been significantly improved
    • Added PopupControl for general custom drop-down support
    • Added download files: Version 2
  • 28th June, 2008: Updated download files for Version 2
  • 15th July, 2008: Updated to version 2.1.
    • Many thanks to member “Leon v Wyk” for this update
    • The improved version now hides incompatible properties from the Visual Studio .IDE properties panel which was causing confusion
  • 21st June, 2009: Updated to version 2.2 
    • Updated source, demo project and UML overview diagram
    • Fixed bug found by CodeProject member “dokmanov”
      Whilst the “DropDownClosed” event was being fired when the drop down arrow was used to close the drop down, it was not being fired when the drop down was closed by clicking in the form area

License

This article, along with any associated source code and files, is licensed under The MIT License


Written By
Software Developer Rotorz Limited
United Kingdom United Kingdom
I have been fascinated by software and video games since a young age when I was given my first computer, a Dragon 32. Since then I have experimented with numerous methods of development ranging from point-and-click type packages to C++. I soon realized that software development was what I wanted to do.

Having invested a lot of time into programming with various languages and technologies I now find it quite easy to pickup new ideas and methodologies. I relish learning new ideas and concepts.

Throughout my life I have dabbled in game and engine development. I was awarded a first for the degree "BEng Games and Entertainment Systems Software Engineering" at the University of Greenwich. It was good to finally experience video games from a more professional perspective.

Due to various family difficulties I was unable to immediately pursue any sort of software development career. This didn't stop me from dabbling though!

Since then I formed a company to focus upon client projects. Up until now the company has primarily dealt with website design and development. I have since decided that it would be fun to go back to my roots and develop games and tools that other developers can use for their games.

We have recently released our first game on iPhone/iPad called "Munchy Bunny!" (see: http://itunes.apple.com/us/app/munchy-bunny!/id516575993?mt=8). We hope to expand the game and release to additional platforms.

Also, check out our tile system extension for Unity! (see: http://rotorz.com/tilesystem/)

Comments and Discussions

 
GeneralRe: DropDownClosed event not firing Pin
Lea Hayes19-Jun-09 21:40
Lea Hayes19-Jun-09 21:40 
GeneralRe: DropDownClosed event not firing Pin
dokmanov20-Jun-09 17:48
dokmanov20-Jun-09 17:48 
GeneralRe: DropDownClosed event not firing Pin
Lea Hayes21-Jun-09 2:31
Lea Hayes21-Jun-09 2:31 
GeneralRe: DropDownClosed event not firing Pin
Lea Hayes21-Jun-09 12:52
Lea Hayes21-Jun-09 12:52 
GeneralRe: DropDownClosed event not firing Pin
debajyoti2005in8-Jul-09 23:34
debajyoti2005in8-Jul-09 23:34 
GeneralRe: DropDownClosed event not firing Pin
Lea Hayes9-Jul-09 0:41
Lea Hayes9-Jul-09 0:41 
QuestionUsing a ListBox? Pin
metator25-Apr-09 9:33
metator25-Apr-09 9:33 
AnswerRe: Using a ListBox? Pin
Lea Hayes26-Apr-09 15:35
Lea Hayes26-Apr-09 15:35 
Hi,

I am glad that you have found my combo box control useful!

One solution might be to create a new control with a name like "ListComboBox" which:

1. Constructs an instance of my combo box class, along with a list box.
2. Associates the list box with the combo box.
3. Provide access to the combo and list box properties that you need.

I have put together an example of how you might do this (see below). Simply put this in a file named "ListComboBox.cs". It works in much the same way as a regular list box, however it reacts more like a regular combo box. You will probably want to make some of your own alterations to this.

Thanks!
Lea Hayes



using System;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Design;
using System.Windows.Forms;

namespace CustomComboDemo
{
    public class ListComboBox : Control
    {
        #region Construction and destruction

        public ListComboBox()
        {
            List = new ListBox();
            
            Combo = new CustomComboBox.CustomComboBox(List);
            Combo.Dock = DockStyle.Fill;
            Controls.Add(Combo);

            List.SelectedIndexChanged += new EventHandler(List_SelectedIndexChanged);
            Combo.TextChanged += new EventHandler(Combo_TextChanged);
            Combo.KeyDown += new KeyEventHandler(ListComboBox_KeyDown);
        }

        #endregion

        #region Events

        void ListComboBox_KeyDown(object sender, KeyEventArgs e)
        {
            switch (e.KeyCode)
            {
                case Keys.Up:
                    if (List.SelectedIndex > 0)
                        --List.SelectedIndex;
                    break;
                case Keys.Down:
                    if (List.SelectedIndex + 1 < List.Items.Count)
                        ++List.SelectedIndex;
                    break;
                case Keys.Home:
                    List.SelectedIndex = List.Items.Count > 0 ? 0 : -1;
                    break;
                case Keys.End:
                    List.SelectedIndex = List.Items.Count - 1;
                    break;
            }
        }

        void List_SelectedIndexChanged(object sender, EventArgs e)
        {
            // Adjust content of combo box text.
            Combo.Text = (List.SelectedItem != null) ? List.SelectedItem.ToString() : "";
            Combo.HideDropDown();
            Combo.Select();
        }

        void Combo_TextChanged(object sender, EventArgs e)
        {
            // Adjust selection within list control.
            List.SelectedIndex = List.FindStringExact(Combo.Text);
        }

        #endregion

        #region Combo Methods

        public void ShowDropDown()
        {
            Combo.ShowDropDown();
        }

        public void HideDropDown()
        {
            Combo.HideDropDown();
        }

        #endregion

        #region Combo Properties

        [Browsable(false)]
        public bool IsDroppedDown
        {
            get { return Combo.IsDroppedDown; }
        }

        [Category("Custom Drop-Down"), Description("Indicates if drop-down is resizable.")]
        public bool AllowResizeDropDown
        {
            get { return Combo.AllowResizeDropDown; }
            set { Combo.AllowResizeDropDown = value; }
        }

        [Category("Custom Drop-Down"), Description("Indicates current sizing mode."), DefaultValue(CustomComboBox.CustomComboBox.SizeMode.UseComboSize)]
        public CustomComboBox.CustomComboBox.SizeMode DropDownSizeMode
        {
            get { return Combo.DropDownSizeMode; }
            set { Combo.DropDownSizeMode = value; }
        }

        [Category("Custom Drop-Down")]
        public Size DropSize
        {
            get { return Combo.DropSize; }
            set { Combo.DropSize = value; }
        }

        [Category("Custom Drop-Down"), Browsable(false)]
        public Size ControlSize
        {
            get { return Combo.ControlSize; }
            set { Combo.ControlSize = value; }
        }

        #endregion

        #region List Events

        public event EventHandler DataSourceChanged
        {
            add { List.DataSourceChanged += value; }
            remove { List.DataSourceChanged -= value; }
        }
        public event EventHandler DisplayMemberChanged
        {
            add { List.DisplayMemberChanged += value; }
            remove { List.DisplayMemberChanged -= value; }
        }
        public event ListControlConvertEventHandler Format
        {
            add { List.Format += value; }
            remove { List.Format -= value; }
        }

        [EditorBrowsable(EditorBrowsableState.Advanced)]
        [Browsable(false)]
        public event EventHandler FormatInfoChanged
        {
            add { List.FormatInfoChanged += value; }
            remove { List.FormatInfoChanged -= value; }
        }
        public event EventHandler FormatStringChanged
        {
            add { List.FormatStringChanged += value; }
            remove { List.FormatStringChanged -= value; }
        }
        public event EventHandler FormattingEnabledChanged
        {
            add { List.FormattingEnabledChanged += value; }
            remove { List.FormattingEnabledChanged -= value; }
        }
        public event EventHandler SelectedValueChanged
        {
            add { List.SelectedValueChanged += value; }
            remove { List.SelectedValueChanged -= value; }
        }
        public event EventHandler ValueMemberChanged
        {
            add { List.ValueMemberChanged += value; }
            remove { List.ValueMemberChanged -= value; }
        }

        #endregion

        #region List Methods

        public string GetItemText(object item)
        {
            return List.GetItemText(item);
        }

        public void ClearSelected()
        {
            List.ClearSelected();
        }

        public int FindString(string s)
        {
            return List.FindString(s);
        }

        public int FindString(string s, int startIndex)
        {
            return List.FindString(s, startIndex);
        }

        public int FindStringExact(string s)
        {
            return List.FindStringExact(s);
        }

        public int FindStringExact(string s, int startIndex)
        {
            return FindStringExact(s, startIndex);
        }

        public int GetItemHeight(int index)
        {
            return List.GetItemHeight(index);
        }

        #endregion

        #region List Properties

        [AttributeProvider(typeof(IListSource))]
        [DefaultValue("")]
        [RefreshProperties(RefreshProperties.Repaint)]
        public object DataSource
        {
            get { return List.DataSource; }
            set { List.DataSource = value; }
        }

        [TypeConverter("System.Windows.Forms.Design.DataMemberFieldConverter, System.Design, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a")]
        [DefaultValue("")]
        [Editor("System.Windows.Forms.Design.DataMemberFieldEditor, System.Design, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", typeof(UITypeEditor))]
        public string DisplayMember
        {
            get { return List.DisplayMember; }
            set { List.DisplayMember = value; }
        }

        [EditorBrowsable(EditorBrowsableState.Advanced)]
        [Browsable(false)]
        [DefaultValue("")]
        public IFormatProvider FormatInfo
        {
            get { return List.FormatInfo; }
            set { List.FormatInfo = value; }
        }

        [MergableProperty(false)]
        [Editor("System.Windows.Forms.Design.FormatStringEditor, System.Design, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", typeof(UITypeEditor))]
        [DefaultValue("")]
        public string FormatString
        {
            get { return List.FormatString; }
            set { List.FormatString = value; }
        }

        [DefaultValue(false)]
        public bool FormattingEnabled
        {
            get { return List.FormattingEnabled; }
            set { List.FormattingEnabled = value; }
        }

        public int SelectedIndex
        {
            get { return List.SelectedIndex; }
            set { List.SelectedIndex = value; }
        }

        [Browsable(false)]
        [DefaultValue("")]
        [Bindable(true)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public object SelectedValue
        {
            get { return List.SelectedValue; }
            set { List.SelectedValue = value; }
        }

        [DefaultValue("")]
        [Editor("System.Windows.Forms.Design.DataMemberFieldEditor, System.Design, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", typeof(UITypeEditor))]
        public string ValueMember
        {
            get { return List.ValueMember; }
            set { List.ValueMember = value; }
        }

        [RefreshProperties(RefreshProperties.Repaint)]
        [DefaultValue(true)]
        [Localizable(true)]
        public bool IntegralHeight
        {
            get { return List.IntegralHeight; }
            set { List.IntegralHeight = value; }
        }

        [Localizable(true)]
        [RefreshProperties(RefreshProperties.Repaint)]
        [DefaultValue(13)]
        public int ItemHeight
        {
            get { return List.ItemHeight; }
            set { List.ItemHeight = value; }
        }

        [Editor("System.Windows.Forms.Design.ListControlStringCollectionEditor, System.Design, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", typeof(UITypeEditor))]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
        [Localizable(true)]
        [MergableProperty(false)]
        public ListBox.ObjectCollection Items
        {
            get { return List.Items; }
        }


        [DefaultValue(false)]
        public bool MultiColumn
        {
            get { return List.MultiColumn; }
            set { List.MultiColumn = value; }
        }

        [Localizable(true)]
        [DefaultValue(false)]
        public bool ScrollAlwaysVisible
        {
            get { return List.ScrollAlwaysVisible; }
            set { List.ScrollAlwaysVisible = value; }
        }

        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        [Browsable(false)]
        public ListBox.SelectedIndexCollection SelectedIndices
        {
            get { return List.SelectedIndices; }
        }

        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        [Bindable(true)]
        public object SelectedItem
        {
            get { return List.SelectedItem; }
            set { List.SelectedItem = value; }
        }

        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        [Browsable(false)]
        public ListBox.SelectedObjectCollection SelectedItems
        {
            get { return List.SelectedItems; }
        }

        public virtual SelectionMode SelectionMode
        {
            get { return List.SelectionMode; }
            set { List.SelectionMode = value; }
        }

        [DefaultValue(false)]
        public bool Sorted
        {
            get { return List.Sorted; }
            set { List.Sorted = value; }
        }

        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public int TopIndex
        {
            get { return List.TopIndex; }
            set { List.TopIndex = value; }
        }

        [Browsable(false)]
        [DefaultValue(false)]
        public bool UseCustomTabOffsets
        {
            get { return List.UseCustomTabOffsets; }
            set { List.UseCustomTabOffsets = value; }
        }

        [DefaultValue(true)]
        public bool UseTabStops
        {
            get { return List.UseTabStops; }
            set { List.UseTabStops = value; }
        }

        #endregion

        #region Properties

        protected CustomComboBox.CustomComboBox Combo { get; private set; }
        protected ListBox List { get; private set; }

        #endregion
    }
}

GeneralRe: Using a ListBox? Pin
metator10-Jul-09 4:24
metator10-Jul-09 4:24 
GeneralRe: Using a ListBox? Pin
Lea Hayes10-Jul-09 14:55
Lea Hayes10-Jul-09 14:55 
QuestionHow do I add this to my project pls? Pin
Member 197457016-Apr-09 3:15
Member 197457016-Apr-09 3:15 
AnswerRe: How do I add this to my project pls? Pin
Lea Hayes26-Apr-09 15:41
Lea Hayes26-Apr-09 15:41 
GeneralRe: How do I add this to my project pls? Pin
Member 197457028-Apr-09 17:25
Member 197457028-Apr-09 17:25 
GeneralRe: How do I add this to my project pls? Pin
Lea Hayes29-Apr-09 12:26
Lea Hayes29-Apr-09 12:26 
GeneralSetting the datasource of the combo box Pin
CraigKniese5-Dec-08 8:53
CraigKniese5-Dec-08 8:53 
GeneralRe: Setting the datasource of the combo box Pin
Lea Hayes9-Dec-08 1:28
Lea Hayes9-Dec-08 1:28 
AnswerRe: Setting the datasource of the combo box Pin
Coarval COOP V25-Sep-14 1:00
Coarval COOP V25-Sep-14 1:00 
GeneralRe: Setting the datasource of the combo box Pin
Dyolf_Knip13-Jan-22 6:21
Dyolf_Knip13-Jan-22 6:21 
GeneralNice - Suggestions Pin
jmcstone3-Dec-08 1:55
jmcstone3-Dec-08 1:55 
GeneralRe: Nice - Suggestions Pin
Lea Hayes9-Dec-08 1:02
Lea Hayes9-Dec-08 1:02 
GeneralEscape issue Pin
Brad Bruce16-Jul-08 0:33
Brad Bruce16-Jul-08 0:33 
GeneralRe: Escape issue Pin
Lea Hayes17-Jul-08 7:32
Lea Hayes17-Jul-08 7:32 
GeneralPlease !! help custom Pin
kristianbr14-Jul-08 7:10
kristianbr14-Jul-08 7:10 
GeneralRe: Please !! help custom Pin
Lea Hayes14-Jul-08 9:37
Lea Hayes14-Jul-08 9:37 
GeneralRe: Please !! help custom Pin
kristianbr20-Jul-08 8:07
kristianbr20-Jul-08 8:07 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.