Click here to Skip to main content
Click here to Skip to main content

DropDownPanel

By , 11 Feb 2007
Rate this:
Please Sign up or sign in to vote.

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 I, 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 appart 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 2 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.

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 choosen a TreeNode or canceled editing.

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 (ie. 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.

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>:

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:

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

About the Author

Alexander Stojakovic
Technical Lead Artaker Computersysteme GmbH
Austria Austria
No Biography provided

Comments and Discussions

 
Questionissue when combo is inside a tab [modified] Pinmemberjuan.carlos25-Mar-10 8:42 
QuestionRe: issue when combo is inside a tab Pinmemberblah23810-Aug-10 7:18 
Questionno win32 api? PinmemberUnruled Boy16-Apr-09 21:09 
QuestionSingle click to select, cause the form deactivated Pinmembericeway6-Nov-08 20:59 
AnswerRe: Single click to select, cause the form deactivated PinmemberAlexander Stojakovic18-Nov-08 9:54 
GeneralRe: Single click to select, cause the form deactivated [modified] Pinmembericeway1-Dec-08 21:07 
GeneralRe: Single click to select, cause the form deactivated PinmemberAlexander Stojakovic3-Dec-08 11:56 
GeneralRe: Single click to select, cause the form deactivated Pinmembericeway30-Dec-08 14:56 
GeneralGood article! I have question Pinmemberbluemoong12-Feb-08 23:11 
QuestionIs it necessary to be a UserControl? PinmemberSteve Harding2-Sep-07 14:33 
AnswerRe: Is it necessary to be a UserControl? PinmemberAlexander Stojakovic2-Sep-07 22:46 
QuestionNice control, but I have a question Pinmembervovair29-May-07 0:26 
AnswerRe: Nice control, but I have a question PinmemberAlexander Stojakovic29-May-07 6:56 
GeneralRe: Nice control, but I have a question Pinmembervovair29-May-07 22:17 
GeneralNodes.Find problem Pinmemberperspolis26-May-07 7:04 
GeneralRe: Nodes.Find problem PinmemberAlexander Stojakovic29-May-07 6:35 
QuestionRightToLeft? Pinmemberperspolis21-May-07 5:39 
AnswerRe: RightToLeft? PinmemberAlexander Stojakovic23-May-07 0:29 
QuestionShow value selected PinmemberAldinCC2-May-07 4:09 
AnswerRe: Show value selected PinmemberAlexander Stojakovic9-May-07 3:48 
GeneralRe: Show value selected PinmemberAldin CC9-May-07 6:05 
GeneralRe: Show value selected Pinmemberyongfa36511-May-09 0:29 
GeneralPlease Tell me PinmemberMember #386972714-Mar-07 21:48 
GeneralRe: Please Tell me PinmemberAlexander Stojakovic15-Mar-07 10:38 
GeneralUse ToolStripDropDown class PinmemberLukasz Swiatkowski13-Feb-07 4:21 

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

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

| Advertise | Privacy | Mobile
Web02 | 2.8.140415.2 | Last Updated 11 Feb 2007
Article Copyright 2007 by Alexander Stojakovic
Everything else Copyright © CodeProject, 1999-2014
Terms of Use
Layout: fixed | fluid