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;
...
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:
TreeNode n1 = treeViewMS1.Nodes[0].Nodes[0].Nodes[0];
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
{
protected ArrayList m_coll;
protected TreeNode m_lastNode, m_firstNode;
...
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)
{
base.OnBeforeSelect(e);
bool bControl = (ModifierKeys==Keys.Control);
bool bShift = (ModifierKeys==Keys.Shift);
if (bControl && m_coll.Contains( e.Node ) )
{
e.Cancel = true;
removePaintFromNodes();
m_coll.Remove( e.Node );
paintSelectedNodes();
return;
}
m_lastNode = e.Node;
if (!bShift) m_firstNode = e.Node;
}
protected override void OnAfterSelect(TreeViewEventArgs e)
{
base.OnAfterSelect(e);
bool bControl = (ModifierKeys==Keys.Control);
bool bShift = (ModifierKeys==Keys.Shift);
if (bControl)
{
if ( !m_coll.Contains( e.Node ) )
{
m_coll.Add( e.Node );
}
else
{
removePaintFromNodes();
m_coll.Remove( e.Node );
}
paintSelectedNodes();
}
else
{
if (bShift)
{
Queue myQueue = new Queue();
TreeNode uppernode = m_firstNode;
TreeNode bottomnode = e.Node;
bool bParent = isParent(m_firstNode, e.Node);
if (!bParent)
{
bParent = isParent(bottomnode, uppernode);
if (bParent)
{
TreeNode t = uppernode;
uppernode = bottomnode;
bottomnode = t;
}
}
if (bParent)
{
TreeNode n = bottomnode;
while ( n != uppernode.Parent)
{
if ( !m_coll.Contains( n ) )
myQueue.Enqueue( n );
n = n.Parent;
}
}
else
{
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)
{
TreeNode t = uppernode;
uppernode = bottomnode;
bottomnode = t;
nIndexUpper = uppernode.Index;
nIndexBottom = bottomnode.Index;
}
TreeNode n = uppernode;
while (nIndexUpper <= nIndexBottom)
{
if ( !m_coll.Contains( n ) )
myQueue.Enqueue( n );
n = n.NextNode;
nIndexUpper++;
}
}
else
{
if ( !m_coll.Contains( uppernode ) )
myQueue.Enqueue( uppernode );
if ( !m_coll.Contains( bottomnode ) )
myQueue.Enqueue( bottomnode );
}
}
m_coll.AddRange( myQueue );
paintSelectedNodes();
m_firstNode = e.Node;
}
else
{
if (m_coll!=null && m_coll.Count>0)
{
removePaintFromNodes();
m_coll.Clear();
}
m_coll.Add( e.Node );
}
}
}
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.