Click here to Skip to main content
15,867,308 members
Articles / Programming Languages / C#

DropDownPanel

Rate me:
Please Sign up or sign in to vote.
4.87/5 (26 votes)
11 Feb 2007Public Domain4 min read 120.6K   2K   107   30
A template for custom ComboBoxes

Image 1

Introduction

Controls like the MultiColumnCombo, the TreeCombo, the better DateTimePicker or similar can be found in all sorts of implementations on almost every programming site. All of these implementations have their pros and cons, so it can be very hard to choose the best for your purpose and it gets even harder as soon as you have to mix them in your particular application. Since they tend to be designed in a slightly different way, it could well happen that you end up with an application where each of those specialized ComboBoxes has its own custom behavior. This can make your application quite irrational for your users to work with.

That's why in my daily work the requirement arose to build something like a library or template as basis for custom combos. There might be bugs in it, at least in the early versions, there might be better solutions around for some particular combo behavior, but all combos created from that template will at least have consistent base behavior. New features as well as bug fixes will have to be implemented only once.

Background

Writing a decent ComboBox clone is not all that sophisticated, but can become complex enough once you start to take the details into account. Displaying a modal form instead of the drop-down area is fairly easy, but having the hosting form appear focused requires some extra care. Fortunately, someone a lot cleverer than me, Steve McMahon, the guy who runs vbaccelerator.com, already published a decent solution on popup windows in .NET, so that all I needed to do was to take his excellent work and just add a little bit of my own code. Because I needed to make Steve's code fit into a larger application infrastructure, I had to refactor the original class names, but apart from that, Steve's code was left next to unchanged.

Using the Code

DropDownPanel is a simple UserControl hosting a ComboBox and exposing the interface IDropDownAware and a single property DropDownControl, that again is of type IDropDownAware. To enable any control to show up in the drop-down area of the DropDownPanel, all you have to do is implement two interfaces (one for the control itself and one for the value it exposes) and set the DropDownPanel's DropDownControl to an instance of it.

The DropDownPanel then takes care to host your control in its internal DropDownForm, that pops up instead of the drop-down area of its ComboBox whenever the combo's DropDown event is triggered. Firing the FinishEditing event from your control or a mouse click outside the DropDownForm will cause the DropDownForm to close and subsequently the DropDownPanel to fire its own FinishEditing event to let your application decide the next steps.

IDropDownAware

This interface enables the DropDownPanel to expose its value and provides 2 events, one that indicates that the user is done with the dropdown control and one that indicates changes while editing is in process. Possibly the custom EventArgs will be extended in future versions as the necessity arises.

C#
public interface IDropDownAware
{ 
    event DropDownValueChangedEventHandler FinishEditing;
    event DropDownValueChangedEventHandler ValueChanged;
    
    object Value { get; set; }
}

So let's start creating a very basic implementation of a DropDownTree as can be seen in this article's screenshot. We derive a class from TreeView and implement IDropDownAware. Of course, some events have to be handled to inform the DropDownPanel when we think the user has chosen a TreeNode or canceled editing.

C#
internal class DropDownTree : TreeView, IDropDownAware
{
    public DropDownTree()
    {
    }

    protected override void OnAfterSelect(
        TreeViewEventArgs e)
    {
        base.OnAfterSelect(e);
        if (ValueChanged != null)
            ValueChanged(this, new 
            DropDownValueChangedEventArgs(e.Node));
    }
        
    protected override void OnDoubleClick(EventArgs e)
    {
        base.OnDoubleClick(e);        
        TreeNode node = HitTest(PointToClient(Cursor
            .Position)).Node;
        if (FinishEditing != null)
        FinishEditing(this, new 
            DropDownValueChangedEventArgs(node));
    }

    protected override void OnKeyUp(KeyEventArgs e)
    {
        base.OnKeyUp(e);
        if (FinishEditing != null)
        {            
            switch (e.KeyCode)
            {
                case Keys.Enter:
                    FinishEditing(this, new 
                      DropDownValueChangedEventArgs(Value));
                    break;
                case Keys.Escape:
                    FinishEditing(this, new 
                      DropDownValueChangedEventArgs(null));
                    break;
            }
        }
    }
    
    // IDropDownAware Implementation
    public event DropDownValueChangedEventHandler FinishEditing;
    public event DropDownValueChangedEventHandler ValueChanged;

    public object Value
    {
        get { return base.SelectedNode; }
        set 
        { 
            if (value is TreeNode)
                base.SelectedNode = value as TreeNode; 
        }
    }
}

ILookupItem<T>

Since I like the new object databinding features in .NET 2.0, I use them extensively throughout my projects. So I had this interface already available in my little toolbox, which is why I decided to reuse it here as well.

To keep the control as simple as possible, I set the internal combo's DropDownStyle to DropDownList to prevent text editing in the ComboBox itself and instead allow editing in the drop-down portion of the control only. When DropDownPanel's value should change after editing your control (i.e., your control fires the FinishEditing event), the combo's DataSource has to be cleared and the new value added to the combo's DataSource in order to display the new value.

Possibly in future versions of this control, text editing features will be added, which could then make this simple interface obsolete. Don't get excited, I am afraid that whatever will replace it will be a lot more complex.

C#
public interface ILookupItem<T> where T: struct
{
    T      Id   { get; }
    string Text { get; }
}

We better not populate the DropDownTree with standard TreeNodes but with objects derived from it instead. We just add an implementation of our ILookupItem<T>:

C#
internal class DropDownNode : TreeNode, ILookupItem<long>
{
    public DropDownNode() : base()
    {
    }
        
    public DropDownNode(string Text) : base(Text)
    {
    }
        
    public DropDownNode(string Text, DropDownNode[] 
        Children) : base(Text, Children)
    {
    }
        
    public DropDownNode(string Text, int ImageIndex, int 
                                    SelectedImageIndex) 
                                    : base(Text, ImageIndex,
                                    SelectedImageIndex)
    {
    }
        
    public DropDownNode(string Text, int ImageIndex, 
                        int SelectedImageIndex, 
                        DropDownNode[] Children) 
                        : base(Text, ImageIndex, 
                            SelectedImageIndex, Children)
    {
    }

    // ILookupItem<long> Implementation
    public long Id
    {
        get { return 0; }
    }
    
    public new string Text
    {
        get { return base.Text; }
    }
}

Having done this, all that is left to do, is to set up the DropDownTree and to push it into the DropDownPanel from within our application:

C#
protected override void OnLoad(EventArgs e)
{
    base.OnLoad(e);
    treePanel.DropDownControl = CreateTree(); 
}

private DropDownTree CreateTree()
{
    DropDownTree tree = new DropDownTree();
    DropDownNode root = new DropDownNode("1");
            
    tree.BorderStyle = BorderStyle.None;
    tree.Size = new Size(200, 300);
    tree.Nodes.Add(root);
            
    for (int i = 1;  i <= 20; i++)
    {
        DropDownNode node = new DropDownNode("1." + 
                                        i.ToString("00"));
        root.Nodes.Add(node);
                
        for (int j =  1;  j <= 2; j++)
            node.Nodes.Add(new DropDownNode("1." + 
                i.ToString("00") + "." + j.ToString()));
    }
            
    root.Expand();
    this.treePanel.FinishEditing += new 
        DropDownValueChangedEventHandler(
            treePanel_FinishEditing);
    this.treePanel.ValueChanged += new 
        DropDownValueChangedEventHandler(
            treePanel_ValueChanged);
    return tree;
}

Of course, it would be better to inherit your own specialized combos from DropDownPanel, instead of using the DropDownPanel itself. A sample for such an inherited control is included in the demo project.

History

  • 1.0 Initial release

License

This article, along with any associated source code and files, is licensed under A Public Domain dedication


Written By
Technical Lead Artaker Computersysteme GmbH
Austria Austria
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralRe: Use ToolStripDropDown class Pin
Alexander Stojakovic13-Feb-07 11:13
Alexander Stojakovic13-Feb-07 11:13 
GeneralRe: Use ToolStripDropDown class Pin
Lukasz Sw.13-Feb-07 12:28
Lukasz Sw.13-Feb-07 12:28 
GeneralRe: Use ToolStripDropDown class Pin
Alexander Stojakovic13-Feb-07 16:01
Alexander Stojakovic13-Feb-07 16:01 
Generalnice Pin
Richard Prinz12-Feb-07 4:07
Richard Prinz12-Feb-07 4:07 
Generalexcellent work Pin
Thomas Krojer12-Feb-07 2:59
Thomas Krojer12-Feb-07 2:59 

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.