ComboTreeBox controlThat’s right, exactly what it says on the box. But why do we need a control like this? Well, it’s a need that i’ve come across many times, in fact, and applies when:

  • You have hierarchical data
  • You want the user to make a selection
  • You don’t have enough room to use a TreeView control
  • You don’t expect the selection to be changed often…
  • …but when it is, a dialog box is too clunky and interrupting

In these situations, a regular ComboBox just won’t cut the mustard; it doesn’t show the structure of your data and its drop-down shows every item, whether you like it or not.

I have explored the challenges associated with writing a control like this in Windows Forms at great length, and i’d like to share the common pitfalls as well as the ultimate solution, which revolves around the ToolStripDropDown control.

What is a drop-down, really?

Most people, when considering this question, would immediately tell you that a drop-down is a Form. After all, it exhibits a lot of the behaviour of a form:

  • It can spill over outside the client area of its parent form
  • It can independently process mouse and keyboard input
  • It can appear top-most; other windows do not obscure it

However, this assessment is fundamentally incorrect. Drop-downs do some very un-Form-like things:

  • When they open, they do not take focus away from the parent form
  • When they capture mouse/keyboard input, they do not take focus away from the parent form
  • Interacting with their child controls/items does not switch focus

So, how does one achieve this behaviour? Those last few points seem to represent a drastic departure from the way forms and controls work!

How to get it wrong

With that in mind, here are some great ways to fail at implementing drop-downs in Windows Forms:

Use a Form (ignorantly)

So, you decide to implement your drop-down as a Form. You make it borderless, suppress it from the taskbar, perhaps make it TopMost as well. You might even make it an owned form. Whatever the particulars, your users click the drop-down button and the form is shown. There’s a slight flicker/flash as focus switches to the drop-down’s form. The parent form’s title bar changes colour and its drop-shadow (under Aero) narrows. The user makes their selection via the controls on the drop-down form and it closes. Focus isn’t restored to the parent form. Keyboard input ceases to have any effect, so the user can’t tab to the next control. As soon as they click on the parent form again, focus changes, the title bar changes colour and the drop shadow expands. There might be an unsightly flicker too. And if you think it looks passable, try opening and closing the drop-down several times in quick succession. Overall result? Loads of superfluous clicks and a very poor user experience.

Use a Form and try to be clever

Those who have tried the above (or arrive at the problem possessing a better understanding of WinForms/Win32) will try to solve the initial problem first; that is, that the drop-down’s form takes focus away from the parent form. A well-kept secret about the Form class is that it has a protected property called ShowWithoutActivation, which (in most cases) will skip the activation of the form when it is shown:

protected override bool ShowWithoutActivation {
    get { return true; }
}

In those situations where ShowWithoutActivation has no effect, you can forcefully set the WS_EX_NOACTIVATE flag on control’s style by overriding the CreateParams property:

protected override CreateParams CreateParams {
    get {
        const int WS_EX_NOACTIVATE = 0x08000000;

        CreateParams p = base.CreateParams;
        p.ExStyle |= WS_EX_NOACTIVATE;
        return p;
    }
}

Mind you, these do nothing to prevent the form from gaining focus once it is visible; so merely clicking on your form will bring the whole problem to light again. So, having succeeded in preventing the form from taking focus when it is displayed, the next challenge is to prevent it from ever getting focus. The most commonly-used technique for this is to go all Win32 and start intercepting window messages. Specifically, the response to the WM_MOUSEACTIVATE message (which is sent to the form when it is clicked, but doesn’t have focus) is set to MA_NOACTIVATE (which processes the click without changing focus):

protected override void DefWndProc(ref Message m) {
    const int WM_MOUSEACTIVATE = 0x21;
    const int MA_NOACTIVATE = 0x0003;

    if (m.Msg == WM_MOUSEACTIVATE)
        m.Result = (IntPtr)MA_NOACTIVATE;
    else
        base.DefWndProc(ref m);
}

This actually works; clicking the drop-down’s form will not cause focus to change. The problem is, your drop-down isn’t just going to consist of the form itself. Clicking any control on the form will give focus to the drop-down’s form, undoing all your good work. You can replicate the message interception technique for every control on your form, but some controls (e.g. the TreeView) won’t play ball. Selecting a TreeNode, for example, will always set focus.

So, at the end of all this fuss, you’re left with a series of subclassed controls and low-level code. And what does it get you? A drop-down that will give you a nice user experience… as long as you don’t actually click on it. Ultimately useless.

Use a Control instead

Finally, you might think that using a Control and adding it to the parent form might be a clever way of avoiding issues with focus… and yes, it does completely mitigate that problem. Unfortunately, you’re now left with a drop-down that can’t extend beyond the parent form’s client area. You might think this is fine, so long as your drop-down control has plenty of space below it on the form… but doesn’t that completely defeat the purpose of having a drop-down in the first place? This ‘solution’ is also a dud.

Enter, ToolStripDropDown

In fact, when you look into the behaviours exhibited by drop-downs, it becomes apparent that they are more like menus than forms. Menus can independently process keyboard and mouse input without taking focus away from their parent windows. So, let’s look to the most obvious example in the framework; the ContextMenuStrip control. It’s a Control by inheritance only; in all other respects, it behaves like a Component. Importantly, though, it’s not a Form – and therefore avoids the issues with focus. In fact, menus represent a third class of focus when compared to other elements:

  • Forms take focus from their parent, but also have a separate notion of focus for their controls.
  • Controls take focus from their parent, but surrender it to their children.
  • Menus share focus with their parent and children.

This behaviour represents exactly what we want out of a drop-down, however the ContextMenuStrip has a very specific purpose. Looking into its inheritance chain, we can find ToolStrip, ToolStripDropDown and ToolStripDropDownMenu. The first doesn’t represent a menu, so that’s no use to us. The second is the base class for a drop-down, which makes a good start. It’s worthwhile to note that ToolStripDropDown doesn’t support scrolling (whereas its next descendant does), however with the method i’m going to use to implement my drop-down, automatic scrolling support won’t be necessary.

Any derivative of ToolStripItem (labels, buttons, etc) can be added to a drop-down, including ToolStripControlHost (although i’ve elected not to simply host a TreeView control inside the drop-down – there are still some quirky focus issues in doing this, not to mention the overheads involved). The drop-down itself must contain at least one item, even if it takes up no space. Being a descendant of Control, it can be painted by overriding the OnPaint method and can respond to mouse and keyboard events as well. Combined with the non-focusing behaviour, this gives us all the tools necessary to create a drop-down that behaves as expected.

Implementation

The full functionality of the TreeView is not required for this drop-down control, so a similar (but simplified) data model will form the basis of the implementation.

ComboTreeBox Class Diagram

DropDownBase contains the basic functionality for the editable portion of the control; painting, event handling and design-time support, as well as management of the DroppedDown state. The data model centers around ComboTreeNode, which is akin to TreeNode. The circular relationship between it and its collection type, ComboTreeNodeCollection, creates the tree structure. ComboTreeBox is the primary implementation of DropDownBase. It holds and controls the data model, and visualises the selected node. It also holds ComboTreeDropDown, the actual drop-down (descended from ToolStripDropDown).

In this implementation, the data model is completely separated from the view; nodes can be defined and manipulated independently of the control/drop-down.

Model – ComboTreeNode and ComboTreeNodeCollection

ComboTreeNode is a simple, atomic class used to hold the node’s name, text, state and model its relationship to the other nodes in the tree. ComboTreeNodeCollection represents a sub-tree, and is therefore associated with an owning node. The only exception to this rule is for sub-trees which have not yet been added to the control, and the root collection which belongs to the control. Neither class depends upon ComboTreeBox, however both take on additional properties and behaviours once added to the control.

ComboTreeNodeCollection implements IList<ComboTreeNode>, because the order of the nodes is important. An inner List<ComboTreeNode> is used as the backing store. The collection is responsible for assigning each node’s parent, as well as ensuring that CollectionChanged event (from INotifyCollectionChanged) is fired recursively up the tree:

public void Add(ComboTreeNode item) {
    innerList.Add(item);
    item.Parent = node;

    // changes in the subtree will fire the event on the parent
    item.Nodes.CollectionChanged += this.CollectionChanged;
    OnCollectionChanged(
         new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item)
    );
}

It also implements the non-generic IList to provide compatibility with CollectionEditor, used by the Windows Forms Designer.

View – The drop-down itself

ComboTreeDropDown is responsible for displaying and providing interaction with the data model.Visually, the nodes can be represented as a series of rows, whose bounds are determined by their Depth in the tree, and whose visibility is determined by whether their Parent node is expanded. The visibility of each node is determined by recursively checking the Expanded property of the node’s parents; a node is visible only if the path to the root of the tree contains no collapsed nodes:

internal bool IsNodeVisible(ComboTreeNode node) {
    bool displayed = true;
    ComboTreeNode parent = node;
    while ((parent = parent.Parent) != null) {
        if (!parent.Expanded) {
            displayed = false;
            break;
        }
    }
    return displayed;
}

Rendering

A reasonably simple set of rules govern how the connectors between nodes in the tree are drawn – in fact, it’s possible to isolate the different permutations and cache the resulting bitmaps in order to save on processing and memory (these permutations are represented using the BitmapInfo structure). The superset of visible nodes changes whenever a node is expanded or collapsed, or if nodes are added/removed from the tree. Changes to the size and font of the owning control will also cause the set to be re-evaluated.

Scrolling

Scrolling is implemented manually, on a very simple principle: A contiguous subset of the visible nodes, the size of which is determined by the maximum height of the drop-down, is selected using an offset.

  • The range of the scroll bar is equal to the total number of visible items, minus the size of the largest subset that can be displayed in the drop-down.
  • The position of the scroll bar can be expressed as a percentage of the offset, relative to the range.
  • The size of the ‘thumb’ on the scroll bar is equal to the size of the scroll range as a proportion of the total number of visible items.

To actually render the drop-down, then, the visible nodes within the scroll range are positioned, relative to the top. The visual properties of each node are represented using NodeInfo. There are two painting operations per node; the (cached) bitmap which represents the indentation, node image, glyphs & connectors, and the text on the node. If the number of nodes in the scroll range is less than the total number of visible nodes, the scrollbar is painted too.

The bounds for drawing each node and part of the scroll bar (stored using ScrollBarInfo) are also used when responding to mouse events. If the mouse pointer falls within the bounds of an item, the associated ComboTreeNode can be determined. Clicking the plus/minus glyph beside a node causes it to collapse/expand, whereas clicking anywhere else on the node will change the selection and close the drop-down. (It’s worthwhile to note that some mouse features, such as dragging the scroll bar and holding down a button with auto-repeat behaviour, are somewhat complex and fall outside the scope of this overview. I will, however, mention the most noteworthy quirk – the MouseWheel event for the drop-down can only be handled on the owning control.)

Highlighted item vs. selected node

Each time the drop-down is displayed, it scrolls to (and highlights) the node corresponding to the SelectedNode property of the owning control. The highlighted item is a separate concept to the selected node, as is the case in an ordinary ComboBox‘s drop-down. These rules apply:

  • Moving the mouse over an item causes it to become highlighted, however the selected node does not change unless the item is clicked.
  • Navigating with the keyboard (up/down) changes both the highlighted item and the selected node.
  • Scrolling (with either the mouse wheel, keyboard (page up/page down) or using the scroll bar) changes neither the highlighted or selected items.

Controller – The owning control

ComboTreeBox is the controller in this implementation. In addition to holding the data model and providing access to the view, it exposes operations for manipulating both – bringing the whole concept together into a reusable Windows Forms control. As such, it combines aspects of behaviour from both the ComboBox and TreeView controls:

  • The ability to assign a Name to each node, which can be used to access the node in its collection.
  • Use of ImageIndex/ImageKey to specify an image for each node.
  • Persistence of the Expanded state for each node.
  • SelectedNode property to get/set the user’s selection in the tree.
  • PathSeparator and Path properties to express the selected node as a path string.
  • BeginUpdate()/EndUpdate() methods for bulk adding.
  • ExpandAll()/CollapseAll() methods to manage the tree.
  • Ability to sort the tree (performs a recursive sort, using either a default or custom comparer).

The following additional features are provided:

  • Recursive enumerator (AllNodes) to iterate over the entire tree.
  • Ability to choose whether paths are constructed from node names or text.
  • Ability to set the selected node using the Path property.
  • ExpandedImageIndex/ExpandedImageKey to allow a different image to be displayed for expanded nodes.

The following features from the TreeView control are NOT implemented for ComboTreeBox:

  • Horizontal scrolling (visually unappealing on a drop-down).
  • Checkboxes beside nodes (this would imply multiple selected values).
  • The ability to customise the visual elements or owner-draw the control using events.
  • SelectedImageIndex/SelectedImageKey properties (which, personally, I find useless).
  • Tooltips on nodes, drag-and-drop and other functionality outside the scope of a drop-down.
  • A Sorted property to create an automatically-sorted tree; instead, they can be sorted on-demand.

Final words

The ComboTreeBox is an example of both the need for custom drop-downs, as well as the solution to the problem of creating one in Windows Forms. The ToolStripDropDown component makes it all possible, even though (in this case) it places the burden of implementing the content of the drop-down manually. This was also a very useful learning exercise in writing a custom control which adhered to established conventions for appearance, behaviour and the handling of input. I am particularly pleased with the elegance of the scrolling mechanism and the use of bitmap caching; with these, I was successfully able to populate a drop-down with several hundred thousand nodes without suffering a significant performance hit (even though such volume would be thoroughly unsuitable for a drop-down!).

I hope it proves useful in its own right, as well as in its capacity for demonstrating how to implement a custom drop-down.

Download

Please visit the project page here: Drop-Down Controls

 

104 thoughts on “A ComboBox with a TreeView Drop-Down

  1. 0
    vote

    Hi Brad, thank you for providing such good custom control and wrote details in an clear article. I successfully used the ComboTreeBox on .net framework 3.5. However, the code won’t compile on .net framework 4.0 client profile. Since the deployment size is critical in my application, I cannot switch to full profile. Will you give me some hints on how to transfer your control to support .net framework 4.0 client profile? Ideally, I’d like to see a full working project officially from you as I’m not familiar with designer features. -:)

    Best Regards,
    Wenbin Xu

    Reply
    • 0
      vote

      The .NET Framework 4.0 Client Profile does not include the System.Design assembly, which is used to provide design-time support for the control. It is theoretically possible to remove the run-time dependency on this assembly by moving design-time classes such as DropDownControlDesigner into a separate assembly. The “design-time assembly” targets the full framework, whereas the “run-time assembly” only targets the client profile. You then have to change any instances of the [Designer] attribute so that they specify a string name instead of a Type.

      Bear in mind, however, that I have never tried this before. I am still using Framework 3.5 for most of my projects. You might want to read up on techniques for separating design-time code from run-time code.

      Reply
  2. vote

    Hi Brad, thanks a lot for the instruction. I commented out those “designer…” code and now it works on .net framework 4.0 client profile. Not an ideal solution but helps get my job done. I’ll revisit the code and make it fully .net framework 4.0 client profile compatiable when having time.

    BTW, what should I do if I want the control to support DataBinding?

    Reply
    • vote

      Data binding is more complicated when you are dealing with a hierarchical data source; conventional data binding only works with flat lists. You would have to determine what form your data would take (tree data structure, DataView with a self-referencing foreign key, nested objects, etc) and then write appropriate routines to support them. I did not provide data binding support in my implementation for the simple reason that there is no standard representation for this kind of data in the .NET Framework.

      Reply
  3. vote

    The only problem with this solution is that the combo box renders as a toolstrip combo box and will appear much different than ‘normal’ combo boxes used elsewhere on your form. Take a look at Windows 7 and you’ll see what I mean.

    Reply
    • vote

      Yes, unfortunately there is no easy way to render a Vista/Win7-style combo box in the “button” style used in DropDownList mode. This is also true of any ComboBox control with the OwnerDrawFixed/OwnerDrawVariable option set, so I do not think that it deviates too much from the OS style.

      That said, I have since discovered a much better way to simulate that appearance; the downside, though, is that it will look the same even in future versions of Windows (which may not render in that style). I also learnt how to use buffered painting to recreate the fade-in/fade-out animations used by native controls. I may post a separate article detailing how to use these.

      Reply
  4. Pingback: Converting a CollectionChanged event handler line from C# to VB.net | SeekPHP.com

  5. vote

    I like the control. Would prefer to see a standard combo drop list rendering but I can live with that.
    I remove support for INotifyCollectionChanged because the inclusion of WindowsBase.dll in all my future project is a too much heavy price to pay and it seems to work well anyway.
    Developer Note: If you have a Get/Set property markes as Browsable( false ), please also include the very useful bool ShouldSerializeXxxx() (where Xxxx is the property name). By having that method returning false, it tells the designer not to add initialization code. This prevent the form designer to add a myControl.MySpecialField = (MySpecialType) new MySpecialType(); in all the non browsable properties. Most of the time it cause problem whem the type is not a System type.

    For example:

    ///
    /// Gets or sets the first visible ComboTreeNode in the drop-down portion of the control.
    ///
    [Browsable( false )]
    public ComboTreeNode TopNode
    {
    get
    {
    return dropDown.TopNode;
    }
    set
    {
    dropDown.TopNode = value;
    }
    }

    ///
    /// Determines if the designer should serialize the property TopNode
    ///
    ///
    /// True if it should be serialized, false otherwise
    ///
    private bool ShouldSerializeTopNode()
    {
    return false;
    }

    Reply
    • 0
      vote

      Thanks for your comments, HEMEL. Since .NET Framework 4, INotifyCollectionChanged has been moved from WindowsBase into System, so it no longer incurs any overheads to use. Please have a look at a revisited version of the control here: . I prefer to use the [DesignerSerializationVisibility] attribute to control serialisation instead of adding lots of additional methods to my classes.

      Reply
  6. vote

    Brad,

    Sorry to ask such an elementary question but now that I have the source code, what do I do with it to use it in a project?

    I do quite a bit of C# development but this in not the normal thing I do.

    Maybe an sample project that uses the code would be helpful.

    Regards,
    Jim

    Reply
    • -1
      vote

      Jim: It’s as simple as dropping the source files into a project of your own. After the first successful compile, the ComboTreeBox control will automatically appear in the toolbox in Visual Studio.

      Reply
  7. vote

    Hi dear Brad,
    thanks a lot of your beautiful component.
    I’ve a problem with RightToLeft property of control, that is doesn’t work properly, the drop down list doesn’t move to right side.
    what should I do?

    Best Regards,
    Mohammad Ali

    Reply
    • 0
      vote

      Mohammad: I did not factor RTL support into the design of the control, as I am not familiar with the rules associated with it. You would have to add a condition in the OnPaint() method of the ComboTreeDropDown class to use a different combination of TextFormatFlags if the RightToLeft property was set to true. That will reverse the text, but the scroll bar, expand/collapse glyphs and dotted lines would be unchanged; further changes to the painting method would be required to completely reverse the layout.

      Reply
  8. vote

    I have been able to load your control with data but when I click it during run time it doesn’t make a selection. It highlights the desired option but doesn’t collapse the dropdown nor fill the combobox text property with the user’s selection. Your thoughts??

    Reply
    • 0
      vote

      I can’t reproduce your problem, Maverick. Bear in mind, however, that the dropdown will only collapse if you click on the text associated with the each item; clicking the plus/minus glyph or the image will simply collapse/expand the node.

      Reply
    • 0
      vote

      Hi Michal,

      The process is very similar to that of the built-in Windows Forms TreeView control:

      ComboTreeBox combo = new ComboTreeBox();

      combo.Nodes.Add("Hello"); // you can use this syntax to create a node from a string
      combo.Nodes.Add("NodeName", "NodeText"); // or this syntax to specify a name for the node

      ComboTreeNode node = new ComboTreeNode(); // or this syntax to set additional properties
      node.Name = "Name";
      node.Text = "Text";
      node.FontStyle = FontStyle.Bold;
      combo.Nodes.Add(node);

      // or, to add multiple nodes at a time:
      combo.Nodes.AddRange(new ComboTreeNode[] { node1, node2, node3 });

      node.Nodes.Add("Same syntax"); // to add nodes to an existing node

      // you can also access nodes by their name:
      combo.Nodes["Grandparent"].Nodes["Parent"].Nodes["Child"].Text = "Test";

      I hope that example helps!

      Reply
  9. vote

    Brad,

    Thanks for this control. It almost does what I need but need a little more functionality. One is the ability to Display a Simple DropDownStyle combo box meaning ability to type a value in the drop down text area. The second is to add the autocomplete feature to search through the node collection when the user starts typing to find the matching node. If you can give some pointers on how to add this functionality I would really appreciate it.

    Thanks
    Bo

    Reply
    • vote

      Hi Bo,

      This would actually be quite complicated to implement. By adding the ability to enter text, you would need to change the mouse cursor (depending on the position), render a caret, handle keyboard shortcuts (arrow keys, copy, paste, etc) and more. You also have to resolve the problem of where to create a node if the user enters a custom value; does it automatically become a top-level node, or must the control attempt to interpret a path from the text entered into it?

      Autocomplete would not be that difficult by comparison; it could be implemented using another ToolStripDropDown which is displayed when the user begins to enter text. All that would be needed to find matches in the list of nodes would be some simple string manipulation (and, optionally, LINQ). However, because the control does not easily lend itself to supporting text entry, this may not be worth pursuing.

      Reply
      • +3
        vote

        Thanks for a great control!

        I’ve added a simple ‘auto complete’ that replicates the SuggestAppend behaviour of a standard ComboBox running in DropDownList mode. There is no text entry control, but pressing one or more keys selects and displays the appropriate node from the dropdown tree.

        It seems to do what I require, but if you (or anyone else) has any comments or further improvement I’d be interested.

        In ComboBoxTree.cs I’ve added:

        private Timer _timer;

        protected override void OnKeyPress(KeyPressEventArgs e)
        {
        base.OnKeyPress(e);

        dropDown_KeyPress(this, e);
        }

        private void dropDown_KeyPress(object sender, KeyPressEventArgs e)
        {
        _timer.Enabled = false;

        if (new TimeSpan(DateTime.Now.Ticks – _lastSearch).TotalMilliseconds > 1000)
        _searchString = e.KeyChar.ToString(); // reset search string
        else
        _searchString += e.KeyChar.ToString(); // add to search string

        _lastSearch = DateTime.Now.Ticks;

        _timer.Enabled = true;
        }

        private void timer_Tick(object sender, EventArgs e)
        {
        _timer.Enabled = false;
        AutoCompleteSearch(_searchString);
        }

        private void AutoCompleteSearch(string search)
        {
        ComboTreeNode node = FindMatchingNode(Nodes, _searchString);

        if (node != null)
        {
        SelectedNode = node;
        _dropDown.Open();
        }
        }

        private ComboTreeNode FindMatchingNode(ComboTreeNodeCollection nodes, string search)
        {
        foreach (ComboTreeNode node in nodes)
        {
        if (node.Text.StartsWith(search, StringComparison.OrdinalIgnoreCase))
        {
        return node;
        }

        if (node.Nodes.Count > 0)
        {
        ComboTreeNode childNode = FindMatchingNode(node.Nodes, search);

        if (childNode != null)
        return childNode;
        }
        }

        return null;
        }

        In the constructor I’ve added:

        // autocomplete timer
        _timer = new Timer();
        _timer.Interval = 100;
        _timer.Enabled = false;
        _timer.Tick += new EventHandler(timer_Tick);

        Reply
        • -1
          vote

          Cool, nice work! Just remember to dispose the Timer in the control’s Dispose method 🙂

          Reply
  10. +2
    vote

    Hi Brad, first I would like to compliment you on your tool that you have made. I am using it in a project and am very pleased as I have been looking for this functionality and hadn’t found it until this. One thing I was hoping to find out is that if I want certain nodes to not be selectable (they are the group name and only the nodes below it in the hierarchy do I want to be able to select). If you have time I was wondering if you had any recommendations for me on implementing this. I can provide more details if needed, thanks in advance!

    – Joel

    Reply
    • 0
      vote

      Thanks for the kind words, Joel! What you’re asking would be relatively simple to implement. Each node would require an additional property (‘Selectable’ or ‘Enabled’ or whatever) and this property would have to be inspected in the OnMouseClick() handler for ComboTreeDropDown to determine whether to update the selected node or do nothing. Code would also have to be added to SetSelectedNode() in ComboTreeBox to throw an exception when an unselectable node was passed to the method. You could also enhance the control graphically to avoid highlighting nodes which cannot be selected, but that’s just a visual detail.

      Reply
      • vote

        Thank you for the quick response, I will work on implementing those changes and let you know how it goes. Again, thank you

        Reply
      • vote

        I was able to make the changes to make certain nodes not select-able as well as not highlight-able. Thank you for your tips as they helped me navigate to the correct areas to make the proper changes!

        Reply
    • 0
      vote

      My code is in C# only. However, if you compile it into an class library, you can reference that assembly in any VB.NET project.

      Reply
      • -2
        vote

        Thanks for ur quick reply but Im not able to compile it ,,Im getting errors like
        using System.Windows.Forms.Design.Behavior;
        Error 1 The type or namespace name ‘Behavior’ does not exist in the namespace ‘System.Windows.Forms.Design’ (are you missing an assembly reference?) C:\Users\Administrator\Desktop\WindowsFormsApplication1\WindowsFormsApplication1\ComboTreeBox\DropDownControlBase.cs 10 35 WindowsFormsApplication1

        Im new to programminng…what i have done is copied comboTreebox folder to my project and build the project and I got 7 errors…There is no other forms in this project…because I was testing ur code

        Reply
        • 0
          vote

          As stated in my article, you need to add references to the following assemblies in order to compile the code:
          – System.Design
          – WindowsBase

          If you begin with an empty Visual Studio project, the easiest way to add the references is to right-click the ‘References’ folder in the Solution Explorer, choose ‘Add Reference…’ and tick the boxes beside the two assemblies mentioned above. They are both part of the .NET Framework 3.5 and above.

          Reply
          • vote

            Thanku Thank u thank u so much for your reply….quick reply

            I successfuuly build and populated with the code u above n its working fine !!!
            I did testing by making a c#application

            Now I want to have this in my project which already has lot of forms and is vb.net
            You said to complile it into class library..but How to do that …sorry for such basic question but im a newbie

            and thank u so much for your reply!!!

          • 0
            vote

            If you right-click on the C# project and choose Properties, then click on Application, you’ll see an option called ‘Output type’ – you can change this from ‘Windows Application’ to ‘Class Library’. When you compile the project, it will create a .DLL instead of a .EXE.

            You can then take this file, place it in the same folder as your VB.NET project and then add it as a reference (as in my previous comment, but this time clicking ‘Browse’ instead of ‘.NET’).

            Once you’ve done this, the control should be available in the toolbox (although you might have to build your VB.NET project first).

  11. -1
    vote

    Thanks for ur reply…

    If I shift this project to some other computer will this dll create any problem? I have never worked with windows application so bit worried to use dll and all

    Reply
    • +1
      vote

      As long as you keep the DLL file in the same folder as the project, it should work fine on any computer. When you compile the project, it should automatically place a copy of the DLL in the output folder – and it will also get picked up automatically if you are building a setup project or deploying via ClickOnce.

      As far as portability goes, Visual Studio projects compile to ‘Any CPU’ by default, so it will not matter whether the other computer is 32-bit or 64-bit. In general, VS makes it easy for you to reference other code in your own projects.

      Reply
  12. -1
    vote

    I copied only the dll file to my project ,referenced it and build the project but i cannot find it in toolbox….do i have to copy the cs file also to my project

    Reply
    • +1
      vote

      You may have to right-click the toolbox area, select the ‘Choose Items…’ option and manually browse to the DLL. Visual Studio does not always do this automatically.

      Reply
  13. -1
    vote

    1more question..

    Im populating data from database to combotreebox. How to get the selected node’s txt and key?

    Reply
    • 0
      vote

      If the key is in the form of a string, you can use the node’s Name property to store the key; otherwise, use the node’s Tag property. Depending on your approach, you would retrieve the selected node’s text and key as follows:

      comboTreeBox1.SelectedNode.Text (for the text)
      comboTreeBox1.SelectedNode.Name (for the key, if string-based)
      comboTreeBox1.SelectedNode.Tag (for the key, if another data type)

      Reply
  14. -1
    vote

    Hi bradley,
    how to make a node selected by its text

    normally it can be done as
    treeview.Nodes.Find(“Landline”, True)

    but in combotreebox how to do that
    please help

    Reply
    • +1
      vote

      The TreeNodeCollection.Find() method will return a TreeNode, but not actually select it. The ComboTreeBox does not provide a Find() method, but you can simulate this using LINQ, e.g.:

      comboTreeBox1.SelectedNode = comboTreeBox1.AllNodes.Where(x => x.Text.Equals(“Landline”)).FirstOrDefault();

      (This searches all children, like when you use ‘True’ for the second parameter of the Find() method. To search only within a particular level of the tree, using Nodes instead of AllNodes.)

      Note: The above example is in C# – I don’t know how to write LINQ queries in VB.NET

      Reply
  15. -1
    vote

    what is x in that …i copied that line to my code and it gives x is not declared…I have no idea of linq..plz help

    Reply
    • +2
      vote

      The ‘x’ is part of a lambda expression. Based on my -very limited- knowledge of VB.NET, it may translate to this:

      comboTreeBox1.SelectedNode = comboTreeBox1.AllNodes.Where(Function(x) x.Text = “Landline”).FirstOrDefault()

      If you have any further questions, you will have to research the answers on your own.

      Reply
  16. vote

    I have another question you might be able to answer: where do I control how a selected node appears? In the image at the top of the screen has the select node text of “iodine\liverock\spongefilter” and it is not bolded. For some reason, whatever node I have selected and the text appears at the top is bold. Also, how is the path (node selected is just spongefilter) appearing at the top for the selected node and can the names be separated by a space instead (e.g. “iodine liverock spongefilter”)?

    Reply
    • vote

      The selected node is displayed using the control’s Font property. If you didn’t explicitly set this to a bold font, remember that the designer applies the parent control’s font by default. Whether the path is shown or not is controlled by the ShowPath property. You can control the separator between nodes in the path using the PathSeparator property; the same convention that the ordinary Windows Forms TreeView uses.

      Reply
  17. vote

    hi,
    I find a wrong,When I load Data by this control, the tree is like
    AA1
    BB1
    BB2
    BB3
    AA2
    BB4
    BB5
    BB6
    1) BB1 Selected, drop-down is Open, then Closed.
    2)BB5 Selected, Wrong Happen, ‘Index was out of range. Must be non-negative and less than the size of the collection’
    line 885 NodeInfo info = visibleItems[i];

    Reply
  18. +1
    vote

    Please Email to me,I Send source code to your mail.

    testing :
    When I open the form after the tree node is selected, and then close the form.

    And then reopen the form, and then select the tree node error.
    This error is not often, in general, change the parent node

    Reply
      • vote

        ComboTreeDropDown : scrollOffset

        When the the second ComboTreeBox expand after loading data, scrollOffset the value is not zero???

        Reply
        • -1
          vote

          The code should be mostly compatible with .NET 3.5 – the main difference is that I reference ObservableCollection from System.dll (under .NET 4.0) which used to be in WindowsBase.dll (under .NET 3.5). If you add a reference to WindowsBase in your project, it should compile under .NET 3.5.

          Reply
          • vote

            its still giving error
            Error 2 Default parameter specifiers are not permitted FolderName\DropDownControlsUpdated\BufferedPaint\BufferedPainter.cs 163 93 Controls

            total 4 errors same as above is giving in different lines

          • vote

            You’ll have to compile it with version 4 of the C# compiler (the one that comes with VS 2010). You can still target the 3.5 framework.

  19. Pingback: Drop-Down Controls Revisited | Brad Smith's Coding Blog

  20. vote

    Brad, your control is like a master’s class in how to write a beautiful control!

    I have added Gavin’s extension to allow search although the following line would simply not compile:

    if (new TimeSpan(DateTime.Now.Ticks – _lastSearch).TotalMilliseconds > 1000)
    _searchString = e.KeyChar.ToString(); // reset search string
    else
    _searchString += e.KeyChar.ToString(); // add to search string

    I finally had to remove the time check, increase the timer interval to 1000ms and simply decide whether to append or replace the search text based on whether the timer was enabled or not.

    I note your comment abount destroying the timer, but could not work out where to put this or how to actually destroy the timer…

    Despite this, it works great but my problem is that once the drop down list is displayed, the seach no longer works. Can you suggest how this could be added to the drop down as well?

    Thank you for your excellent contribution, Martin

    Reply
    • vote

      Hi Martin –

      My preferred way to calculate elapsed time would be:
      if (DateTime.Now.Subtract(_lastSearch).TotalMilliseconds > 1000) …
      (this requires _lastSearch to be declared as a DateTime instead) His code still seems syntactically correct, so i’m not sure why it doesn’t compile.

      I have a feeling that the reason why the search no longer functions once the dropdown is open is that keyboard focus moves to the dropdown, hence the KeyPress event on the main control no longer fires. You would have to attach the handler to the KeyPress event on both components in order to make it work properly. (It would not be desirable to return focus to the main control; the user would expect to be able to use the up/down keys to navigate inside the dropdown)

      As for the focus rectangle – there appears to be some inconsistent behaviour in Windows Forms itself here; by setting breakpoints in the PaintContent method, I was able to observe that, when the control is initially painted, the ShowFocusCues property is set to false (hence no focus rectangle is drawn). When the control receives focus by pressing the tab key, the property changes back to true and the rectangle is drawn. According to best practices, no focus rectangle should be drawn on the control unless that property is set to true. I know that the built-in ComboBox control behaves differently, but I cannot explain why. I would not want to write a hacky solution to this problem.

      Reply
    • vote

      I had the same trouble compiling this line:

      if (new TimeSpan(DateTime.Now.Ticks – _lastSearch).TotalMilliseconds > 1000)

      Before I realized it was due to WordPress turning the minus sign into an n-dash. If anyone else is having that issue, try that first.

      I’m also working on some minor changes which include the dropped-down box accepting the keyboard-bases searching as well as improved navigation with arrows (the best way to describe it is I’m making it mimic the native keyboard-input behavior of a TreeView as much as possible).

      When I’m done, would it be possible for me to send this along or otherwise share it somehow?

      Reply
        • vote

          Here’s what I have so far:

          http://www.technitivity.com/sandbox/

          It’s all explained on the page, but the short version is: both the combo box and the dropdown treeview process keyboard input the way normal .NET combobox and treeview controls would.

          I say “so far” because the page up and page down functionality does not work exactly the same. But my guess is not many people would notice. Still, I’d like to clean that up at some point. For now, my component isn’t just good: it’s good enough. 🙂

          Reply
  21. vote

    Sorry, forgot to say that having a little issue with the focus rectangle. When the form containing the control is first loaded and your control is the first in the tab order, no focus rectangle is displayed even though the control has the focus. It is necessary to go to another control and TAB to brinf the focus back to your control to see a focus rectangle. I have tried to get to the bottom of this but failed. Any ideas?

    Thanks, Martin

    Reply
  22. vote

    Hi Bradley, you may remember that I was trying to add a search facility whilst the drop down tree was visible (Gavin’s code for a search before drop down works well). Being a VB programmer, it is proving too tough for me (and converting to VB didn’t work). I am an independent developer with limited resources, but if you could add search to your core code then I would be happy to send some money your way. I very much hope that you can find the time.

    Reply
  23. Pingback: ComboTreeBox Enhancements and New Project Page | Brad Smith's Coding Blog

  24. vote

    Hi Brad
    I was searching a control like that. Many many thanks. I have downloaded your code. But I need some modifications. Can you tell me how will I do that. First of all, my tree data are 4 level nested. Secondly those data will come from database. The table will follow id, parent id relationship. I have done populating tree from database using treeview control. But I do not know how I will use that code in your control.

    Reply
    • vote

      You will be able to populate the control without the need for any modifications.

      The easiest strategy for creating tree nodes from multi-level data is to use an intermediate dictionary to keep track of the parent nodes (the key is the ID, the value is the node):
      1. Sort the records by their depth (top-level nodes first, leaf nodes last).
      2. For each record:
      2.1. Create a tree node.
      2.2. Add to the dictionary.
      2.3. If the parent is empty, add the node to the control.
      2.4. Otherwise, add the node to the parent node (by looking it up in the dictionary).

      The above strategy will work for a tree of any depth.

      Reply
      • vote

        Hi Brad

        Thanks for your reply. I told you that I generated the tree from the database for treeview control. I used the same technique here in your combotreebox control also. And it works.
        I have one problem, in the original treeview control I have been able to change the node color for the node level.
        Example:
        TreeNode node
        if (Node.Level == 3)
        {
        Node.ForeColor = Color.Blue;
        }

        In your control I can find the level using the property Depth. But how will I change the color? I have seen for the ComboTreeNode there is a property FontStyle. I tried to add Color property, but it is not working.
        Another problem, if I want to expand the tree upto first level during initialization, what I have to do. It has the property ExpandAll.
        Finally, once again thanks for your brilliant control.

        Reply
      • vote

        Hi Smith
        I am selecting the node and it is displaying in the combotreebox. Now I have a Reset button which will clear the combotreebox. I have written
        ctbBranch.Text = String.Empty;
        But it is not clearing the control.
        I have one more control i.e. datagridview which will display the record. Now when one record is selected from the datagridview, its details are displayed in several control. One of the control is combotreebox. I have written the following code
        cell = (DataGridViewTextBoxCell)dataGridViewStock.Rows[e.RowIndex].Cells[“BG_BRANCH”];
        cboBankBranch.Text = cell.Value.ToString();
        Here also combotreebox is not assigned new value.
        How will I solve above two problem?

        Reply
    • vote

      Hi Shyamlal,

      You can easily do this using a recursive method, e.g.:

      IEnumerable<ComboTreeNode> GetChildNodes(ComboTreeNode parent) {
          foreach (ComboTreeNode child in parent.Nodes) {
              yield return child;
              foreach (ComboTreeNode grandchild in GetChildNodes(child)) {
                  yield return grandchild;
              }
          }
      }

      Reply
  25. vote

    Keyboard functionality, using F4, Escape, Enter, etc. keys, do not work.

    Excellent control! Excellent article explaining relevant concepts!! However, there are some issues:

    1) F4 key cannot be used to toggle display dropdown, i.e. TreeView.
    2) Escape key cannot be used
    3) When TreeView is in dropped-down state and an item is selected, pressing Enter key won’t hide the drop-down portion.
    4) Up/down arrow keys cannot be used to display drop-down and move selection.

    Reply
    • vote

      Thanks for letting me know, i’ve released an updated version of the drop-down controls here.

      However, both (3) and (4) in your list of issues had already been implemented. Perhaps you weren’t using the latest version at the time.

      Reply
  26. Pingback: DataGridView Column Types for Drop-Down Controls | Brad Smith's Coding Blog

  27. vote

    There is no option to include “Select ALL” in the parent node. Is that possible to do in this control ?

    Reply
    • vote

      Do you mean ‘check all’, as in when ShowCheckBoxes is set to true? If so, you can use the CascadeCheckState property to check all child nodes when the parent node is checked.

      Otherwise, are you talking about multiple selection within the control? If so, the ComboTreeBox does not support multiple selected nodes.

      Reply
      • vote

        I mean if we can put a “Select all ” like in MS excel filter , above all parent nodes and if we click on that checkbox, every options that is in the treeview should be selected.

        Reply
  28. vote

    Bradley Hello, I am Brazilian, congratulations on your art, I am implementing in my project, it is getting great. I am working on the screen usability and am having trouble updating the property ‘Text’ or ‘Node selected’, I would leave empty the component on the screen for the User having to reselect a node, but it does not obey!

    Reply
  29. vote

    I was unable to scroll a ComboTreeDropDown when it was opened, using a mousewheel. My mouse hovers over the ComboTreeDropDown, and using the MouseWheel, nothing would happen. I added the following event:


    ///
    /// Moves the scrollbar in response to the mouse wheel moving .
    ///
    ///
    ///
    protected void ComboTreeDropDown_MouseWheel(object sender, MouseEventArgs e)
    {
    HandledMouseEventArgs he = (HandledMouseEventArgs)e;
    he.Handled = true;

    if (he.Delta > 0 ) {
    ScrollDropDown(-1);
    }
    else {
    ScrollDropDown(1);
    }
    }

    Reply
      • vote

        Out of curiosity; all the mousewheel event handler code I could find was for the ComboTreeBox. What was the usefulness of this? As far as I could tell, one could only scroll with a mousewheel with the ComboTreeDropDown open, by placing the mouse pointer over the ComboTreeBox.

        Reply
        • vote

          The regular Windows Forms ComboBox lets you scroll through the items with the mouse wheel while the dropdown is collapsed. Try it out for yourself! 🙂

          Reply
  30. vote

    Hi,
    It works great for me, but I would like to implement ToolTip.

    do you successfully implement tooltip on nodes now ?

    Thanks

    Reply
    • vote

      I have just released an updated version which adds a ToolTip property to the ComboTreeNode class. Please try it out and let me know if the functionality meets your requirements.

      Reply
  31. vote

    Great controls! I was trying to set the back color for the combo tree box with Visual Style enabled but wasn’t able to. Is this possible?

    Reply
    • vote

      I don’t offer this functionality with the control because the operating system can only render it using the system colors. As visual styles are intended to create a consistent look-and-feel across all applications, changing the background color would go against this principle.

      Reply
  32. vote

    Great work! Thank you for this! Do you happen to know if I can add a FlatStyle property for the ComboTreeBox Normal?

    Reply
    • vote

      This functionality would have to be implemented manually, by adding a FlatStyle property to DropDownControlBase and implementing the rendering logic for the other styles in the bufferedPainter_PaintVisualState event handler. This would not be too difficult, but I cannot offer any guidance as to how the Flat or Popup styles would be implemented.

      Reply
  33. vote

    Is it possible to expand tree clicking on Node instead PLUS if Node is not a leaf?

    for example

    +A
    –A1
    –A2
    +B
    –B1

    if i click on A and not his plus is it possible to expand the tree?

    ty great component

    Reply
    • vote

      The control is designed to allow any node (including non-leaf nodes) to be selected, which is why the dropdown closes when you click on the node. However, it is possible to change the behaviour of the control with a few lines of code so that 1) non-leaf nodes cannot be selected and 2) clicking a non-leaf node expands or collapses the tree:

      Firstly, you want to set the Selectable property on all non-leaf nodes to false:

      foreach (ComboTreeNode node in comboTreeBox1.AllNodes) {
          if (node.Nodes.Any()) node.Selectable = false;
      }
      comboTreeBox1.NodeClick += comboTreeBox1_NodeClick;

      Then, handle the NodeClick event and, if a non-leaf node is clicked, expand/collapse the tree:

      private void comboTreeBox1_NodeClick(object sender, ComboTreeNodeEventArgs e) {
          if (e.Node.Nodes.Any()) {
              comboTreeBox1.BeginUpdate();
              e.Node.Expanded = !e.Node.Expanded;
              comboTreeBox1.EndUpdate();
          }
      }

      (Note that changing the Expanded property while the dropdown is open not does normally cause the view to update, which is why BeginUpdate and EndUpdate are used above.)

      Reply

Leave a reply to Bo Cancel reply

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

required