Drop-Down TreeView Control (faking it)

DdoubleD 1 Tallied Votes 1K Views Share

Overview:

Oddly enough, MS never created a TreeViewDrowDown or equivalent. What I mean, is a control that looks and acts like a ComboBox , but contains a TreeView instead of just a list when in the expanded drop-down mode. I've never understood why they haven't, but I always seem to want to incorporate such a control into a design/form. So, a few months ago while working on an application, I decided to create such a control (or rather fake it on a form) in C#.

In this particular example, I have a pseudo drop-down TreeView control that is intended to drive the contents of the TabControl on the form. However, I did not take the time to demonstrate this activity by populating the TabPages with other controls.:( Let it suffice to say, the "Drop Down TreeView" would drive the rest of the form (TabControl) in this example. In addition, the design and implementation herein is not really a custom control, or a UserControl for that matter, but rather a way to fake the appearance of a TreeViewDropDown control on a Windows Form. Here is a sample (runtime) of the form when the TreeView control is hidden:

[IMG]http://www.daniweb.com/forums/attachment.php?attachmentid=12681&stc=1&d=1258944292[/IMG]

And, here is what the control looks like when it is Visible (also at runtime):

[IMG]http://www.daniweb.com/forums/attachment.php?attachmentid=12680&stc=1&d=1258944292[/IMG]

The actual controls used to create this effect consist of a TextBox, Button (with down-arrow image), and a TreeView that get's made visible when active and then hidden again when no longer needed (or dropped down).


Form's Design View

When viewing the form and controls inside the VS designer, the form appears as this:

[IMG]http://www.daniweb.com/forums/attachment.php?attachmentid=12679&stc=1&d=1258950910[/IMG]

With respect to the above designer-view, the little sliver underneath the TextBox and down-arrow Button is actually the TreeView control. The only reason it is visible at all on the form's design is so we can remember it's location and easily select it's properties and events if need be.


Runtime Characteristics

Operation at runtime, if not obvious enough, is to have the TreeView control appear (Visible) with a height that is large enough to adequately accommodate the data it contains; and much larger than the sliver seen in the designer. This is accomplished by adjusting the this.treeView1.Height when the user clicks the down-arrow Button, which causes the TreeView to become active. We don't need to adjust the width because we have already adjusted that with the Designer.

When the TreeView becomes active (via drop-down button), we disable the TabControl (via EnableControls() method) to prevent user from changing the focus. This is because we want the user to complete the selection (or ESCAPE) since our TreeView is driving the rest of the form. Once the user makes a selection, we call the SelectionChanged() method, which then hides the TreeView and performs the necessary update for the rest of the form's controls (sorry, just enabling the TabControl in this case because I didn't actually create a real-world scenario, but I think you'll get the jist).


Getting Underneath the Hood:

In the form's constructor, we set the TreeView's Height to be something appropriate for the anticipated amount of TreeNodes and for the size of our form. We also Hide() the TreeView control at this time because, if you remember, we kept it visible in the Designer.cs view.

public Form_TreeViewDropDown()
        {
            InitializeComponent();
            this.treeView1.Height = 250;
            this.treeView1.Hide();
            this.textBox1.ReadOnly = true;
            this.textBox1.BackColor = Color.White;
        }

IMPORTANT: You may need to modify the Designer.cs file to be sure the treeView1 control is added to the TOP of the form's Control list or other controls will overlap (hide portions of) the TreeView when it is made visible. Here is an example of the Designer code with the TreeView on top as it should be:

this.Controls.Add(this.treeView1);
            this.Controls.Add(this.tabControl1);
            this.Controls.Add(this.textBox1);
            this.Controls.Add(this.btnDropDownTreeView);

In the form's Load event, we create some TreeView Nodes and force a selection change because the TreeView selection drives our form:

private void Form_TreeViewDropDown_Load(object sender, EventArgs e)
        {
            // Load the treeView1 here, and select a TreeNode...
            treeView1.Nodes.Add(CreateTreeNodes());
            treeView1.ExpandAll();
            treeView1.SelectedNode = treeView1.Nodes[0];

            // Call the method that processes a change in our treeView's selected node
            treeView1_SelectionChanged();
        }

When the user clicks the drop-down button, we either show or hide the TreeView depending on its current state (we toggle the status). If clicked and TreeView is not visible, we make it visible with focus, save the current selection in case of cancel, and disable form controls ( EnableControls() ):

private void btnDropDownTreeView_Click(object sender, EventArgs e)
        {
            if (!treeView1.Visible)
            {
                saveSelectedNode = treeView1.SelectedNode;
                treeView1.Visible = true;
                treeView1.Focus();
                EnableControls();
            }
            
            // treeview already visible so toggle it (hide it) and process current selection...
            else
                treeView1_SelectionChanged();
        }

In our SelectionChanged method, we update the pseudo drop-down treeview's textBox1.Text to reflect the currently selected TreeNode, and hide the TreeView control. This is also where we would call any necessary code to update the form's controls (TabControl) to reflect any data lookups pertaining to the selected TreeNode... Finally, we enable controls ( EnableControls() ) to allow user to once again interact with the other controls on the form. Also, notice we have a holder for the currently selected node, saveSelectedNode , which is used to restore the selection if the user cancels while the TreeView is active (visible).

private void treeView1_SelectionChanged()
        {
            TreeNode node = treeView1.SelectedNode;

            // Avoid runtime error if treeView has nothing in it!
            if (node != null)
            {
                saveSelectedNode = node;
                textBox1.Text = node.Text;
            }
            treeView1.Hide();
            EnableControls();
        }

        private void treeView1_DoubleClick(object sender, EventArgs e) // Node was selected...
        {
            treeView1_SelectionChanged();
        }

To handle ENTER and ESCAPE key presses while (and occurs only when) the TreeView is active:

private void treeView1_KeyDown(object sender, KeyEventArgs e)
        {
            TreeNode selNode = treeView1.SelectedNode;
            // Selection accepted...
            if (e.KeyCode == Keys.Enter)
            {
                treeView1_SelectionChanged();
                e.Handled = true;
            }
            // Selection canceled...
            else if (e.KeyCode == Keys.Escape)
            {
                treeView1.SelectedNode = saveSelectedNode;
                treeView1_SelectionChanged();
                e.Handled = true;
            }
        }

That's it!

+++++++++++++++++++++++++++++++++++++++++++++++

Here is the complete Form code:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace ForumSolutions
{
    public partial class Form_TreeViewDropDown : Form
    {
        // Used when canceling change made to TreeView.SelectedNode
        TreeNode saveSelectedNode;

        public Form_TreeViewDropDown()
        {
            // IMPORTANT: 
            // Be sure the treeView1 control is added to the form's Control list
            // before any controls it will overlap when it is made visible; otherwise,
            // the treeView1 control will be cut-off/hidden by the other controls that 
            // it is behind (controls that are in front/on top of the treeView1).

            InitializeComponent();

            // Setting size here because we have it shrunk during design so it doesn't 
            // hide our other controls...
            this.treeView1.Height = 250;
            
            // The treeview itself is normally hidden unless dropdown button is clicked...
            // We could have hidden it in the designer, but then we wouldn't see it when designing...
            this.treeView1.Hide();

            // We want to capture the ENTER and ESCAPE keys...
            //this.treeView1.KeyDown += new System.Windows.Forms.KeyEventHandler(this.treeView1_KeyDown);

            // The textbox for our selected node is read only, but we want the background
            // color to be white instead of the read only gray color...
            this.textBox1.ReadOnly = true;
            this.textBox1.BackColor = Color.White;
        }

        private void Form_TreeViewDropDown_Load(object sender, EventArgs e)
        {
            // Load the treeView1 here, and select a TreeNode...
            treeView1.Nodes.Add(CreateTreeNodes());
            treeView1.ExpandAll();
            treeView1.SelectedNode = treeView1.Nodes[0];

            // Call the method that processes a change in our treeView's selected node
            treeView1_SelectionChanged();
        }

        private TreeNode CreateTreeNodes()
        {
            TreeNode topNode = new TreeNode("TopNode");
            int order = 0;

            for (int i = 0; i < 5; i++)
            {
                TreeNode iNode = new TreeNode("Hey, I'm a grandparent! Order: " + order++);
                topNode.Nodes.Add(iNode);

                for (int ii = 0; ii < 5; ii++)
                {
                    TreeNode iiNode = new TreeNode("Hey, I'm the parent! Order: " + order++);
                    topNode.Nodes[i].Nodes.Add(iiNode);

                    for (int iii = 0; iii < 5; iii++)
                    {
                        TreeNode iiiNode = new TreeNode("Yeah, I'm just a child (leaf)! Order: " + order++);
                        topNode.Nodes[i].Nodes[ii].Nodes.Add(iiiNode);
                    }
                }
            }
            return topNode;
        }

        private void treeView1_SelectionChanged() // NOTE: This method is not directly wired to any event...
        {
            TreeNode node = treeView1.SelectedNode;

            // Avoid runtime error if treeView has nothing in it!
            if (node != null)
            {
                saveSelectedNode = node; // save the selected node for any future cancel operations...
                textBox1.Text = node.Text; // update our textbox for the simulated drop down control...
            }
            // Hide the treeview control because any time this method is called,
            // it means an item (TreeNode) was accepted (double clicked, etc.) and selected...
            treeView1.Hide();

            // Enable form's controls...
            EnableControls();
        }


        private void treeView1_DoubleClick(object sender, EventArgs e) // Node was selected...
        {
            treeView1_SelectionChanged();
        }


        private void btnDropDownTreeView_Click(object sender, EventArgs e)
        {
            // If treeview is not visible then do the drop down (make it visible)...
            if (!treeView1.Visible)
            {
                saveSelectedNode = treeView1.SelectedNode;
                treeView1.Visible = true;
                treeView1.Focus();

                // Disable TabControl until user makes or cancels selection...
                EnableControls();
            }
            
            // treeview already visible so toggle it (hide it) and process current selection...
            else
                treeView1_SelectionChanged();
        }

        private void EnableControls()
        {
            if (treeView1.Visible)
            {
                tabControl1.Enabled = false;
            }
            else
            {
                tabControl1.Enabled = true;
            }
        }


        private void treeView1_KeyDown(object sender, KeyEventArgs e)
        {
            TreeNode selNode = treeView1.SelectedNode;
            // Selection accepted...
            if (e.KeyCode == Keys.Enter)
            {
                treeView1_SelectionChanged();
                e.Handled = true;
            }
            // Selection canceled...
            else if (e.KeyCode == Keys.Escape)
            {
                treeView1.SelectedNode = saveSelectedNode;
                treeView1_SelectionChanged();
                e.Handled = true;
            }
        }
    }
}

And, in the designer.cs file:

namespace ForumSolutions
{
    partial class Form_TreeViewDropDown
    {
        /// <summary>
        /// Required designer variable.
        /// </summary>
        private System.ComponentModel.IContainer components = null;

        /// <summary>
        /// Clean up any resources being used.
        /// </summary>
        /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
            {
                components.Dispose();
            }
            base.Dispose(disposing);
        }

        #region Windows Form Designer generated code

        /// <summary>
        /// Required method for Designer support - do not modify
        /// the contents of this method with the code editor.
        /// </summary>
        private void InitializeComponent()
        {
            System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(Form_TreeViewDropDown));
            this.textBox1 = new System.Windows.Forms.TextBox();
            this.btnDropDownTreeView = new System.Windows.Forms.Button();
            this.treeView1 = new System.Windows.Forms.TreeView();
            this.tabControl1 = new System.Windows.Forms.TabControl();
            this.tabPage1 = new System.Windows.Forms.TabPage();
            this.tabPage2 = new System.Windows.Forms.TabPage();
            this.tabControl1.SuspendLayout();
            this.SuspendLayout();
            // 
            // textBox1
            // 
            this.textBox1.Location = new System.Drawing.Point(16, 20);
            this.textBox1.Name = "textBox1";
            this.textBox1.ReadOnly = true;
            this.textBox1.Size = new System.Drawing.Size(329, 20);
            this.textBox1.TabIndex = 1;
            // 
            // btnDropDownTreeView
            // 
            this.btnDropDownTreeView.Image = ((System.Drawing.Image)(resources.GetObject("btnDropDownTreeView.Image")));
            this.btnDropDownTreeView.Location = new System.Drawing.Point(344, 20);
            this.btnDropDownTreeView.Name = "btnDropDownTreeView";
            this.btnDropDownTreeView.Size = new System.Drawing.Size(20, 20);
            this.btnDropDownTreeView.TabIndex = 2;
            this.btnDropDownTreeView.UseVisualStyleBackColor = true;
            this.btnDropDownTreeView.Click += new System.EventHandler(this.btnDropDownTreeView_Click);
            // 
            // treeView1
            // 
            this.treeView1.Location = new System.Drawing.Point(16, 39);
            this.treeView1.Name = "treeView1";
            this.treeView1.Size = new System.Drawing.Size(348, 11);
            this.treeView1.TabIndex = 3;
            this.treeView1.DoubleClick += new System.EventHandler(this.treeView1_DoubleClick);
            this.treeView1.KeyDown += new System.Windows.Forms.KeyEventHandler(this.treeView1_KeyDown);
            // 
            // tabControl1
            // 
            this.tabControl1.Controls.Add(this.tabPage1);
            this.tabControl1.Controls.Add(this.tabPage2);
            this.tabControl1.Location = new System.Drawing.Point(16, 71);
            this.tabControl1.Name = "tabControl1";
            this.tabControl1.SelectedIndex = 0;
            this.tabControl1.Size = new System.Drawing.Size(619, 260);
            this.tabControl1.TabIndex = 0;
            // 
            // tabPage1
            // 
            this.tabPage1.Location = new System.Drawing.Point(4, 22);
            this.tabPage1.Name = "tabPage1";
            this.tabPage1.Padding = new System.Windows.Forms.Padding(3);
            this.tabPage1.Size = new System.Drawing.Size(611, 234);
            this.tabPage1.TabIndex = 0;
            this.tabPage1.Text = "tabPage1";
            this.tabPage1.UseVisualStyleBackColor = true;
            // 
            // tabPage2
            // 
            this.tabPage2.Location = new System.Drawing.Point(4, 22);
            this.tabPage2.Name = "tabPage2";
            this.tabPage2.Padding = new System.Windows.Forms.Padding(3);
            this.tabPage2.Size = new System.Drawing.Size(611, 234);
            this.tabPage2.TabIndex = 1;
            this.tabPage2.Text = "tabPage2";
            this.tabPage2.UseVisualStyleBackColor = true;
            // 
            // Form_TreeViewDropDown
            // 
            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
            this.ClientSize = new System.Drawing.Size(647, 354);
            this.Controls.Add(this.treeView1);
            this.Controls.Add(this.tabControl1);
            this.Controls.Add(this.textBox1);
            this.Controls.Add(this.btnDropDownTreeView);
            this.Name = "Form_TreeViewDropDown";
            this.Text = "Form_TreeViewDropDown";
            this.Load += new System.EventHandler(this.Form_TreeViewDropDown_Load);
            this.tabControl1.ResumeLayout(false);
            this.ResumeLayout(false);
            this.PerformLayout();

        }

        #endregion

        private System.Windows.Forms.TextBox textBox1;
        private System.Windows.Forms.Button btnDropDownTreeView;
        private System.Windows.Forms.TreeView treeView1;
        private System.Windows.Forms.TabControl tabControl1;
        private System.Windows.Forms.TabPage tabPage1;
        private System.Windows.Forms.TabPage tabPage2;
    }
}
ddanbe commented: Nice snippet! +5
abc16 0 Newbie Poster

THanks for sharing this code DdoubleD.
Very good code it is........

Diamonddrake 397 Master Poster

I like it.
You say its not really a control, rather that its faked. but don't you realize that if the treeview was on its own borderless form and was created and called via the button next to a textbox, and you encapsulated all the code in a usercontrol it would be a true simple drop in control VS what you are calling a fake. Its a really good idea. And you have taken it all the way to the end. all that is missing is a little tidying up to make it a full fledged drop in control.

Good work.

you could even use EX_Animate_window to make it slide down and up when appearing, if you wanted too.

If you are interested in this and need some help getting started i would love to give you a hand.

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.