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

TreeView Multiselect Using TVS_EX_MULTISELECT in C#

, 25 Oct 2007 CPOL
Rate this:
Please Sign up or sign in to vote.
An example using the new TreeView control TVS_EX_MULTISELECT option with C# and .NET.

Screenshot - TreeView_MultiSelect.jpg

Introduction

Recently, I had a need to implement a TreeView that supported multiple node selection. I searched around and found several examples on how to do this, including two projects here on CodeProject. However, I also found a reference to a new option for the TreeView control included with Windows Vista, called TVS_EX_MULTISELECT. While the other code examples work across older platforms, TVS_EX_MULTISELECT only works with Windows Vista (and the forthcoming Windows 2008). As this is a new feature for the TreeView control, there is no support yet in the .NET framework for it. Also, I should note that the latest Windows SDK documentation has the terse notation "Not supported. Do not use." for TVS_EX_MULTISELECT. I have not been able to find any authoritative information as to why this is, other than support for this feature may be incomplete. The best evidence I have found for this is the fact that the TVN_SELCHANGED and TVN_SELCHANGING notification messages (the BeforeSelect and AfterSelect events in .NET) are not fired when TVS_EX_MULTISELECT is in effect. This does make sense because these events are issued when the selection is changed from one item to another. In the case of multiple selection, the selection is being expanded not changed from one item to another. There are two new messages called TVN_ITEMCHANGING and TVN_ITEMCHANGED that might be used to determine selection changes, though I have not tested them. By checking for the TVIS_SELECTED state, it would be possible to tell if an item has been selected.

Using the code

The code presented in this article is not a complete project. It implements a public function called TreeView_GetSelectedNodes that returns the selected nodes in a System.Collections.ArrayList. You can add it and various constants and DllImports to an existing project that has a TreeView control. For this article, I named that control m_TreeView, but that would be replaced with the name of your control.

The first thing that needs to be done is define a call to the Win32 SendMessage call via System.Runtime.InteropService. Your application should reference the System.Runtime.InteropServices namespace:

using System.Runtime.InteropServices;

[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern IntPtr SendMessage(HandleRef hWnd, 
               Int32 Msg, IntPtr wParam, IntPtr lParam);

Next several constants are needed for the various native operations. The SPACE_IL constant is used for defining the space between a treenode image and the label:

private const Int32 TV_FIRST = 0x1100;
private const Int32 TVM_GETNEXTITEM = (TV_FIRST + 10);
private const Int32 TVM_SETEXTENDEDSTYLE = (TV_FIRST + 44);
private const Int32 TVGN_CARET = 0x0009;
private const Int32 TVGN_NEXTSELECTED = 0x000B;
private const Int32 TVS_EX_MULTISELECT = 0x0002;
private const Int32 SPACE_IL = 3;

Next, within your form's constructor, you will add the following code after the call to InitializeComponent. This will turn on the TVS_EX_MULTISELECT style for your TreeView control:

SendMessage(new HandleRef(null, m_TreeView.Handle),
    TVM_SETEXTENDEDSTYLE,
    new IntPtr(TVS_EX_MULTISELECT),
    new IntPtr(TVS_EX_MULTISELECT));

This brings us to the main function for reading the selected nodes. It takes as its argument the TreeView control to read. It gets the first selected node using the TVGN_CARET message, and subsequently steps through any other selected nodes using the new TVGN_NEXTSELECTED message. Each of the SendMessage calls returns a handle to the selected TreeView item. Unfortunately, that by itself is not very useful as it is the TreeNode object that contains useful information. We need to map the item handle to a TreeNode. After searching for information on how to do this, I came across an interesting article on MSDN that provides an example function called FindTreeNodeFromHandle that does this. I was able to cut/paste the sample code as-is and incorporate it into my project.

public System.Collections.ArrayList TreeView_GetSelectedNodes(TreeView tv)
{
    System.Collections.ArrayList TreeNodeList = new System.Collections.ArrayList();

    IntPtr hItem = SendMessage(new HandleRef(null, tv.Handle),
        TVM_GETNEXTITEM, new IntPtr(TVGN_CARET), new System.IntPtr(0));
    while (hItem.ToInt32() != 0)
    {
        TreeNode tn = FindTreeNodeFromHandle(tv.Nodes, hItem);
        if (tn != null)
            TreeNodeList.Add(tn);
        hItem = SendMessage(new HandleRef(null, tv.Handle),
            TVM_GETNEXTITEM, new IntPtr(TVGN_NEXTSELECTED), hItem);
    }
    return TreeNodeList;
}

// This code is from http://msdn2.microsoft.com/en-us/library/ms229669(VS.80).aspx
// Finds a TreeNode in the provided TreeNodeCollection that has the handle specified.
// Warning: recursion!
// tnc - The TreeNodeCollection to search
// handle - The handle of the TreeNode to find in the collection
// Returns tThe TreeNode if found; null otherwise

private TreeNode FindTreeNodeFromHandle(TreeNodeCollection tnc, IntPtr handle)
{
    foreach (TreeNode tn in tnc)
    {
        if (tn.Handle == handle) return tn;
        if (tn.IsExpanded)
        {
            TreeNode tn2 = FindTreeNodeFromHandle(tn.Nodes, handle);
            if (tn2 != null) return tn2;
        }
    }
    return null;
}

At this point, the code will return a list of all selected nodes in an ArrayList. However, if there is code that is depending upon the AfterSelect or BeforeSelect events, it will not be called because those events are disabled by TVS_EX_MULTISELECT. I had some AfterSelect code that I needed to get working, and came up with the following workaround. It uses the NodeMouseClick event to see if the user has clicked within the bounds of the clicked TreeNode. If the user did click within the node's rectangle, then I assume the node is in the process of being selected and I can perform my AfterSelect operation. If an ImageList has been defined for the TreeView, I adjust the size of the rectangle to include the image since clicking on the image will also perform a select operation but the image is not included within the defined bounds for the node.

private void m_TreeView_NodeMouseClick(object sender, TreeNodeMouseClickEventArgs e)
{
    Rectangle r = e.Node.Bounds;
    if (e.Node.TreeView.ImageList != null)
    {
        r.X -= e.Node.TreeView.ImageList.ImageSize.Width + SPACE_IL;
        r.Width += e.Node.TreeView.ImageList.ImageSize.Width + SPACE_IL + 1;
    }
    if (r.Contains(e.Location))
        // Do some operation

}

Points of Interest

It was very interesting developing this as there is very little information on this new TreeView feature other than an MSDN Magazine article that demonstrates the new functionality in native C++. Much of the early code involved working with HTREEITEMs, until I found the ability to map to a TreeNode.

History

  • V1.0 - October 26, 2007.

License

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

Share

About the Author

n2jtx
Software Developer (Senior)
United States United States
a.k.a. Robert G. Schaffrath
 
I am a programmer and system administrator with a teeny tiny software company on Long Island and I also do some freelance programming work.
 
I started programming in High School with Basic-Plus on a timeshared DEC PDP-11/70 running RSTS/E back in the late 1970's. Next I worked with various languages under DEC VAX/VMS and Data General AOS/VS in the 1980's. That was followed by C and Perl under various flavors of Unix. On the PC side I worked with Turbo Pascal and Turbo C under DOS and eventually migrated to Visual C and Visual Basic under Windows. I have been working with C# and .NET for a over six years now and have been doing a lot of "Interop" work between native code and .NET.
 
In my spare time I am an Amateur Radio operator and I also volunteer with our local Community Emergency Response Team (CERT) and serve on the Board of Directors for an organization that provides an educational enrichment program for children in need of after-school care and
instruction. In addition, I like to brew beer and bicycle.

Comments and Discussions

 
GeneralMy vote of 4 Pinmembersignitary22-Sep-11 10:42 

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
Web03 | 2.8.1411022.1 | Last Updated 25 Oct 2007
Article Copyright 2007 by n2jtx
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid