Click here to Skip to main content
11,640,125 members (71,188 online)
Click here to Skip to main content

C# TreeView with multiple selection

, 18 Aug 2002 325.2K 3.9K 78
Rate this:
Please Sign up or sign in to vote.
Enable multiple selection in .NET treeview controls

The .NET TreeView control has no built-in multiple selection. Let there be. This article about C# depicts a tree view control with multiple selection, derived from the base .NET TreeView control. It supports CTRL and SHIFT combinations.

How to use it

This control is a C# control. Once compiled, it becomes a managed .NET component and behaves much like good ol' ActiveX components for VB developers. To use it in your application, you have at least two options. The first is to simply reference the TreeViewMS project given in the source project, by clicking right on your current Windows Form application and choosing Add Reference, then browse to TreeViewMS.csproj. The screen capture below shows the steps:

Adding a reference to the new TreeView control to your code

You may also add the tree view control once for all in your Toolbox window, then simply drag&drop it onto your Form. In order to do this, show up the .NET Studio Toolbox window, then right click and choose Customize toolbox, then choose the .NET Framework components tab, and browse to the compiled control: TreeViewMS.dll, as in the capture below:

Adding the new tree view control to the Visual Studio .NET toolbox

Once you have added this tree view control in a form, you may start to use it like the base .NET TreeView control. The addition lies in a new exposed property, SelectedNodes, which returns the collection of selected TreeNode items. If we take the demo app, which adds selected items in the list view on the right, then code goes like this:

    private TreeViewMS.TreeViewMS treeViewMS1;
    private System.Windows.Forms.ListView listView1;
    ...

    // add selected items from treeview to the
    // listview on the right hand side
    foreach (TreeNode n in treeViewMS1.SelectedNodes)
    {
       listView1.Items.Add( n.Text, n.ImageIndex );
    }

SelectedNodes is also a read-write property. What follows is a sample code that forces the selection of given tree items:

    // valid item from the sample tree
    TreeNode n1 = treeViewMS1.Nodes[0].Nodes[0].Nodes[0]; 
    // valid item from the sample tree
    TreeNode n2 = treeViewMS1.Nodes[0].Nodes[0].Nodes[2]; 
    ArrayList coll = new ArrayList();
    coll.Add(n1);
    coll.Add(n2);
    treeViewMS1.SelectedNodes = coll;

Technical details

Of course, this control is derived from the base TreeView control:

public class TreeViewMS : System.Windows.Forms.TreeView
{
    // class members
    protected ArrayList     m_coll;
    protected TreeNode      m_lastNode, m_firstNode;

    ...

    /* API method */ public ArrayList SelectedNodes
    {
        get
        {
            return m_coll;
        }
        set
        {
            removePaintFromNodes();
            m_coll.Clear();
            m_coll = value;
            paintSelectedNodes();
        }
    }

}

What we need to control item selection is events such like BEFORESELECT and AFTERSELECT to get the current selected node. BEFORESELECT is only useful to undo a node selection, for instance when you select twice a node with CTRL down.

Because Microsoft has clearly figured out that TreeView s were likely to be derived, they have overridable methods BeforeSelect(...) and AfterSelect(...) that don't interfere with the events named the same. All what we need is override the 2 methods and not forget to call the base class in the implementation:

protected override void OnBeforeSelect(TreeViewCancelEventArgs e)
{
    // e.Node is the current node exposed by the base TreeView control
    base.OnBeforeSelect(e);

    bool bControl = (ModifierKeys==Keys.Control);
    bool bShift = (ModifierKeys==Keys.Shift);

    // selecting twice the node while pressing CTRL ?
    if (bControl && m_coll.Contains( e.Node ) )
    {
        // unselect it
        // (let framework know we don't want selection this time)
        e.Cancel = true;

        // update nodes
        removePaintFromNodes();
        m_coll.Remove( e.Node );
        paintSelectedNodes();
        return;
    }

    m_lastNode = e.Node;
    if (!bShift) m_firstNode = e.Node; // store begin of shift sequence
}

protected override void OnAfterSelect(TreeViewEventArgs e)
{
    // e.Node is the current node exposed by the base TreeView control

    base.OnAfterSelect(e);

    bool bControl = (ModifierKeys==Keys.Control);
    bool bShift = (ModifierKeys==Keys.Shift);

    if (bControl)
    {
        if ( !m_coll.Contains( e.Node ) ) // new node ?
        {
            m_coll.Add( e.Node );
        }
        else  // not new, remove it from the collection
        {
            removePaintFromNodes();
            m_coll.Remove( e.Node );
        }
        paintSelectedNodes();
    }
    else 
    {
        if (bShift)
        {
            Queue myQueue = new Queue();

            TreeNode uppernode = m_firstNode;
            TreeNode bottomnode = e.Node;

            // case 1 : begin and end nodes are parent
            bool bParent = isParent(m_firstNode, e.Node);
            if (!bParent)
            {
                bParent = isParent(bottomnode, uppernode);
                if (bParent) // swap nodes
                {
                    TreeNode t = uppernode;
                    uppernode = bottomnode;
                    bottomnode = t;
                }
            }
            if (bParent)
            {
                 TreeNode n = bottomnode;
                 while ( n != uppernode.Parent)
                 {
                     if ( !m_coll.Contains( n ) ) // new node ?
                         myQueue.Enqueue( n );

                      n = n.Parent;
                 }
            }
            // case 2 : nor the begin nor the
            // end node are descendant one another
            else
            {
                 // are they siblings ?                 

                 if ( (uppernode.Parent==null && bottomnode.Parent==null) 
                       || (uppernode.Parent!=null && 
                       uppernode.Parent.Nodes.Contains( bottomnode )) )
                 {
                      int nIndexUpper = uppernode.Index;
                      int nIndexBottom = bottomnode.Index;
                      if (nIndexBottom < nIndexUpper) // reversed?
                      {
                           TreeNode t = uppernode;
                           uppernode = bottomnode;
                           bottomnode = t;
                           nIndexUpper = uppernode.Index;
                           nIndexBottom = bottomnode.Index;
                      }

                      TreeNode n = uppernode;
                      while (nIndexUpper <= nIndexBottom)
                      {
                           if ( !m_coll.Contains( n ) ) // new node ?
                               myQueue.Enqueue( n );

                           n = n.NextNode;

                           nIndexUpper++;
                      } // end while

                  }
                  else
                  {
                      if ( !m_coll.Contains( uppernode ) ) 
                          myQueue.Enqueue( uppernode );
                      if ( !m_coll.Contains( bottomnode ) ) 
                          myQueue.Enqueue( bottomnode );
                  }

             }

             m_coll.AddRange( myQueue );

             paintSelectedNodes();
             // let us chain several SHIFTs if we like it
             m_firstNode = e.Node; 

         } // end if m_bShift
         else
         {
              // in the case of a simple click, just add this item
              if (m_coll!=null && m_coll.Count>0)
              {
                   removePaintFromNodes();
                   m_coll.Clear();
              }
              m_coll.Add( e.Node );
          }
     }
}


// Helpers
//
//


protected bool isParent(TreeNode parentNode, TreeNode childNode)
{
    if (parentNode==childNode)
        return true;

    TreeNode n = childNode;
    bool bFound = false;
    while (!bFound && n!=null)
    {
        n = n.Parent;
        bFound = (n == parentNode);
    }
    return bFound;
}


protected void paintSelectedNodes()
{
    foreach ( TreeNode n in m_coll )
    {
        n.BackColor = SystemColors.Highlight;
        n.ForeColor = SystemColors.HighlightText;
    }
}

protected void removePaintFromNodes()
{
    if (m_coll.Count==0) return;

    TreeNode n0 = (TreeNode) m_coll[0];
    Color back = n0.TreeView.BackColor;
    Color fore = n0.TreeView.ForeColor;

    foreach ( TreeNode n in m_coll )
    {
        n.BackColor = back;
        n.ForeColor = fore;
    }
}

As you may note, SelectedNodes returns a System.Collections.ArrayList instance of underlying TreeView items, not the more natural System.Windows.Forms.TreeNodeCollection instance. Why this? In fact, that's not up to developers, the TreeNodeCollection is deliberately provided by the .NET framework with a hidden constructor, hence it is not possible to reuse it. This has raised some concern on public newsgroups, but Microsoft people have not agreed to change this anytime soon.

Simpler reuse

What if you don't want to redistribute the TreeViewMS.dll library (I admit that separate libraries are always bottlenecks)? All you have to do is take the code for OnBeforeSelect() and OnAfterSelect() as described above and attach it to the standard TreeView events.

Thanks to David Sleeckx for the bug hunting.

History

  • August 8, 2002 - First version.
  • Updated August 18, 2002.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

Share

About the Author

Addicted to reverse engineering. At work, I am developing business intelligence software in a team of smart people (independent software vendor).

Need a fast Excel generation component? Try xlsgen.


You may also be interested in...

Comments and Discussions

 
QuestionLicensing Pin
Member 104455314-Dec-13 12:14
memberMember 104455314-Dec-13 12:14 
QuestionHow to find thus the selected node are Parent or Child Pin
arventh30-May-12 2:17
memberarventh30-May-12 2:17 
QuestionLicense Clarification Pin
Member 181268929-Dec-11 11:34
memberMember 181268929-Dec-11 11:34 
GeneralMy vote of 4 Pin
Armando de la Torre22-Oct-11 12:10
memberArmando de la Torre22-Oct-11 12:10 
GeneralCheck for null in SelectedNodes setter Pin
Member 429184417-Apr-10 2:11
memberMember 429184417-Apr-10 2:11 
GeneralremovePaintFromNodes and a check on null Pin
zazoef16-Nov-09 8:32
memberzazoef16-Nov-09 8:32 
GeneralRe: removePaintFromNodes and a check on null Pin
landete8523-Mar-12 5:03
memberlandete8523-Mar-12 5:03 
GeneralBug when deleting nodes from the tree: invalid nodes in m_coll Pin
Member 26063281-Sep-09 5:06
memberMember 26063281-Sep-09 5:06 
GeneralBug: Don't use TreeViewMS.SelectedNodes.Clear() Pin
Member 39983554-Jun-09 4:00
memberMember 39983554-Jun-09 4:00 
Generala bug related to the shift-click Pin
aerishan19-Sep-08 8:08
memberaerishan19-Sep-08 8:08 
Questioncustomized TreeNode class Pin
sanjeevmedhi7-Aug-08 0:41
membersanjeevmedhi7-Aug-08 0:41 
Generalcolor mismatch when disabling the treeview. Pin
Member 258632628-May-08 6:59
memberMember 258632628-May-08 6:59 
Questionhi Pin
Member 459203229-Feb-08 2:29
memberMember 459203229-Feb-08 2:29 
QuestionHeelo Stepehen Pin
Member 459203228-Feb-08 23:51
memberMember 459203228-Feb-08 23:51 
GeneralHelp with non-dll implementation Pin
Hayden Devlin13-Aug-07 6:03
memberHayden Devlin13-Aug-07 6:03 
GeneralRe: Help with non-dll implementation Pin
Member 459203214-Feb-08 22:11
memberMember 459203214-Feb-08 22:11 
Generalanother small bug Pin
alacevic28-Aug-06 16:40
memberalacevic28-Aug-06 16:40 
GeneralRe: another small bug [modified] Pin
William H Gates III3-Jul-08 20:41
memberWilliam H Gates III3-Jul-08 20:41 
QuestionVarious colors in the treenode text? Pin
Saez31-May-06 7:56
memberSaez31-May-06 7:56 
GeneralReload Treeview Pin
Paul_Fels21-May-06 2:20
memberPaul_Fels21-May-06 2:20 
GeneralRe: Reload Treeview Pin
Stephane Rodriguez.21-May-06 20:29
memberStephane Rodriguez.21-May-06 20:29 
GeneralNullException! Pin
Mohanad!4-May-05 11:01
memberMohanad!4-May-05 11:01 
GeneralRe: NullException! Pin
dkerr00724-Sep-08 13:21
memberdkerr00724-Sep-08 13:21 
GeneralNodes font and background colors aren't saved Pin
Artem Kliatchkine1-Apr-05 2:31
memberArtem Kliatchkine1-Apr-05 2:31 
QuestionHow to select the node by coding instead of Clicking the mouse? Pin
Thanh Pham9-Dec-04 13:36
memberThanh Pham9-Dec-04 13:36 
AnswerRe: How to select the node by coding instead of Clicking the mouse? Pin
Stephane Rodriguez.14-Dec-04 9:50
memberStephane Rodriguez.14-Dec-04 9:50 
AnswerRe: How to select the node by coding instead of Clicking the mouse? Pin
Naveen.Prabhu9-Feb-07 6:37
memberNaveen.Prabhu9-Feb-07 6:37 
GeneralRe: How to select the node by coding instead of Clicking the mouse? Pin
trueplinius6-Jul-07 12:14
membertrueplinius6-Jul-07 12:14 
GeneraltreeView control Pin
Salarzai24-Nov-04 21:19
sussSalarzai24-Nov-04 21:19 
QuestionHow to ignore SelectedImageIndex Pin
stan_buyanov25-Sep-04 14:40
memberstan_buyanov25-Sep-04 14:40 
AnswerRe: How to ignore SelectedImageIndex Pin
Anonymous25-May-05 1:53
sussAnonymous25-May-05 1:53 
GeneralTreeNodeCollection Pin
marybr17-Sep-04 0:06
sussmarybr17-Sep-04 0:06 
GeneralRe: TreeNodeCollection Pin
Stephane Rodriguez.17-Sep-04 5:03
memberStephane Rodriguez.17-Sep-04 5:03 
GeneralNew implementation Pin
lustuyck22-Jul-04 23:40
memberlustuyck22-Jul-04 23:40 
GeneralRe: New implementation Pin
Paul_Fels21-May-06 2:00
memberPaul_Fels21-May-06 2:00 
GeneralCan't unselect the last selected item Pin
Kluch14-Jul-04 16:25
memberKluch14-Jul-04 16:25 
GeneralRe: Can't unselect the last selected item Pin
Stephane Rodriguez.14-Jul-04 18:58
memberStephane Rodriguez.14-Jul-04 18:58 
GeneralRe: Can't unselect the last selected item Pin
Kluch15-Jul-04 18:04
memberKluch15-Jul-04 18:04 
GeneralRe: Can't unselect the last selected item Pin
LucyXiao10-Oct-04 16:08
memberLucyXiao10-Oct-04 16:08 
GeneralRe: Can't unselect the last selected item Pin
Anonymous14-Oct-04 3:48
sussAnonymous14-Oct-04 3:48 
GeneralRe: Can't unselect the last selected item Pin
LucyXiao30-Mar-05 21:10
memberLucyXiao30-Mar-05 21:10 
GeneralProblem with shift-click selection Pin
jezbo15-Feb-04 22:28
memberjezbo15-Feb-04 22:28 
GeneralRe: Problem with shift-click selection Pin
jezbo15-Feb-04 22:36
memberjezbo15-Feb-04 22:36 
GeneralAdd +/- button without adding children Pin
Bruce Cutler1-Dec-03 11:26
memberBruce Cutler1-Dec-03 11:26 
GeneralRe: Add +/- button without adding children Pin
Stephane Rodriguez.1-Dec-03 21:08
memberStephane Rodriguez.1-Dec-03 21:08 
GeneralRe: Add +/- button without adding children Pin
Bruce Cutler2-Dec-03 4:20
memberBruce Cutler2-Dec-03 4:20 
GeneralRe: Add +/- button without adding children Pin
Stephane Rodriguez.2-Dec-03 4:44
memberStephane Rodriguez.2-Dec-03 4:44 
GeneralRe: Add +/- button without adding children Pin
Bruce Cutler2-Dec-03 4:48
memberBruce Cutler2-Dec-03 4:48 
JokeRe: Add +/- button without adding children Pin
theRealCondor7-Jul-06 4:05
membertheRealCondor7-Jul-06 4:05 
GeneralI need a horizontal tree control Pin
MtnBiknGuy19-Oct-03 4:48
memberMtnBiknGuy19-Oct-03 4:48 

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 | Terms of Use | Mobile
Web02 | 2.8.150731.1 | Last Updated 19 Aug 2002
Article Copyright 2002 by Stephane Rodriguez.
Everything else Copyright © CodeProject, 1999-2015
Layout: fixed | fluid