DropDownControls demo

About

This is a set of drop-down controls for Windows Forms that add a hierarchical dimension to their list items. They utilise visual styles and the Buffered Paint API to provide consistency with standard controls.

GroupedComboBox

Key features:

  • Grouping
  • Data-binding
  • Sorting
  • Visual style rendering

Extends the standard ComboBox control by adding a GroupMember property (in addition to the familiar DisplayMember and ValueMember properties). This allows you to display the drop-down list items in groups. The grouping functionality uses the PropertyDescriptor mechanism, thus it is compatible with data-binding. Although owner-drawn, the control overcomes the inconsistent visual style problem inherent to the combo box control by using visual style rendering and the Buffered Paint API.

You can use the GroupedComboBox control as a drop-in replacement whenever you need to display list items in groups.

ComboTreeBox

Key features:

  • Tree structure for list items
  • Offers similar features to the TreeView control
    • Nodes indexed by key
    • Node images
    • Font styles
    • Path separator, get/set node by its path
  • Passive drop-down (does not steal focus)
  • Support for multiple selected nodes (using checkboxes)
  • Visual style rendering

This drop-down control is built from the ground up to provide a tree-based data structure instead of a flat list. The nodes are similar to the built-in TreeNode class, offering font styles, images and the ability to index according to the Name property. By using ToolStripDropDown, its popup does not steal focus and provides a seamless user experience. When dropped down, the user can hover and scroll in the usual manner, but clicking the plus/minus glyphs will expand and collapse child nodes. The standard TreeView keyboard shortcuts are also implemented. You can optionally display the full path to the selected node in the main part of the control; the path separator is configurable. A recursive enumerator is also included to assist in walking the nodes.

The control also supports multiple selected items via the use of the ShowCheckBoxes and CheckedNodes properties. In this mode of operation, normal selection rules are suspended and checkbox glyphs are displayed beside each item. You can decide whether parent nodes behave independently of their children or not. As you check individual nodes, the main part of the control displays their concatenated text.

Use the ComboTreeBox control in situations where your list items have an n-depth tree structure, a grouped structure with large numbers of items or even when you have a flat list for which you need multi-select functionality.

Custom DataGridView column types

Also included with the latest version of the project are custom column types (based on the above controls) that can be used in the DataGridView control.

Requirements

  • .NET Framework 3.5 or 4.0 (works with both runtime versions)

Download

If you find my code useful, please consider making a donation.

Current version: 1.0.7

  • ComboTreeBox now supports a DropDownWidth property.
  • Fixed rendering node images for ComboTreeBox if all nodes are depth 0.

Previous version: 1.0.6

  • GroupedComboBox now allows custom sorting via a new SortComparer property.
  • ComboTreeBox now supports tooltips for nodes.
  • ComboTreeDropDown now supports scrolling with the mouse wheel.

Binaries

.NET Framework 4.0 – dropdowncontrols-1-0-7-bin.zip

.NET Framework 3.5 – dropdowncontrols-1-0-7-bin-net35.zip

Source

Visual Studio 2012 solution (includes demo app) – dropdowncontrols-1-0-7-src.zip

Related articles

A ComboBox with Grouping
A ComboBox with a TreeView Drop-Down
Painting Controls With Fade Animations Using the Buffered Paint API
Drop-Down Controls Revisited
DataGridView Column Types for Drop-Down Controls

49 thoughts on “Drop-Down Controls

  1. +4
    vote

    Hello,
    I have an issue with ComboTreBox (checkboxes).
    i.e.
    Initial condition: all nodes are unchecked.
    So, If you create nodes with deep >= 2, where 0 is initial deep, and you check=true one node in deep 0, then only children nodes with deep 1 are checked. However children nodes with deep >= 2 are not checked.
    Could you please look into it??

    Reply
    • +1
      vote

      Thanks for bringing this problem to my attention. The checkbox state was not recursing properly. I’ve released a new version of the project which fixes the issue.

      Reply
      • +2
        vote

        Hello,
        The problem is still here in version 1.0.6, althought it seems that the afterCheck event is not fired when the node’s depth >= 2.
        Thank you.

        Reply
        • vote

          Hello All,

          i think i managed to know where the problem is.

          If you are adding the ComboTreeNodes in the correct order from Up to Down (Parent first and then the Sub-Nodes), then this should be working OK.

          If you are adding the ComboTreeNode in a random way (for example you add the sub-sub-nodes to the sub-node without adding them to the parent first) then this won’t work.

          The correct order is to add the nodes from the Parent to the children not the other way.

          When i tried to add the nodes manually (using the Designer) it didn’t work. When i updated the Code to call the Add function in the order mentioned above, it worked.

          When i added the nodes by code using the correct order it worked as well.

          I hope this helps.

          P.S: Great work by the way, Bradley! 🙂


          Regards,
          Ahmad

          Reply
  2. vote

    Hi Bradley,
    I have one question..

    My code is like this and no problem with databinding bur how can i get selected item data value or group name ?

    ArrayList datas = new ArrayList(new object[] {
    new{Display = “Tiger”, Group= “Female”, Value = 1337532130},
    new{Display = “Tiger”, Group= “Male”, Value = 3},
    new{Display = “Lion”,Group= “Female”, Value = -930376119},
    new{Display = “Lion”,Group= “Male”, Value = 9},
    });

    cmbb.ValueMember = “Value”;
    cmbb.DisplayMember = “Display”;
    cmbb.GroupMember = “Group”;
    cmbb.DataSource = datas;

    Thanks for all;

    Reply
    • vote

      Retrieving the selected value with GroupedComboBox is the same as with the normal ComboBox control; simply use the SelectedValue property (i.e. cmbb.SelectedValue) and cast to the required type.

      To get the group to which the selected item belongs, use the SelectedItem property and then read the value of the Group property. Because you are using an anonymous type, you cannot access the property directly, however under .NET 4 and newer, you can use the dynamic keyword:

      dynamic item = cmbb.SelectedItem;
      Console.WriteLine("The selected group is {0}.", item.Group);

      On .NET 3.5 and older, you can use reflection to achieve the same thing:

      object item = cmbb.SelectedItem;
      Console.WriteLine("The selected group is {0}.", item.GetType().GetProperty("Group").GetValue(item, null));

      Reply
  3. vote

    Hi,

    there is an issue with combobox columns in DataGridView. The problem is that you have to click twice on the cell to open the drop down part. On standard combobox you need to click only once. I suppose this is caused by the controls structure and event firing when clicked on a cell. I’ve tried to figure out how to fix it but I didn’t really find the fix. If you have any idea how to do it, please let me know. Thanks.

    Reply
    • vote

      Hi once again,

      I’ve found a workaround… when placing in DropDownCellBase in
      protected override void OnMouseEnter(int rowIndex)

      DataGridView.CurrentCell = DataGridView.Rows[rowIndex].Cells[OwningColumn.Index];
      you need to click only once on the combobox. Hope this helps you 🙂

      Reply
      • vote

        Using the OnMouseEnter event is not the greatest because if you move your mouse over the cell you lose any selection you previously had.

        Instead, in the DropDownCellBase, change your OnMouseDown override to this:

        protected override void OnMouseDown(DataGridViewCellMouseEventArgs e) {
        base.DataGridView.CurrentCell = base.DataGridView.Rows[e.RowIndex].Cells[base.OwningColumn.Index];

        _wasCurrentCell = (DataGridView.CurrentCellAddress == new Point(e.ColumnIndex, e.RowIndex));
        base.OnMouseDown(e);
        DataGridView.InvalidateCell(e.ColumnIndex, e.RowIndex);
        }

        Reply
    • vote

      I’m sure it could be adapted, but as i’m not familiar with the rules for layouts in other languages, I am not the best person to implement it.

      Reply
  4. vote

    Hi Bradley,
    Great code, really useful. One problem i’m having though. Say you have this order (using your example)
    var groupedItems = new[] {
    new { Group = “Gases”, Value = 1, Display = “Helium” },
    new { Group = “Gases”, Value = 2, Display = “Hydrogen” },
    new { Group = “Gases”, Value = 3, Display = “Oxygen” },
    new { Group = “Gases”, Value = 4, Display = “Argon” },
    new { Group = “Radioactive”, Value = 5, Display = “Uranium” },
    new { Group = “Radioactive”, Value = 6, Display = “Plutonium” },
    new { Group = “Radioactive”, Value = 7, Display = “Americium” },
    new { Group = “Radioactive”, Value = 8, Display = “Radon” },
    new { Group = “Metals”, Value = 9, Display = “Iron” },
    new { Group = “Metals”, Value = 10, Display = “Lithium” },
    new { Group = “Metals”, Value = 11, Display = “Copper” },
    new { Group = “Metals”, Value = 12, Display = “Gold” },
    new { Group = “Metals”, Value = 13, Display = “Silver” }

    };
    And you want the groups to display in that order (Gases->Radioactive->Metals) is this possible or do the groups have to be ordered alphabetical?

    Best Regards,
    Ashley

    Reply
    • vote

      The GroupedComboBox control always sorts both the groups and items alphabetically using the default comparer. The internal items collection is sorted in order to guarantee that each item will appear under the appropriate group heading. Because it uses an unstable sort to do this, it is not possible to preserve the original order of the groups or items. If you comment-out the line in the SyncInternalItems() method containing the call to _internalItems.Sort(this) then you can disable the internal sorting; however, you will have to guarantee that the items in the DataSource are in the correct order (as in your code example) – otherwise you will get unpredictable behaviour.

      I may add support for custom sorting in a future version of the control, but it will have to operate differently to the current implementation.

      Reply
  5. vote

    Hi Bradley,

    Great code. Exactly what, I am trying to implement!!
    I need to a treeview in datagridview column. I tried using your ComboTreeBoxColumn type in datagridview.
    My question is, Can I show checkboxes for this treeview in datagridview? If yes, which property should be used for this?
    I checked showcheckboxes and checknodes, but those are applicable to comboboxtree. Sorry if I am sounding ignorant.

    Reply
    • vote

      Unfortunately, the checkbox options are not yet supported by the ComboTreeBoxColumn type. This is because they would change the meaning/type of the cell’s Value property and I need to devise a sensible way of dealing with that. This is complicated further by the requirement for DataGridView cell values to be convertible to/from strings; if each cell may contain multiple checked values, it is difficult to parse and format these in a consistent way. I will look into this further at a later date and, if possible, update the controls to support these options.

      Reply
  6. vote

    Hello,

    I’m not getting any images in the ComboTreeBox. My code is pretty straightforward:


    Me.cbtbDatabaseItems.Images = Me.ImageList1

    Dim ndX As New ComboTreeNode
    ndX.Name = "Artiestnaam"
    ndX.ImageIndex = 0
    ndX.ExpandedImageIndex = 0
    ndX.Text = "Artiestnaam"

    Me.cbtbDatabaseItems.Nodes.Add(ndX)

    ndX = New ComboTreeNode
    ndX.Name = "Titel"
    ndX.ImageIndex = 1
    ndX.ExpandedImageIndex = 1
    ndX.Text = "Titel"

    Me.cbtbDatabaseItems.Nodes.Add(ndX)

    What am I doing wrong?

    Reply
    • vote

      Looks like you’ve found a bug. You can fix this if you’re compiling from the source, by changing the code in ComboTreeDropDown.cs, line 202 so that it reads:

      // composite the image associated with node (appears at far right)
      g.DrawImage(nodeImage, new Rectangle(
      bmpWidth - nodeImage.Width,
      composite.Height / 2 - nodeImage.Height / 2,
      nodeImage.Width,
      nodeImage.Height
      ));

      Reply
      • vote

        Hi Bradley,

        I mean, I’m not getting any images in the dropdownlist. Once I’ve selected an item, the image is shown in the combobox.

        Regards,
        Coen van Elst

        Reply
  7. vote

    Hi Bradley

    This is a great piece of code, and I am using it on my application. However, I am using this on almost ever screen I build, and some of my treeviews in this combobox are quiet big and take a while to build. How can I template the data in this. I tried declaring it as a public variable and populate it, but when I assign the object to the variable, the object is blank. I also tried populating one object with the data, then telling new combotreebox equal to the primary object, but that did not work either. Any suggestions?

    Thanks in advance
    Bradley Wheeler

    Reply
    • vote

      I’m not sure what you mean by ‘template the data’ – do you mean share a common set of nodes across several controls? If so, the problem here is that each node may only belong to a single collection (and therefore control). This is the way I designed the control, so currently there is no way to do this without modifying the source code.

      The change you would need to make would be to the Nodes property:

      public ComboTreeNodeCollection Nodes {
      get { return _nodes; }
      set {
      if (value == null) throw new ArgumentNullException("value");

      if (_nodes != null) {
      _nodes.AfterCheck -= nodes_AfterCheck;
      _nodes.CollectionChanged -= nodes_CollectionChanged;
      }

      _nodes = value;
      _nodes.CollectionChanged += nodes_CollectionChanged;
      _nodes.AfterCheck += nodes_AfterCheck;
      }
      }

      This will allow you to do something like the following:

      comboTreeBox2.Nodes = comboTreeBox1.Nodes;

      Reply
    • vote

      No, however I should note that ‘select’ fields on Web forms already support grouping. To get tree functionality, however, you would have to develop a completely custom solution.

      Reply
  8. vote

    Hello!
    normally we get comboBox selected item’s value like this:
    myComboBX.selectedValue.
    How to get checked items values in ComboTreeBox (using checkboxes)?

    Reply
    • -1
      vote

      Use the CheckedNodes property to get the checked items. You can inspect the properties of each item (Text, Tag, etc) or use GetFullPath() to get the path of each checked item.

      Reply
  9. vote

    Hi Bradley.

    First of all a great job , congratulations.

    I want to ask if esposible activate the visual effects of GroupedComboBoxCell in DataGridView ?

    Reply
  10. vote

    A really nice control — thank you for sharing! I’m using the ComboTreeBox in a ToolStrip by hosting it in a ToolStripControlHost. This works, but visually it doesn’t look like other toolstrip controls.

    I was wondering, since you started with a ToolStripComboBox, if it’s possible to put all of the code (the “controller” logic) for the ComboTreeBox in the in the ToolStripComboBox too? The entire control would derive from ToolStripComboBox, not just the drop-down. Do you think this is possible? Anything I should look out for before going down this path?

    Reply
    • vote

      Since the control currently extends DropDownBase (which itself just extends Control), you would have to make it extend ToolStripItem instead. This means that you would have to do all the rendering of the control manually as well. The ComboTreeNode and ComboTreeDropDown classes would remain virtually the same, but you would have to significantly rework ComboTreeBox itself.

      Reply
  11. vote

    Hi Bradley,

    there is a small problem with the controls. It’s about events executed after the control was showed. Do folowing tests:
    1. Open GroupedCombobox(list), without selecting anyting, When the combobox is shown, try to open ComboTreeBox(normal) -> it’s impossible to open the ComboTreeBox, you have to click once again – that’s the correct behaviour
    2. Now do the same in other direction, first open ComboTreeBox, and then click on GroupedComboBox – its opened direct – this can cause problems when the dropdown controls are used within DataGridView and nested forms.

    Reply
  12. vote

    Hello,
    Thanks for this great control. I have an issue with mono
    Unhandled Exception:
    System.NullReferenceException: Object reference not set to an instance of an object
    at GroupedComboBox.ToggleStyle () [0x00000] in :0
    at GroupedComboBox.OnDropDownStyleChanged (System.EventArgs e) [0x00000] in :0
    at System.Windows.Forms.ComboBox.set_DropDownStyle (ComboBoxStyle value) [0x00000] in :0
    at System.Windows.Forms.ComboBox..ctor () [0x00000] in :0
    at GroupedComboBox..ctor () [0x00000] in :0
    at (wrapper remoting-invoke-with-check) GroupedComboBox:.ctor ()
    at Test.GroupedToolStripComboBox+ToolStripGroupedComboBox.CreateControlInstance () [0x00000] in :0
    at Test.GroupedToolStripComboBox+ToolStripGroupedComboBox..ctor () [0x00000] in :0
    at (wrapper remoting-invoke-with-check) Test.GroupedToolStripComboBox/ToolStripGroupedComboBox:.ctor ()
    at Test.UI.Main.InitializeComponent () [0x00000] in :0
    at Test.UI.Main.ctor () [0x00000] in :0
    at (wrapper remoting-invoke-with-check) Test.UI.Main:.ctor ()
    at Test.UI.Main.Main () [0x00000] in :0

    I hope you can fix this.

    Reply
    • vote

      It looks like the Mono version of Windows Forms is implemented differently than the official .NET Framework assembly; it is firing the DropDownStyleChanged event in the constructor for the ComboBox class. This should not occur, and does not fire in the Microsoft implementation. My controls are designed for the .NET Framework only, they are untested and unsupported on Mono. To solve this on Mono, you will need to change the OnDropDownStyleChanged method so that nothing happens until after the base class constructor has been called.

      Reply
  13. vote

    Hey Bradley! thanks for this wonderful control.
    However I encouter an issue (I’m on VS2015). I added the dropDownControls project to my solution (It was easier), but an exception shows up saying that ‘DataGridViewPaintParts’ and “MouseButtons” don’t have a definiton for “HasFlag”.
    This exception doesn’t show in the demoApp.
    Do you have an idea where it could come from?
    Thanks a lot.

    Reply
    • vote

      The Enum.HasFlag method was introduced in .NET 4.0, so if your project is targeting an earlier version of the framework then you will need to replace those calls with the equivalent code:
      e.g. value.HasFlag(MouseButtons.Left) can be rewritten as ((value & MouseButtons.Left) == MouseButtons.Left)
      If you are using .NET 3.0 then you can write an extension method to replace the missing method.

      Reply
    • vote

      I have released an updated version (1.0.7) which includes a DropDownWidth property. The dropdown portion must be at least as wide as the control.

      As for horizontal scrollbars, I deliberately chose not to implement these because they are not consistent with good UI design. If the items do not fit horizontally within the dropdown portion, a different type of control (perhaps even a separate dialog box) should be used to display the choices.

      Reply
  14. vote

    Hello Bradley Smith, I hope you are doing well.

    I congratulate you for such great control, I have a detail when dynamically filling the ComboTreeBox, since in each load I should clean the .Text, but the text is loaded with the previous selection … what solution you have to help me. Thank you

    Reply
  15. vote

    Hi Bradley,
    first of all thanks for sharing this great custom control.
    I want to share with you this strange behavior I experienced: I tried to fix the windows 10 blurry problem of windows form (during debug) using the following fix:

    http://crsouza.com/2015/04/13/how-to-fix-blurry-windows-forms-windows-in-high-dpi-settings/#comment-1319

    However after this fix the ComboTreeBox starts rendering in a strange way. Here the link of how the control looks like before and after the blurry fix:
    – Before: https://ibb.co/gEK80k
    – After: https://ibb.co/fyTCRQ

    Do you have any suggestion how to have both working well, or maybe how to better fix the blurry problem?

    Thanks in advance,
    kind regards.

    Note: the blurry problem is not only a visual problem, also sizing of the objects I set at design time changes at run time, this is the reason why I wanted to fix it.

    Reply
    • vote

      Thanks for the feedback. I did not really design these controls with high-DPI support in mind; many of the internal metrics operate on the assumption that the display will always be 96dpi.

      The method you followed is certainly the recommended approach for WinForms apps, so you’re not doing anything wrong in that respect. However, because of the reasons stated above, the ComboTreeBox will require changes in order to look right at different DPIs.

      The reason why the plus/minus glyphs appear too large is that the code in the GenerateBitmap method (in ComboTreeDropDown.cs) uses a fixed 16×16 pixel square as the bounds for the glyph. At 96dpi, the VisualStyleRenderer will draw a small glyph inside this square but, at larger DPIs, it seems that it will allow the glyph to fill the entire square. In theory, this could be fixed by changing the GLYPH_SIZE constant to a smaller number (e.g. 10). The bounds of the glyph would need to be changed slightly as well:
      Rectangle glyphBounds = new Rectangle(indentation + (INDENT_WIDTH / 2 - GLYPH_SIZE / 2), composite.Height / 2 - GLYPH_SIZE / 2, GLYPH_SIZE, GLYPH_SIZE);

      I cannot easily explain why the text is too large, as the height of each item in the dropdown is determined by the size of the font and therefore should always be able to accommodate the text. I was able to improve the fit at larger font sizes by adding an arbitrary amount onto the calculated height:
      _itemHeight = Math.Max(MIN_ITEM_HEIGHT, Font.Height + 3);

      This may go some length to addressing your problems. I wish I could help more, but I do not currently have access to a high-DPI display so I am unable to test these code changes. My preference would be to remove all of the hard-coded metrics and replace them with values that scaled properly with the DPI of the display.

      Reply
  16. vote

    Thanks for the prompt response.
    Justo to complete the picture, the problem I’m experiencing is on Windows 10, display scaling level to 125%, 140 dpi monitor (is a 15.6″ full HD on laptop).
    If I bring back the scaling level to 100% the control works well even at 140 dpi. The problem seems to be the scaling, by the way also Visual Studio native controls such as data grid view have some strange behavior while setting dimension.

    Thanks & regards.

    Reply
  17. vote

    At first thank you, yours controls are very usefull

    I have just a question. The control for Datagridview need “double-click” for appear list.
    Is there any simple solution for change it on simple-click?

    Thank you & regards

    Reply
    • vote

      This behaviour is consistent with the built-in DataGridViewComboBoxColumn, which requires one click to place the cell into ‘edit mode’ and a second click to drop down the list. You can change the behaviour for all columns in the grid by setting the EditMode property of the DataGridView to EditOnEnter.

      Reply

Leave a reply

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong> 

required