Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

A Lightweight FileTreeView with MultiSelect

4.80/5 (7 votes)
3 Apr 2010CPOL4 min read 1   1.7K  
A lightweight event driven control with minimal recursion and no manual tracking of selected items

Introduction

There are a number of public domain Multi-Select TreeView controls and a number of File Explorers as well, but most seem to be either very complex, older (.NET 2.0 or earlier), too slow for our needs, or are not multi-select.

We wanted something lightweight, flexible and relatively fast that we could populate on demand from any node in the File System.

Background

When I started to build this DLL, the Multi-Select feature seemed the most problematic so I looked at some of the other code online. Anything I found was tracking node selection manually in an IList, ArrayList (erg) or other data structure.

The code to do that can get a little hairy if one lets the user select/deselect away to their hearts content and I felt that there had to be a better way.

I thought about using the TreeNode Tag property to hold a custom object that would contain both the node's selected status as well as the appropriate 'Info' object (FileInfo, DirectoryInfo, DriveInfo) but in a moment of silliness I decided to first see whether the TreeNode Checked property would work even if the CheckBox display is turned off.

And what do you know, they do. They are merrily checking away in the background even if the TreeView isn't showing them. With that little revelation (to me at least) in hand, Multi-Select got much easier.

The rest of this article is focused on how we implemented the Multi-Select feature using the Checked property.

Note: The project also needed TreeView populate-on-demand code and not being one to reinvent the wheel, I derived mine from another CodeProject article by Chandana Subasinghe in October 2006 - http://www.codeproject.com/KB/cs/TreeViewFileExplorer.aspx). Yes, I could have used a DataSource but for this project, the lighter the better.

The Code

The guts of the MFT Multi-Select code is comprised mainly of two event handlers:

_AfterSelect fires whenever a node is clicked, after the Tree's SelectedNode property has been set. _AfterCheck fires after that, each time we programmatically check a node.

Let's look at _AfterSelect first:

In our code, the event handler decides what nodes to check by looking at the ModifierKeys the user has pressed (SHIFT, CTRL, or both).

We implemented to duplicate Windows Explorer right-pane functionality. The SHIFT and SHIFT-CTRL handling is the trickiest but not too bad since we are only firing 'Checked' events, not trying to track the selections (sans error trapping for clarity):

C#
protected void MultiSelectFileTreeView_AfterSelect(object sender, TreeViewEventArgs e)
{
    // if it is CTRL-SHIFT then bControl and bShift are both false 
    // but bBoth is true
    bool bBoth = ModifierKeys == (Keys.Shift | Keys.Control); 
    bool bControl = (ModifierKeys == Keys.Control);
    bool bShift = (ModifierKeys == Keys.Shift);

    if (!(bBoth || bControl || bShift))
    {
        //no modifier keys, so clear all nodes and select just this one
        ClearChecksRecursive(this.Nodes);
        e.Node.Checked = true;
        this.SelectedNode = e.Node;
        return;
    }
    if (bControl)
    {
        //don't clear, just do it - if already selected then deselect it
        e.Node.Checked = !e.Node.Checked;
        this.SelectedNode = e.Node;
        return;
    }
    if (bShift || bBoth)
    {
        // _prevNode is the SelectedNode at the time of the click
        //  bShift means no CTRL key so clear all the checks
        if (bShift)
            ClearChecksRecursive(this.Nodes);
        TreeNode TopNode = new TreeNode();
        TreeNode BottomNode = new TreeNode();

        // don't allow range selection across levels 
        if (_prevNode.Level != SelectedNode.Level)
        {
            TopNode = SelectedNode.Parent.Nodes[0];
            BottomNode = e.Node;
        }
        else
        {
            //are we going up or down?
            TopNode = (_prevNode.Index < SelectedNode.Index) ? _prevNode : SelectedNode;
            BottomNode = (SelectedNode.Index > _prevNode.Index) ? 
			SelectedNode : _prevNode;
        }
        // check all nodes in the range we selected
        // and if it's not a file, collapse it
        // we could add an option here to check folder children
        // and then not collapse the folder
        for (int x = TopNode.Index; x <= BottomNode.Index; x++)
        {
            TreeNode n = SelectedNode.Parent.Nodes[x];
            n.Checked = true;
            if (n.Tag.GetType() != typeof(FileInfo))
                n.Collapse();
        }
        return;
    }
 } 

Now that we are firing "Check" events, all we have to do is trap them and color the Node being checked/unchecked to reflect its status.

_AfterCheck

C#
protected void MultiSelectFileTreeView_AfterCheck(object sender, TreeViewEventArgs e)
{
  e.Node.BackColor = (e.Node.Checked == true) ? SelectedBackColor : this.BackColor;
  e.Node.ForeColor = (e.Node.Checked == true) ? SelectedForeColor : this.ForeColor;
  if (e.Node.Checked)
  {
      Type nodeType = e.Node.Tag.GetType();
      if (nodeType == typeof(DirectoryInfo) || nodeType == typeof(DriveInfo))
          e.Node.Expand();
  }
}

Using the Code

MultiSelectFileTreeView_src

The MFT class inherits directly from TreeView and all of the base TreeView members are accessible to your code.

Use the normal VS methodology of adding a DLL reference to your project and, if you like, of adding it to your VS tool box.

The ReadMe.txt file included in the download describes the MFT properties, which are minimal.

We were after lightweight, remember?

You can set the Node Selection colors and specify the image index numbers to be used but the parent form is responsible for supplying the ImageList just as you would with a normal TreeView.

As mentioned, the MFT provides for essentially the same node selection functionality as found in Windows Explorer - SHIFT-select, CTRL-select and SHIFT-CTRL-select functionality are provided.

In addition, a right-click context-menu is provided on expandable nodes. The screen shot shows the Test WinForm app using the context menu to select the files in the root of D:\.

The Test application implements the MFT control in the left pane and subscribes to its After_Check event to populate a Listbox with selected files on the right using the event-handler code shown below. As always, the underlying handler (our MFT event) runs before the handler in the parent form.

Note: The MFT also provides a PopulateAtPath method so that a portion of the file system can be used where appropriate to the application.

Implementing the Control

Here is the code used to implement the control in the Test form. Pardon the lack of error trapping for easier reading.

C#
public Form1()
{
      InitializeComponent();
        MultiSelectFileTreeView1.ImageList = imageList1;
        MultiSelectFileTreeView1.AfterCheck += 
		new TreeViewEventHandler(MultiSelectFileTreeView1_AfterCheck);
        MultiSelectFileTreeView1.SelectedBackColor = Color.Violet;
        if (!MultiSelectFileTreeView1.Populate()) 
        {
            MessageBox.Show(MultiSelectFileTreeView1.Message);
        }
    }

    void MultiSelectFileTreeView1_AfterCheck(object sender, TreeViewEventArgs e)
    {
        if (e.Node.Tag!=null && e.Node.Tag.GetType() == typeof(FileInfo))
     // in this case, I only want the File's path
        {
            listBox1.BeginUpdate();
            bool has = listBox1.Items.Contains(e.Node.FullPath);
            if (e.Node.Checked && !has)
            {
                listBox1.Items.Add(e.Node.FullPath);
             }
                 else if (!e.Node.Checked && has)
             {
                 listBox1.Items.Remove(e.Node.FullPath);
             }
             listBox1.EndUpdate();
         }
    }

Points of Interest

One thing to notice is that the Node.Tag property contains the 'info' object for each node, gathered at the time the node is populated from the file system.

So, while the test app simply shows the FullPath in the listbox, the entire Info object is just as easily available to your code.

History

  • 3rd April, 2010: Initial post

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)