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 DllImport
s 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;
}
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))
}
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 HTREEITEM
s, until I found the ability to map to a TreeNode
.
History
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.