TreeView

A TreeView control displays a collection of items in a hierarchical view. Tree views are very common in the computer world, as seen in Windows Explorer, Microsoft Outlook, and Outlook Express, the Solution Explorer in Visual Studio .NET, and countless other applications.

Each item in a tree view is encapsulated within a TreeNode object. Each TreeNode can have zero, one, or many child nodes. The Nodes property of the tree view represents the collection of TreeNode objects that comprise the root, or top level, nodes in the hierarchy. Each node in turn has its own Nodes collection that contains all of that node's child nodes, and so on down the hierarchy. Your program can iterate through each of these collections, recursively if necessary, to walk the entire tree structure, as demonstrated shortly.

A tree view can have only a single node selected at any given time. The user can select a node either by clicking on the node with the mouse or using the arrow keys on the keyboard to move focus up and down the hierarchy. A node can be selected programmatically by setting the TreeView.SelectedNode property, and the currently selected node can be determined by getting the value of this property.

Since the SelectedNode property returns only the last node in the full path for that node in the hierarchy, which node in the tree is actually selected can be ambiguous if you rely only on the Text property of the TreeNode returned by the SelectedNode property. Use the FullPath property of the currently selected node to determine the entire hierarchy for the node. If necessary, the value of the FullPath property can be parsed by using the value of the TreeView.PathSeparator property in conjunction with the String.Split method. Each node also has a zero-based Index property that uniquely identifies that node's position within the Nodes collection of its parent node.

Your program can navigate the tree independent of user interaction (although some properties depend on previous user actions) through the use of several TreeNode properties, including Parent, FirstNode, LastNode, NextNode, NextVisibleNode, PrevNode, and PrevVisibleNode, all of which are described in Table 14-6.

If the ShowPlusMinus property of the tree view is set to true (the default value), then a plus sign is displayed next to nodes that have unexpanded child nodes, and a minus sign is displayed next to nodes that have their child nodes displayed. By clicking on successive plus signs, the user can expand the tree under a node and drill down through the hierarchy.

If there are no child nodes, then there will be no plus sign or minus sign. Irrespective of the value of the ShowPlusMinus property, the current node can also be expanded by double-clicking on the node or pressing the plus key on the numeric keypad. The current node can be collapsed by pressing the minus key on the numeric keypad.

An undocumented feature: A node and all its child nodes can be expanded by pressing the asterisk on the numeric keypad. No keyboard shortcut collapses all the nodes.

The TreeView control has a CollapseAll method that collapses all the nodes in the entire hierarchy and an ExpandAll method that expands all the nodes in the entire hierarchy, both of which are listed along with other commonly used TreeView methods in Table 14-7. The ExpandAll method can take time to complete if there are a large number of nodes, such as in a reasonably complex directory structure, as demonstrated shortly.

You can expand and collapse individual nodes of a tree view programmatically, using several of the TreeNode methods listed in Table 14-9. The state of a node persists under certain circumstances, although it is not consistent between user interaction and programmatic control. For example, if a child node is expanded and a higher-level node is collapsed by clicking on its minus sign and then expanded by clicking on its plus sign, the original child node remains expanded. However, if the equivalent operations are performed by using the Collapse and Expand methods, the original node does not remain expanded.

Any node in a tree view can have an image associated with it. Unlike other controls that can use either a single image or an ImageList, the image(s) used in a tree view must be contained within a single ImageList object. (ImageLists are described fully in Chapter 7.) A default image can be specified for all the nodes in the tree view when you use the TreeView.ImageIndex property. A different image can be specified for the currently selected node with the TreeView.SelectedImageIndex property, which applies to the entire tree view. In addition, each node can have its own ImageIndex and SelectedImageIndex properties. As demonstrated in Example 14-3 and Example 14-4, this allows your application to have default images for all the nodes, such as directories, and then use different images for specific types of nodes, such as files.

All the properties and methods described above are listed, along with many other commonly used properties and methods of the TreeView, TreeNode, and TreeNodeCollection classes, in Tables Table 14-6 through Table 14-11. A sample program demonstrating a tree view and many of these properties and methods is shown in Example 14-3 (in C#) and in Example 14-4 (in VB.NET).

Table 14-6. TreeView properties

Property

Value type

Description

BorderStyle

BorderStyle

Read/write. The border style of the control. Valid values are members of the BorderStyle enumeration, listed in Table 14-2. Default is BorderStyle.None.

CheckBoxes

Boolean

If false (the default), nodes in the control are displayed without a checkbox. If true, a checkbox is displayed to the left of each node and image, if any. The AfterCheck event is raised when the state of a checkbox is changed.

FullRowSelect

Boolean

Read/write. If true, the entire width of the selection is highlighted. Default is false. Ignored if the ShowLines property is set to true.

HideSelection

Boolean

Read/write. If true (the default), the highlighting of the selected node disappears when the control does not have focus.

HotTracking

Boolean

Read/write. If true, the node appears as a hyperlink when the mouse passes over it. Default is false.

ImageIndex

Integer

Read/write. Zero-based index value of the Image object from the ImageList. Specifies the default image displayed by nodes not currently selected.

ImageList

ImageList

Read/write. The ImageList object that contains all the images is available for display with the nodes. Each node can display one image at a time, specified by the TreeView, TreeNode ImageIndex, or SelectedImageIndex properties.

The ImageList component is described fully in Chapter 7.

Indent

Integer

Read/write. Distance, in pixels, each node level is indented. Default is 19.

ItemHeight

Integer

Read/write. Height, in pixels, of each node. By default, scales with the control's Font property.

LabelEdit

Boolean

Read/write. If false (the default), the node labels cannot be edited by the user.

Nodes

TreeNodeCollection

Read-only. The collection of root level nodes assigned to the tree view.

PathSeparator

String

Read/write. The delimiter string used by the TreeNode.FullPath property. The default is the backslash character ().

Scrollable

Boolean

Read/write. If true (the default), the control displays vertical and/or horizontal scrollbars as necessary.

SelectedImageIndex

Integer

Read/write. Zero-based index value of the Image object from the ImageList. Specifies the image displayed by the currently selected node.

SelectedNode

TreeNode

Read/write. The currently selected TreeNode object.

ShowLines

Boolean

Read/write. If true (the default), lines are drawn between the nodes and the FullRowSelect property is ignored.

ShowPlusMinus

Boolean

Read/write. If true (the default), plus-sign and minus-sign buttons are displayed next to nodes that contain child nodes.

ShowRootLines

Boolean

Read/write. If true (the default), lines are drawn between root nodes. If false, plus or minus sign buttons will not appear next to root nodes, even if ShowPlusMinus is true.

Sorted

Boolean

Read/write. If false (the default), the nodes are not sorted. If true, the nodes are sorted alphabetically.

TopNode

TreeNode

Read-only. The first fully visible node in the control, taking into account scrolling performed by the user.

VisibleCount

Integer

Read-only. The number of potential nodes fully visible in the control, taking into account the height of the client area and the height of a node. Value may be greater than the actual number of nodes in the control.

Table 14-7. TreeView methods

Method

Description

BeginUpdate

Disables redrawing of the tree view control until the EndUpdate method is called. Improves performance when adding nodes one at a time.

CollapseAll

Collapses all the nodes in the tree view.

EndUpdate

Enables redrawing of the tree view control after BeginUpdate was called.

ExpandAll

Expands all the nodes in the tree view. If called on a large tree structure, it can take some time.

GetNodeAt

Returns the node at the specified point or coordinate pair. The MouseEventArgs.X and MouseEventArgs.Y coordinates of the MouseDown event can be passed as the coordinates.

GetNodeCount

Returns the number of nodes in the tree view. If the Boolean argument is true, it includes all the nodes in any subtrees.

Table 14-8. TreeNode properties

Property

Value type

Description

Checked

Boolean

Read/write. true if the node is checked. Only relevant if the TreeView.CheckBoxes property is true.

FirstNode

TreeNode

Read-only. The first child node in the Nodes collection of the current node. If the current node has no child nodes, it returns null/Nothing.

FullPath

String

Read-only. The path from the root node to the current node. Each node is represented by its Text property, separated by the string specified in the TreeView.PathSeparator property.

ImageIndex

Integer

Read/write. Zero-based index value of the Image object from the ImageList used as the image displayed by this node when not currently selected. Overrides the TreeView.ImageIndex property.

Index

Integer

Read-only. Zero-based value representing the position of this node within the Nodes collection of its parent node.

IsEditing

Boolean

Read-only. true if the node is editable, false otherwise.

IsExpanded

Boolean

Read-only. true if the node is expanded, false otherwise.

IsSelected

Boolean

Read-only. true if the node is selected, false otherwise.

IsVisible

Boolean

Read-only. true if the node is visible, false otherwise.

LastNode

TreeNode

Read-only. The last child node in the Nodes collection of the current node. If the current node has no child nodes, returns null/Nothing.

NextNode

TreeNode

Read-only. The next sibling node in the parent node's Nodes collection. If there is no next node, returns null/Nothing.

NextVisibleNode

TreeNode

Read-only. The next visible node, taking into account user interaction, the size of the client area, the font, and other visual properties. If there is no next visible node, returns null/Nothing.

NodeFont

Font

Read/write. The font used to display the text of the current node. Overrides the TreeView.Font property. If font is larger than that specified in the TreeView.Font property, text may be clipped.

Nodes

TreeNodeCollection

Read-only. The collection of nodes assigned to the current node. Properties and methods of the TreeNodeCollection class are listed in Table 14-10 and Table 14-11, respectively.

Parent

TreeNode

Read-only. The parent node of the current node. Root level nodes return null/Nothing.

PrevNode

TreeNode

Read-only. The previous sibling node in the parent node's Nodes collection. If there is no previous node, returns null/Nothing.

PrevVisibleNode

TreeNode

Read-only. The previous visible node, taking into account user interaction, the size of the client area, the font, and other visual properties. If there is no previous node visible, returns null/Nothing.

SelectedImageIndex

Integer

Read/write. Zero-based index value of the Image object from the ImageList used as the image displayed by this node when currently selected. Overrides the TreeView.SelectedImageIndex property.

Tag

Object

Read/write. Object containing data about the node.

Text

String

Read/write. Text displayed as the label of the node. Can either be set explicitly or via one of the TreeNode methods, listed in Table 14-9.

TreeView

TreeView

Read-only. The parent tree view of the node.

Table 14-9. TreeNode methods

Method

Description

BeginEdit

Begins editing of node label. If TreeView.LabelEdit property is false, an exception will be thrown.

Clone

Copies the node and all its child nodes.

Collapse

Collapses the current node. Child nodes are not collapsed.

EndEdit

Ends editing of node label. If Boolean argument is true, editing is canceled without saving the changes.

EnsureVisible

Causes tree view to expand and scroll so that the current node is visible.

Expand

Expands the current node down to the next level of nodes.

ExpandAll

Expands all the nodes in the Nodes collection of the current node.

GetNodeCount

Returns number of child nodes. If Boolean argument is true, includes all child nodes and their child nodes.

Remove

Removes the current node and any child nodes from the tree view.

Toggle

If node is currently collapsed, it is expanded, and vice versa.

Table 14-10. TreeNodeCollection properties

Property

Value Type

Description

Count

Integer

Read-only. Total number of TreeNode objects in the collection.

IsReadOnly

Boolean

Read-only. If false (the default), the collection may be modified.

Item

TreeNode

Read/write. The node at the specified, zero-based index.

Table 14-11. TreeNodeCollection methods

Method

Description

Add

Overloaded. Adds new node to the end of current node collection. If string argument provided, returns TreeNode object representing the new node. If TreeNode argument provided, returns index of new node.

AddRange

Adds an array of previously created TreeNode objects to the end of current node collection.

Clear

Removes all the nodes from the current collection. Can be used to clear an entire tree view.

Contains

Returns true if the specified TreeNode is a member of the collection.

CopyTo

Copies entire collection to the specified array, starting at specified index.

IndexOf

Returns zero-based index of the specified TreeNode.

Insert

Inserts a previously created TreeNode into the collection at the location specified by the index. If TreeView.Sorted property is true, the index is ignored. An exception will be thrown if a node is added to a tree view while still a member of a different tree view. It must be either removed or cloned from the original tree view.

Remove

Removes the specified TreeNode from the collection. All subsequent nodes move up one position.

RemoveAt

Removes the TreeNode from the collection at the location specified by the zero-based index. All subsequent nodes move up one position.

A sample program using a tree view was presented in Chapter 5 to demonstrate an Explorer interface. That program, which was created using Visual Studio .NET, displayed a hierarchy of motor vehicles. It demonstrated the use of the TreeNode Editor for adding nodes to the tree view control, and the Image Collection Editor for adding images to the image list.

The sample programs listed here in Example 14-3 (in C#) and in Example 14-4 (in VB.NET) are more complex and fully featured than the example shown in Chapter 5. These new examples use a tree view control to display the logical drives on the local machine and the directories and (optionally) the files they contain. A checkbox is provided to toggle the display of the files: when it is checked, files are displayed; otherwise, only directories are displayed. There are also several buttons for displaying information about the currently selected node and for expanding and collapsing the tree.

When the example is compiled and run, it looks something like Figure 14-4Figure 14-4. It may not be obvious in this monochrome book, but alternating directories are colored LightPink and alternating files are LightGreen.

A full analysis follows the code listings.

Figure 14-5. TreeViews program

figs/pnwa_1405.gif

Example 14-3. TreeView control in C# (TreeViews.cs)

figs/csharpicon.gif

using System;
using System.Drawing;
using System.Windows.Forms;
using System.IO; // necessary for Directory info
 
namespace ProgrammingWinApps
{
 public class TreeViews : Form
 {
 TreeView tvw;
 CheckBox cb;
 Button btnSelected;
 Button btnExpand;
 Button btnExpandAll;
 Button btnCollapse;
 Button btnCollapseAll;
 Button btnToggle;
 
 public TreeViews( )
 {
 Text = "TreeView";
 Size = new Size(400,600);
 
 ImageList imgList = new ImageList( );
 Image img;
 
 // Use an array to add filenames to the ImageList
 String[ ] arFiles = {
 @"C:Program FilesMicrosoft Visual Studio .NET 2003Common7" + 
 @"Graphicsiconscomputerform.ico",
 @"C:Program FilesMicrosoft Visual Studio .NET 2003Common7" + 
 @"Graphicsiconswin95clsdfold.ico",
 @"C:Program FilesMicrosoft Visual Studio .NET 2003Common7" + 
 @"Graphicsiconswin95openfold.ico",
 @"C:Program FilesMicrosoft Visual Studio .NET 2003Common7" + 
 @"Graphicsitmapsassortedhappy.bmp"
 };
 
 for (int i = 0; i < arFiles.Length; i++)
 {
 img = Image.FromFile(arFiles[i]);
 imgList.Images.Add(img);
 }
 
 tvw = new TreeView( );
 tvw.Parent = this;
 tvw.Location = new Point(10,10);
 tvw.Size = new Size(ClientSize.Width - 20, Height - 200);
 tvw.Anchor = AnchorStyles.Top | AnchorStyles.Left | 
 AnchorStyles.Right | AnchorStyles.Bottom;
 tvw.BackColor = Color.Moccasin;
 tvw.ForeColor = Color.DarkRed;
 tvw.BorderStyle = BorderStyle.Fixed3D;
 tvw.FullRowSelect = false; // default
 tvw.ShowLines = true; // default
 tvw.ShowPlusMinus = true; // default
 tvw.Scrollable = true; // default
 tvw.HideSelection = false; 
 tvw.HotTracking = true; 
 tvw.ImageList = imgList;
 tvw.ImageIndex = 1;
 tvw.SelectedImageIndex = 2;
// tvw.Indent = 35;
// tvw.Font = new Font("Times New Roman", 20f);
// tvw.ItemHeight = tvw.Font.Height * 2;
 tvw.BeforeExpand += new 
 TreeViewCancelEventHandler(tvw_BeforeExpand);
 
 cb = new CheckBox( );
 cb.Parent = this;
 cb.Location = new Point((Width - cb.Width) * 2 / 10, 
 tvw.Bottom + 25);
 cb.Text = "Show Files";
 cb.Anchor = AnchorStyles.Bottom;
 cb.CheckedChanged += new EventHandler(cb_CheckedChanged);
 
 btnSelected = new Button( );
 btnSelected.Parent = this;
 btnSelected.Text = "&SelectedNode";
 int xSize = ((int)(Font.Height * .75) * btnSelected.Text.Length);
 int ySize = Font.Height + 10;
 btnSelected.Size = new Size(xSize, ySize);
 btnSelected.Location = new Point(cb.Left, cb.Bottom + ySize);
 btnSelected.Anchor = AnchorStyles.Bottom;
 btnSelected.Click += new EventHandler(btnSelected_Click);
 
 btnToggle = new Button( );
 btnToggle.Parent = this;
 btnToggle.Location = new Point((Width - cb.Width) * 7 / 10,
 cb.Top);
 btnToggle.Text = "&Toggle";
 btnToggle.Size = new Size(btnSelected.Width, btnSelected.Height);
 btnToggle.Anchor = AnchorStyles.Bottom;
 btnToggle.Click += new EventHandler(btnToggle_Click);
 
 btnExpand = new Button( );
 btnExpand.Parent = this;
 btnExpand.Location = new Point(btnToggle.Left, btnToggle.Bottom);
 btnExpand.Text = "&Expand";
 btnExpand.Size = new Size(btnSelected.Width, btnSelected.Height);
 btnExpand.Anchor = AnchorStyles.Bottom;
 btnExpand.Click += new EventHandler(btnExpand_Click);
 
 btnExpandAll = new Button( );
 btnExpandAll.Parent = this;
 btnExpandAll.Location = new Point(btnExpand.Left, 
 btnExpand.Bottom);
 btnExpandAll.Text = "Expand &All";
 btnExpandAll.Size = new Size(btnSelected.Width, 
 btnSelected.Height);
 btnExpandAll.Anchor = AnchorStyles.Bottom;
 btnExpandAll.Click += new EventHandler(btnExpandAll_Click);
 
 btnCollapse = new Button( );
 btnCollapse.Parent = this;
 btnCollapse.Location = new Point(btnExpandAll.Left, 
 btnExpandAll.Bottom);
 btnCollapse.Text = "&Collapse";
 btnCollapse.Size = new Size(btnSelected.Width, 
 btnSelected.Height);
 btnCollapse.Anchor = AnchorStyles.Bottom;
 btnCollapse.Click += new EventHandler(btnCollapse_Click);
 
 btnCollapseAll = new Button( );
 btnCollapseAll.Parent = this;
 btnCollapseAll.Location = new Point(btnCollapse.Left, 
 btnCollapse.Bottom);
 btnCollapseAll.Text = "Colla&pse All";
 btnCollapseAll.Size = new Size(btnSelected.Width, 
 btnSelected.Height);
 btnCollapseAll.Anchor = AnchorStyles.Bottom;
 btnCollapseAll.Click += new EventHandler(btnCollapseAll_Click);
 
 FillDirectoryTree( );
 } // close for constructor
 
 static void Main( ) 
 {
 Application.Run(new TreeViews( ));
 }
 
 private void FillDirectoryTree( )
 {
 // Populate with the contents of the local hard drive.
 
 // Suppress redraw until tree view is complete
 tvw.BeginUpdate( );
 
 // First clear all the nodes.
 tvw.Nodes.Clear( );
 
 // Get the logical drives and put them into the root nodes.
 // Fill an array with all the logical drives on the machine.
 string[ ] strDrives = Environment.GetLogicalDrives( );
 
 // Iterate through the drives, adding them to the tree.
 // Use a try/catch block, so if a drive is not ready, 
 // e.g. an empty floppy or CD, it will not be added to the tree.
 foreach (string rootDirectoryName in strDrives)
 {
 try 
 {
 // Find all the first level subdirectories.
 // If the drive is not ready, this will throw an 
 // exception, which will have the effect of 
 // skipping that drive.
 Directory.GetDirectories(rootDirectoryName);
 
 // Create a node for each root directory
 TreeNode ndRoot = new TreeNode(rootDirectoryName);
 
 // Add the node to the tree
 tvw.Nodes.Add(ndRoot);
 
 // Set colors based on Index property.
 // Index not set until after node added to collection.
 if (ndRoot.Index % 2 = = 0)
 {
 ndRoot.BackColor = Color.LightYellow;
 ndRoot.ForeColor = Color.Green;
 }
 
 // Add subdirectory nodes.
 // If Show Files checkbox checked, then also get 
 // the filenames.
 GetSubDirectoryNodes(ndRoot, cb.Checked);
 }
 catch (System.IO.IOException)
 {
 // let it through
 }
 catch (Exception e)
 {
 // Catch any other errors.
 MessageBox.Show(e.Message);
 }
 }
 tvw.EndUpdate( );
 } // close FillDirectoryTree
 
 private void GetSubDirectoryNodes(TreeNode parentNode, 
 bool getFileNames)
 {
 // Exit this method if the node is not a directory.
 DirectoryInfo di = new DirectoryInfo(parentNode.FullPath);
 if ((di.Attributes & FileAttributes.Directory) = = 0)
 {
 return;
 }
 
 // Clear all the nodes in this node.
 parentNode.Nodes.Clear( );
 
 // Get an array of strings containing all the subdirectories 
 // in the parent node.
 string[ ] arSubs = Directory.GetDirectories(parentNode.FullPath);
 
 // Add a child node for each subdirectory.
 foreach (string subDir in arSubs)
 {
 DirectoryInfo dirInfo = new DirectoryInfo(subDir);
 // do not show hidden folders
 if ((dirInfo.Attributes & FileAttributes.Hidden)!= 0)
 {
 continue;
 }
 
 TreeNode subNode = new TreeNode(dirInfo.Name);
 parentNode.Nodes.Add(subNode);
 
 // Set colors based on Index property.
 if (subNode.Index % 2 = = 0)
 subNode.BackColor = Color.LightPink;
 }
 
 if (getFileNames)
 {
 // Get any files for this node.
 string[ ] files = Directory.GetFiles(parentNode.FullPath);
 
 // After placing the nodes, 
 // now place the files in that subdirectory.
 foreach (string str in files)
 {
 FileInfo fi = new FileInfo(str);
 TreeNode fileNode = new TreeNode(fi.Name);
 parentNode.Nodes.Add(fileNode);
 
 // Set the icons
 fileNode.ImageIndex = 0;
 fileNode.SelectedImageIndex = 3;
 
 // Set colors based on Index property.
 if (fileNode.Index % 2 = = 0)
 fileNode.BackColor = Color.LightGreen;
 }
 }
 } // close GetSubDirectoryNodes
 
 private void cb_CheckedChanged(object sender, EventArgs e)
 {
 FillDirectoryTree( );
 }
 
 private void tvw_BeforeExpand(object sender,
 TreeViewCancelEventArgs e)
 {
 tvw.BeginUpdate( );
 
 foreach (TreeNode tn in e.Node.Nodes)
 {
 GetSubDirectoryNodes(tn, cb.Checked);
 }
 
 tvw.EndUpdate( ); 
 } 
 
 private void btnSelected_Click(object sender, EventArgs e)
 {
 MessageBox.Show(tvw.SelectedNode.ToString( ) + "
" +
 "FullPath:	" + tvw.SelectedNode.FullPath.ToString( ) + "
" +
 "Index:	" + tvw.SelectedNode.Index.ToString( ));
 }
 
 private void btnExpand_Click(object sender, EventArgs e)
 {
 tvw.SelectedNode.Expand( );
 }
 
 private void btnExpandAll_Click(object sender, EventArgs e)
 {
 tvw.SelectedNode.ExpandAll( );
 }
 
 private void btnCollapse_Click(object sender, EventArgs e)
 {
 tvw.SelectedNode.Collapse( );
 }
 
 private void btnCollapseAll_Click(object sender, EventArgs e)
 {
 tvw.CollapseAll( );
 }
 
 private void btnToggle_Click(object sender, EventArgs e)
 {
 tvw.SelectedNode.Toggle( );
 }
 } // close for form class
} // close form namespace

Example 14-4. TreeView in VB.NET (TreeViews.vb)

figs/vbicon.gif

Option Strict On
imports System
imports System.Drawing
imports System.Windows.Forms
imports System.IO ' necessary for Directory info
 
namespace ProgrammingWinApps
 public class TreeViews : inherits Form
 
 dim tvw as TreeView
 dim cb as CheckBox
 dim btnSelected as Button
 dim btnExpand as Button
 dim btnExpandAll as Button
 dim btnCollapse as Button
 dim btnCollapseAll as Button
 dim btnToggle as Button
 dim i as integer
 
 public sub New( )
 Text = "TreeView"
 Size = new Size(400,600)
 
 dim imgList as new ImageList( )
 dim img as Image
 
 ' Use an array to add filenames to the ImageList
 dim arFiles( ) as string = { _
 "C:Program FilesMicrosoft Visual Studio .NET 2003Common7" + _
 "Graphicsiconscomputerform.ico", _
 "C:Program FilesMicrosoft Visual Studio .NET 2003Common7" + _
 "Graphicsiconswin95clsdfold.ico", _
 "C:Program FilesMicrosoft Visual Studio .NET 2003Common7" + _
 "Graphicsiconswin95openfold.ico", _
 "C:Program FilesMicrosoft Visual Studio .NET 2003Common7" + _
 "Graphicsitmapsassortedhappy.bmp"}
 
 for i = 0 to arFiles.Length - 1
 img = Image.FromFile(arFiles(i))
 imgList.Images.Add(img)
 next
 
 tvw = new TreeView( )
 tvw.Parent = me
 tvw.Location = new Point(10,10)
 tvw.Size = new Size(ClientSize.Width - 20, Height - 200)
 tvw.Anchor = AnchorStyles.Top or AnchorStyles.Left or _
 AnchorStyles.Right or AnchorStyles.Bottom
 tvw.BackColor = Color.Moccasin
 tvw.ForeColor = Color.DarkRed
 tvw.BorderStyle = BorderStyle.Fixed3D
 tvw.FullRowSelect = false ' default
 tvw.ShowLines = true ' default
 tvw.ShowPlusMinus = true ' default
 tvw.Scrollable = true ' default
 tvw.HideSelection = false 
 tvw.HotTracking = true 
 tvw.ImageList = imgList
 tvw.ImageIndex = 1
 tvw.SelectedImageIndex = 2
' tvw.Indent = 35
' tvw.Font = new Font("Times New Roman", 20f)
' tvw.ItemHeight = tvw.Font.Height * 2
 AddHandler tvw.BeforeExpand, AddressOf tvw_BeforeExpand
 
 cb = new CheckBox( )
 cb.Parent = me
 cb.Location = new Point( _
 CType((Width - cb.Width) * 2 / 10, integer), _
 tvw.Bottom + 25)
 cb.Text = "Show Files"
 cb.Anchor = AnchorStyles.Bottom
 AddHandler cb.CheckedChanged, AddressOf cb_CheckedChanged
 
 btnSelected = new Button( )
 btnSelected.Parent = me
 btnSelected.Text = "&SelectedNode"
 dim xSize as integer = CType(Font.Height * .75, integer) * _
 btnSelected.Text.Length
 dim ySize as integer = Font.Height + 10
 btnSelected.Size = new Size(xSize, ySize)
 btnSelected.Location = new Point(cb.Left, cb.Bottom + ySize)
 btnSelected.Anchor = AnchorStyles.Bottom
 AddHandler btnSelected.Click, AddressOf btnSelected_Click
 
 btnToggle = new Button( )
 btnToggle.Parent = me
 btnToggle.Location = new Point( _
 CType((Width - cb.Width) * 7 / 10, integer), _
 cb.Top)
 btnToggle.Text = "&Toggle"
 btnToggle.Size = new Size(btnSelected.Width, btnSelected.Height)
 btnToggle.Anchor = AnchorStyles.Bottom
 AddHandler btnToggle.Click, AddressOf btnToggle_Click
 
 btnExpand = new Button( )
 btnExpand.Parent = me
 btnExpand.Location = new Point(btnToggle.Left, btnToggle.Bottom)
 btnExpand.Text = "&Expand"
 btnExpand.Size = new Size(btnSelected.Width, btnSelected.Height)
 btnExpand.Anchor = AnchorStyles.Bottom
 AddHandler btnExpand.Click, AddressOf btnExpand_Click
 
 btnExpandAll = new Button( )
 btnExpandAll.Parent = me
 btnExpandAll.Location = new Point(btnExpand.Left, _
 btnExpand.Bottom)
 btnExpandAll.Text = "Expand &All"
 btnExpandAll.Size = new Size(btnSelected.Width, _
 btnSelected.Height)
 btnExpandAll.Anchor = AnchorStyles.Bottom
 AddHandler btnExpandAll.Click, AddressOf btnExpandAll_Click
 
 btnCollapse = new Button( )
 btnCollapse.Parent = me
 btnCollapse.Location = new Point(btnExpandAll.Left, _
 btnExpandAll.Bottom)
 btnCollapse.Text = "&Collapse"
 btnCollapse.Size = new Size(btnSelected.Width, _
 btnSelected.Height)
 btnCollapse.Anchor = AnchorStyles.Bottom
 AddHandler btnCollapse.Click, AddressOf btnCollapse_Click
 
 btnCollapseAll = new Button( )
 btnCollapseAll.Parent = me
 btnCollapseAll.Location = new Point(btnCollapse.Left, _
 btnCollapse.Bottom)
 btnCollapseAll.Text = "Colla&pse All"
 btnCollapseAll.Size = new Size(btnSelected.Width, _
 btnSelected.Height)
 btnCollapseAll.Anchor = AnchorStyles.Bottom
 AddHandler btnCollapseAll.Click, AddressOf btnCollapseAll_Click
 
 FillDirectoryTree( )
 end sub ' close for constructor
 
 private sub FillDirectoryTree( )
 ' Populate with the contents of the local hard drive.
 
 ' Suppress redraw until tree view is complete
 tvw.BeginUpdate( )
 
 ' First clear all the nodes.
 tvw.Nodes.Clear( )
 
 ' Get the logical drives and put them into the root nodes.
 ' Fill an array with all the logical drives on the machine.
 dim strDrives( ) as string = Environment.GetLogicalDrives( )
 
 ' Iterate through the drives, adding them to the tree.
 ' Use a try/catch block, so if a drive is not ready, 
 ' e.g. an empty floppy or CD, it will not be added to the tree.
 dim rootDirectoryName as string
 for each rootDirectoryName in strDrives
 try 
 ' Find all the first level subdirectories.
 ' If the drive is not ready, this will throw an exception,
 ' which will have the effect of skipping that drive.
 Directory.GetDirectories(rootDirectoryName)
 
 ' Create a node for each root directory
 dim ndRoot as TreeNode = new TreeNode(rootDirectoryName)
 
 ' Add the node to the tree
 tvw.Nodes.Add(ndRoot)
 
 ' Set colors based on Index property.
 ' Index not set until after node added to collection.
 if ndRoot.Index mod 2 = 0 then
 ndRoot.BackColor = Color.LightYellow
 ndRoot.ForeColor = Color.Green
 end if
 
 ' Add subdirectory nodes.
 ' If Show Files checkbox checked, then also get 
 ' the filenames.
 GetSubDirectoryNodes(ndRoot, cb.Checked)
 catch e as System.IO.IOException
 ' let it through
 catch e as Exception 
 ' Catch any other errors.
 MessageBox.Show(e.Message)
 end try
 next
 tvw.EndUpdate( )
 end sub ' close FillDirectoryTree
 
 private sub GetSubDirectoryNodes(parentNode as TreeNode, _
 getFileNames as Boolean)
 ' Exit this method if the node is not a directory.
 dim di as DirectoryInfo = new DirectoryInfo(parentNode.FullPath)
 if (di.Attributes and FileAttributes.Directory) = 0 then
 return
 end if
 
 ' Clear all the nodes in this node.
 parentNode.Nodes.Clear( )
 
 ' Get an array of strings containing all the subdirectories 
 ' in the parent node.
 dim arSubs( ) as string = _
 Directory.GetDirectories(parentNode.FullPath)
 
 ' Add a child node for each subdirectory.
 dim subDir as string
 for each subDir in arSubs
 dim dirInfo as DirectoryInfo = new DirectoryInfo(subDir)
 
 ' do not show hidden folders
 if (dirInfo.Attributes and FileAttributes.Hidden) = 0 then
 
 dim subNode as TreeNode = new TreeNode(dirInfo.Name)
 parentNode.Nodes.Add(subNode)
 
 ' Set colors based on Index property.
 if (subNode.Index mod 2 = 0) then
 subNode.BackColor = Color.LightPink
 end if
 end if
 next
 
 if getFileNames then
 ' Get any files for this node.
 dim files( ) as string = _
 Directory.GetFiles(parentNode.FullPath)
 
 ' After placing the nodes, 
 ' now place the files in that subdirectory.
 dim str as string
 for each str in files
 dim fi as FileInfo = new FileInfo(str)
 dim fileNode as TreeNode = new TreeNode(fi.Name)
 parentNode.Nodes.Add(fileNode)
 
 ' Set the icons
 fileNode.ImageIndex = 0
 fileNode.SelectedImageIndex = 3
 
 ' Set colors based on Index property.
 if fileNode.Index mod 2 = 0 then
 fileNode.BackColor = Color.LightGreen
 end if
 next
 end if
 end sub ' close GetSubDirectoryNodes
 
 private sub cb_CheckedChanged(ByVal sender as object, _
 ByVal e as EventArgs)
 FillDirectoryTree( )
 end sub
 
 private sub tvw_BeforeExpand(ByVal sender as object, _
 ByVal e as TreeViewCancelEventArgs)
 tvw.BeginUpdate( )
 dim tn as TreeNode
 for each tn in e.Node.Nodes
 GetSubDirectoryNodes(tn, cb.Checked)
 next
 tvw.EndUpdate( )
 end sub
 
 private sub btnSelected_Click(ByVal sender as object, _
 ByVal e as EventArgs)
 MessageBox.Show(tvw.SelectedNode.ToString( ) + vbNewLine + _
 "FullPath:" + vbTab + tvw.SelectedNode.FullPath.ToString( ) + _
 vbNewLine + _
 "Index:" + vbTab + tvw.SelectedNode.Index.ToString( ))
 end sub
 
 private sub btnExpand_Click(ByVal sender as object, _
 ByVal e as EventArgs)
 tvw.SelectedNode.Expand( )
 end sub
 
 private sub btnExpandAll_Click(ByVal sender as object, _
 ByVal e as EventArgs)
 tvw.SelectedNode.ExpandAll( )
 end sub
 
 private sub btnCollapse_Click(ByVal sender as object, _
 ByVal e as EventArgs)
 tvw.SelectedNode.Collapse( )
 end sub
 
 private sub btnCollapseAll_Click(ByVal sender as object, _
 ByVal e as EventArgs)
 tvw.CollapseAll( )
 end sub
 
 private sub btnToggle_Click(ByVal sender as object, _
 ByVal e as EventArgs)
 tvw.SelectedNode.Toggle( )
 end sub
 
 public shared sub Main( ) 
 Application.Run(new TreeViews( ))
 end sub
 end class
end namespace

14.3.1 Code Analysis

This is a large and complex program. The following analysis will break it down into manageable segments that illustrate the tree view and list controls.

14.3.1.1 The constructor

An image list is declared and populated in the constructor. This image list contains four images, all of which are taken from files that are included as part of the normal Visual Studio .NET installation. The ImageList property of the tree view is set to this image list, the default image for all the nodes is set to the second image (with a zero-based index of 1) in the image list using the TreeView.ImageIndex property, and the image to be displayed when a node is selected is set to the third image (index of 2), using the TreeView.SelectedImageIndex property:

figs/csharpicon.gif

String[ ] arFiles = {
 @"C:Program FilesMicrosoft Visual Studio .NET 2003Common7" + 
 @"Graphicsiconscomputerform.ico",
 @"C:Program FilesMicrosoft Visual Studio .NET 2003Common7" + 
 @"Graphicsiconswin95clsdfold.ico",
 @"C:Program FilesMicrosoft Visual Studio .NET 2003Common7" + 
 @"Graphicsiconswin95openfold.ico",
 @"C:Program FilesMicrosoft Visual Studio .NET 2003Common7" + 
 @"Graphicsitmapsassortedhappy.bmp"
 };
 
for (int i = 0; i < arFiles.Length; i++)
{
 img = Image.FromFile(arFiles[i]);
 imgList.Images.Add(img);
}
 
tvw.ImageList = imgList;
tvw.ImageIndex = 1;
tvw.SelectedImageIndex = 2;

figs/vbicon.gif

' Use an array to add filenames to the ImageList
dim arFiles( ) as string = { _
 "C:Program FilesMicrosoft Visual Studio .NET 2003Common7" + _
 "Graphicsiconscomputerform.ico", _
 "C:Program FilesMicrosoft Visual Studio .NET 2003Common7" + _
 "Graphicsiconswin95clsdfold.ico", _
 "C:Program FilesMicrosoft Visual Studio .NET 2003Common7" + _
 "Graphicsiconswin95openfold.ico", _
 "C:Program FilesMicrosoft Visual Studio .NET 2003Common7" + _
 "Graphicsitmapsassortedhappy.bmp"}
 
for i = 0 to arFiles.Length - 1
 img = Image.FromFile(arFiles(i))
 imgList.Images.Add(img)
next
 
tvw.ImageList = imgList
tvw.ImageIndex = 1
tvw.SelectedImageIndex = 2

In the GetSubDirectoryNodes method, which is called to fill the nodes with subdirectories and files (if the Show Files checkbox is checked), the standard and selected images for files (as opposed to directories) are set differently from the default node images using the ImageIndex and SelectedImageIndex properties of the TreeNode class:

figs/vbicon.gif

fileNode.ImageIndex = 0
fileNode.SelectedImageIndex = 3

Looking back at the constructor, the tree view's Size property set is based on the size of the parent form.

figs/vbicon.gif

tvw.Size = new Size(ClientSize.Width - 20, Height - 200)

The tree view width is calculated based on the Width subproperty of the form's ClientSize to allow the four pixels that are occupied by the form border on each edge. Since the Location of the tree view is offset 10 pixels in from the left edge of the form, sizing it 20 pixels less than the ClientSize.Width will center the control in the form. In the vertical direction, it is not centered, so there is no compelling need to account for the small difference between ClientSize.Height and Height.

Setting the Anchor property of the tree view to all four sides has the effect of automatically resizing the control correctly when the user resizes the form (see Chapter 7 for a complete discussion of anchoring):

figs/csharpicon.gif

tvw.Anchor = AnchorStyles.Top | AnchorStyles.Left |
 AnchorStyles.Right | AnchorStyles.Bottom;

figs/vbicon.gif

tvw.Anchor = AnchorStyles.Top or AnchorStyles.Left or _
 AnchorStyles.Right or AnchorStyles.Bottom

Because the Scrollable property of the tree view is set to its default value of true, scrollbars will automatically appear in the tree view as the form and tree view are resized. This also occurs as the nodes of the tree are expanded.

Several other properties of the tree view, including FullRowSelect, ShowLines, and ShowPlusMinus, are set to their default values in the constructor to give you an opportunity to change the default values and observe the resulting behavior.

The HideSelection and HotTracking properties are set to nondefault values because they are commonly used to improve the clarity of the UI. With HideSelection set true, the selected node loses its highlighting when the form loses focus. Although this is the default value of the property, it is not the way either Windows Explorer or Outlook operate, and leaving this property true can confuse the user. Setting the HotTracking property to true causes a node to appear as a hyperlink when the mouse cursor passes over it. This is primarily a matter of personal preference.

Three other properties of the tree view are included in the constructor but commented out: Indent, Font, and ItemHeight. They are included to provide the opportunity to explore the different behavior that this control can exhibit, which is left as an exercise for the ambitious reader.

The final detail specified for the tree view in the constructor is to add an event handler for the BeforeExpand event:

figs/csharpicon.gif

tvw.BeforeExpand += new TreeViewCancelEventHandler(tvw_BeforeExpand);

figs/vbicon.gif

AddHandler tvw.BeforeExpand, AddressOf tvw_BeforeExpand

This event and its handler will be discussed shortly.

The other controls on the form are straightforward: a checkbox to toggle the display of files in the tree view, a button to display information about the currently selected node, and several buttons to control expanding and collapsing of the nodes. Each control is placed and sized using techniques described in previous chapters. All are sized and placed to make them dependent upon the size of the form, the size and location of the tree view, and the size of the current font. There are no absolute sizes or positions used, so if the user changes the default Windows font or resizes the form, everything will retain its proper look.

The Location property of the checkbox control is based on the size of the form and the Bottom property of the tree view, as well as its own width:

figs/csharpicon.gif

cb.Location = new Point((Width - cb.Width) * 2 / 10, tvw.Bottom + 25);

figs/vbicon.gif

cb.Location = new Point( _
 CType((Width - cb.Width) * 2 / 10, integer), tvw.Bottom + 25)

The size of the SelectedNode button is based on the length of its Text property and the current font and its location is based on the location of the checkbox control. It is then anchored to the bottom of the form:

figs/csharpicon.gif

int xSize = ((int)(Font.Height * .75) * btnSelected.Text.Length);
int ySize = Font.Height + 10;
btnSelected.Size = new Size(xSize, ySize);
btnSelected.Location = new Point(cb.Left, cb.Bottom + ySize);
btnSelected.Anchor = AnchorStyles.Bottom;

figs/vbicon.gif

dim xSize as integer = CType(Font.Height * .75, integer) * _
 btnSelected.Text.Length
dim ySize as integer = Font.Height + 10
btnSelected.Size = new Size(xSize, ySize)
btnSelected.Location = new Point(cb.Left, cb.Bottom + ySize)
btnSelected.Anchor = AnchorStyles.Bottom

Since the SelectedNode button has the longest Text property, the Size property of all other buttons is set to be the same as the SelectedNode button, thereby achieving a uniform look. This is accomplished with a line of code such as the following:

figs/csharpicon.gif

btnToggle.Size = new Size(btnSelected.Width, btnSelected.Height);

The location of the Toggle button is set based on the width of the form and the checkbox control's width and location:

figs/csharpicon.gif

btnToggle.Location = new Point((Width - cb.Width) * 7 / 10, cb.Top);

figs/vbicon.gif

btnToggle.Location = new Point( _
 CType((Width - cb.Width) * 7 / 10, integer), cb.Top)

The locations of all other buttons are based on the Left and Bottom properties of the button above each. As with the checkbox control, all buttons are anchored to the bottom of the form, as in:

figs/vbicon.gif

btnExpand.Location = new Point(btnToggle.Left, btnToggle.Bottom)
btnExpand.Anchor = AnchorStyles.Bottom

Each control has its own event handler added. These buttons demonstrate several of the commonly used TreeView and TreeNode properties and methods.

The SelectedNode button uses the TreeView.SelectedNode property and the TreeNode FullPath and Index properties to display information about the currently selected node. Notice how the TreeNode properties are invoked by using the SelectedNode property of the TreeView instance, as in:

figs/vbicon.gif

tvw.SelectedNode.FullPath.ToString( )

The Toggle, Expand, Expand All, and Collapse buttons all call the respective TreeNode methods. Of these, only Expand All has an equivalent TreeView method, but it is not used here because it takes too long to run. As it is, if the current node is a root level node (the root of a hard drive), it may still take some time to expand all the nodes. The Collapse All button calls a TreeView method; there is no equivalent TreeNode method.

The final line of code in the constructor is a call to the FillDirectoryTree method, which actually populates the tree. This method, and the GetSubDirectoryNodes method that it calls, is where most of the action occurs.

14.3.1.2 Populating the Tree

FillDirectoryTree is a method that fills the root level of the tree view with the logical drives on the computer, plus all first-level subdirectories. Even though the first level subdirectories are not displayed initially, it is necessary to find them so that the plus signs can be displayed next to the root nodes that have child nodes available. This method is called in two places in the program: once in the constructor and in the CheckedChanged event handler every time the state of the checkbox is changed.

The FillDirectoryTree method begins by calling the BeginUpdate method on the tree view.

figs/csharpicon.gif

tvw.BeginUpdate( );

This suspends all redraws of the tree view until the EndUpdate method is called. If BeginUpdate/EndUpdate is not used, then the tree view will redraw itself every time a node is added, with a significant performance penalty.

In FillDirectoryTree, the nodes are added one at a time by iterating through an array of strings representing the logical drives on the machine. An alternative would be to create an array of TreeNodes, and then add the entire array to the Nodes collection by using the AddRange method of the TreeNodeCollection class. In that case, there would be no benefit to using BeginUpdate and EndUpdate.

The next line calls the Clear method of the TreeNodeCollection class.

figs/csharpicon.gif

tvw.Nodes.Clear( );

Since this line is called against the Nodes collection of the tree view, it removes all the nodes and subnodes from the entire tree. If this step were omitted, then the tree would acquire a duplicate set of nodes every time the FillDirectoryTree method were called.

To create the root level nodes for this tree view, a call is made to the static (shared) GetLogicalDrives method of the Environment class. This method returns an array of strings of the form "C:", each element of which represents one of the logical drives on the machine.

There are two different static (Shared in VB.NET) methods in the .NET Framework called GetLogicalDrives: one in the Environment class and one in the Directory class. They are equivalent and interchangeable.

Each logical drive is added to the tree view by iterating through the array of strings using a foreach (for each) loop. However, the array of strings representing the logical drives includes all the logical drives on the machine, including those not ready for reading, such as an empty floppy or CD drive.

To prevent not-ready drives from displaying in the tree (which is not how Windows Explorer behaves), wrap the entire contents of the foreach loop in a try/catch block. The first line within the try block is a call to the static (shared) method GetDirectories of the Directory class. This form of the overloaded method takes a string representing a directory and returns an array of strings representing all the directories contained within the specified directory. If the drive is not ready, it will throw an exception of type System.IO.IOException, which will be caught by the first catch block. Nothing happens in this first catch block: execution is allowed to pass through, but the drive that caused the exception to be thrown will be gracefully ignored. If any other errors occur, they are caught by the second catch block, which displays a message:

figs/csharpicon.gif

foreach (string rootDirectoryName in strDrives)
{
 try 
 {
 Directory.GetDirectories(rootDirectoryName);
 TreeNode ndRoot = new TreeNode(rootDirectoryName);
 tvw.Nodes.Add(ndRoot);
 if (ndRoot.Index % 2 = = 0)
 {
 ndRoot.BackColor = Color.LightYellow;
 ndRoot.ForeColor = Color.Green;
 }
 GetSubDirectoryNodes(ndRoot, cb.Checked);
 }
 catch (System.IO.IOException)
 {
 // let it through
 }
 catch (Exception e)
 {
 MessageBox.Show(e.Message);
 }
}

figs/vbicon.gif

dim rootDirectoryName as string
for each rootDirectoryName in strDrives
 try 
 Directory.GetDirectories(rootDirectoryName)
 dim ndRoot as TreeNode = new TreeNode(rootDirectoryName)
 tvw.Nodes.Add(ndRoot)
 if ndRoot.Index mod 2 = 0 then
 ndRoot.BackColor = Color.LightYellow
 ndRoot.ForeColor = Color.Green
 end if
 GetSubDirectoryNodes(ndRoot, cb.Checked)
 catch e as System.IO.IOException
 ' let it through
 catch e as Exception 
 MessageBox.Show(e.Message)
 end try
next

Back in the try block, a TreeNode object is instantiated for each logical drive that is ready to read, and that TreeNode object is then added to the Nodes collection of the tree view. As a final touch, the foreground and background colors of every other directory is changed from the defaulti.e., if the Index property of the TreeNode object is an even value.

The final line of code inside the try block calls the method GetSubDirectoryNodes. This method, which gets called for every root node in the tree, takes two arguments: a Node object representing the current node for which it needs to get the subdirectories and a Boolean that is the value of the Show Files checkbox. Even though the subdirectories are not displayed at this point, it is necessary to get the subdirectories, if any, so that the tree view will know to display the plus sign next to each node indicating that there are subdirectories.

The first thing that happens inside GetSubDirectoryNodes is to verify that the node passed in as the argument parentNode is in fact a directory. It does this by instantiating a DirectoryInfo object on the full pathname of parentNode, using the TreeNode.FullPath property, in order to access the Attributes property of the DirectoryInfo object. The Attributes property is a bitwise combination of members of the FileAttributes enumeration, listed in Table 14-12. These attributes may pertain to either files or directories. The Attributes property of the current node is logically AND'ed with the Directory value of the FileAttributes enumeration: if the result is zero, then the node is not a directory. If it is not a directory, there can be no subdirectories to retrieve, so the method is exited immediately:

figs/csharpicon.gif

DirectoryInfo di = new DirectoryInfo(parentNode.FullPath);
if ((di.Attributes & FileAttributes.Directory) = = 0)
{
 return;
}

figs/vbicon.gif

dim di as DirectoryInfo = new DirectoryInfo(parentNode.FullPath)
if (di.Attributes and FileAttributes.Directory) = 0 then
 return
end if

If the node is in fact a directory, then processing continues.

Table 14-12. FileAttributes enumeration

Attribute

Description

Archive

File's archive bit.

Compressed

File is compressed.

Device

Reserved for future use.

Directory

Directory.

Encrypted

If a file, it is encrypted. If a directory, the default for newly created files or directories.

Hidden

File is hidden.

Normal

File is normal and has no other set attributes. Only valid if used alone.

NotContentIndexed

Not indexed by operating system's indexing service.

Offline

File data not immediately available.

ReadOnly

Read-only.

ReparsePoint

File contains reparse point.

SparseFile

Sparse file, i.e., a large file comprised mostly of zeros.

System

System file.

Temporary

Temporary file. Should be deleted as soon as not needed.

All nodes of the parentNode are cleared with the TreeNodeCollection.Clear method and an array of strings representing all the subdirectories of the parentNode is obtained with the static (shared) GetDirectories method, as was done earlier in the FillDirectoryTree method:

figs/csharpicon.gif

parentNode.Nodes.Clear( );
string[ ] arSubs = Directory.GetDirectories(parentNode.FullPath);

figs/vbicon.gif

parentNode.Nodes.Clear( )
dim arSubs( ) as string = Directory.GetDirectories(parentNode.FullPath)

Again, this array of directory strings is iterated, as was done in FillDirectoryTree, except this time there is no need to use a try/catch block, since you know that the only possible iterated contents of the array are directories.

However, some directories, specifically Hidden directories, are not readable. To avoid displaying hidden directories, instantiate a DirectoryInfo object on the directory being processed and test to see if it is a Hidden directory by logically AND'ing the DirectoryInfo Attributes with the Hidden attribute of the FileAttributes enumeration. If it is Hidden, then skip that directory.

In C#, this is achieved by using the continue keyword in the foreach loop, which causes execution to immediately start over at the top of the loop. Otherwise, instantiate a TreeNode for the subdirectory and add it to the Nodes collection of the parentNode. As before, the background color of the node is changed if its Index property is even.

figs/csharpicon.gif

foreach (string subDir in arSubs)
{
 DirectoryInfo dirInfo = new DirectoryInfo(subDir);
 if ((dirInfo.Attributes & FileAttributes.Hidden)!= 0)
 {
 continue;
 }
 
 TreeNode subNode = new TreeNode(dirInfo.Name);
 parentNode.Nodes.Add(subNode);
 
 if (subNode.Index % 2 = = 0)
 subNode.BackColor = Color.LightPink;
}

In VB.NET, the continue keyword does not exist, so the logic of the if statement must be slightly different:

figs/vbicon.gif

dim subDir as string
for each subDir in arSubs
 dim dirInfo as DirectoryInfo = new DirectoryInfo(subDir)
 
 ' do not show hidden folders
 if (dirInfo.Attributes and FileAttributes.Hidden) = 0 then
 
 dim subNode as TreeNode = new TreeNode(dirInfo.Name)
 parentNode.Nodes.Add(subNode)
 
 ' Set colors based on Index property.
 if (subNode.Index mod 2 = 0) then
 subNode.BackColor = Color.LightPink
 end if
 end if
next

As you will recall, the second argument passed in to the GetSubDirectoryNodes method is the value of the ShowFiles checkbox. If that checkbox is checked, then files and directories will be displayed in the tree view. The next section of code is where that functionality is implemented:

figs/csharpicon.gif

if (getFileNames)
{

figs/vbicon.gif

if getFileNames then

Similarly to how directories were handled previously in this method, the static (shared) Directory.GetFiles method is called to fill an array of strings with all the files in the directory:

figs/csharpicon.gif

string[ ] files = Directory.GetFiles(parentNode.FullPath);

figs/vbicon.gif

dim files( ) as string = Directory.GetFiles(parentNode.FullPath)

The array of strings is iterated with a foreach (for each) loop. For each file in the directory, a FileInfo object is created and a TreeNode object is instantiated to encapsulate that FileInfo object and added to the Nodes collection. The image to use for each file is set with the TreeNode.ImageIndex property, and the image to use when the file is selected is set with the TreeNode.SelectedImageIndex property. Both image properties are set to the zero-based index of the image list created in the constructor. Finally, the background color of the file is set to LightGreen if the Index of the node is even:

figs/csharpicon.gif

foreach (string str in files)
{
 FileInfo fi = new FileInfo(str);
 TreeNode fileNode = new TreeNode(fi.Name);
 parentNode.Nodes.Add(fileNode);
 
 fileNode.ImageIndex = 0;
 fileNode.SelectedImageIndex = 3;
 
 if (fileNode.Index % 2 = = 0)
 fileNode.BackColor = Color.LightGreen;
}

figs/vbicon.gif

dim str as string
for each str in files
 dim fi as FileInfo = new FileInfo(str)
 dim fileNode as TreeNode = new TreeNode(fi.Name)
 parentNode.Nodes.Add(fileNode)
 
 fileNode.ImageIndex = 0
 fileNode.SelectedImageIndex = 3
 
 if fileNode.Index mod 2 = 0 then
 fileNode.BackColor = Color.LightGreen
 end if
next

14.3.1.3 Look Ma, no recursion

Nowhere in the discussion so far have you seen the word "recursion." Yes, the GetSubDirectoryNodes method was called back in the FillDirectoryTree method so the plus signs could be properly displayed next to nodes that have subdirectories. That only goes down one level from the root, though. Inside GetSubDirectoryNodes, no recursion takes place to determine the full tree structure. So how does the application get the full tree structure? Each deeper level of the tree is determined just before its parent node is expanded, by handling the BeforeExpand event raised by the TreeView control. TreeView events will be discussed shortly.

You could implement a traditional recursive technique for filling the tree by commenting out the line of code in the constructor that adds an event handler to the BeforeExpand event delegate. That line looks like:

figs/csharpicon.gif

tvw.BeforeExpand += new TreeViewCancelEventHandler(tvw_BeforeExpand);

figs/vbicon.gif

AddHandler tvw.BeforeExpand, AddressOf tvw_BeforeExpand

Then in the GetSubDirectoryNodes method, add a line of code that calls itself again, this time passing in the current node as the parent node. You would insert this line of code just before the close of the foreach (For Each in VB.NET) loop:

figs/csharpicon.gif

GetSubDirectoryNodes(subNode, getFileNames);

The problem with this approach becomes apparent when you run the program: it takes a long time.[1] As soon as the program is executed, and again whenever the Show Files checkbox is changed, the entire directory structure is searched to find all subdirectories and (if the Show Files checkbox is checked) all files. On my machine (a reasonably fast machine with a typically complex directory structure), this takes well over a minuteclearly too long to wait.

[1] This slower method was shown in Programming C#, by Jesse Liberty (O'Reilly). Live and learn.

14.3.1.4 TreeView events

As just mentioned, the BeforeExpand event is trapped to fill each successive level of the tree hierarchy just in time (immediately before it is displayed). The TreeView class has many events, including several listed in Table 14-13 that are not inherited from Control. (For a general discussion of events, see Chapter 4) These events come in pairs: one before the action and one after.

All "before" events, with the exception of BeforeLabelEdit, take an event argument of type TreeViewCancelEventArgs. This event argument exposes the properties listed in Table 14-14. The Action property tells you the type of action that raised the event, such as keyboard action or mouse action. (All the possible values of this property are members of the TreeViewAction enumeration, listed in Table 14-15.) The Cancel property of TreeViewCancelEventArgs can be set to false if you want to cancel the operation. The Node property returns the node that raised the event. This is probably the most commonly used property because it provides access to all the TreeNode properties, including the Nodes collection, which is used in the tvw_BeforeExpand event handler Example 14-3 and Example 14-4.

All the "after" events, again with the exception of AfterLabelEdit, take an event argument of type TreeViewEventArgs. This event argument is virtually identical to the TreeViewCancelEventArgs event argument of the "before" events, except that there is no Cancel property. This makes sense: you cannot cancel an event after it has already occurred.

Both the BeforeLabelEdit and AfterLabelEdit events take the same event argument: NodeLabelEditEventArgs, whose properties are listed in Table 14-16. These properties let you edit the text associated with a node and cancel the editing process, discarding any changes made to the text.

Table 14-13. TreeView events

Event

Event argument

Description

BeforeCheck

TreeViewCancelEventArgs

Raised before node checkbox is checked. Only raised if the CheckBoxes property is true.

BeforeCollapse

TreeViewCancelEventArgs

Raised before the node is collapsed.

BeforeExpand

TreeViewCancelEventArgs

Raised before the node is expanded.

BeforeLabelEdit

NodeLabelEditEventArgs

Raised before the node label is edited.

BeforeSelect

TreeViewCancelEventArgs

Raised before the node is selected. Only raised if the CheckBoxes property is false.

AfterCheck

TreeViewEventArgs

Raised after node check box is checked. Only raised if the CheckBoxes property is true.

AfterCollapse

TreeViewEventArgs

Raised after the node is collapsed.

AfterExpand

TreeViewEventArgs

Raised after the node is expanded.

AfterLabelEdit

NodeLabelEditEventArgs

Raised after the node label is edited.

AfterSelect

TreeViewEventArgs

Raised after the node is selected. Only raised if the CheckBoxes property is false.

Table 14-14. TreeViewCancelEventArgs and TreeViewEventArgs properties

Property

Description

Action

Returns the type of action that raised the event. Possible values are members of the TreeViewAction enumeration, listed in Table 14-15.

Cancel

TreeViewCancelEventArgs only. Read-write Boolean value indicating if event should be canceled. Inherited from CancelEventArgs.

Node

Returns the node that is checked, collapsed, expanded, or selected.

Table 14-15. TreeViewAction enumeration

Value

Description

ByKeyboard

Event caused by keystroke.

ByMouse

Event caused by mouse operation.

Collapse

Event caused by collapsing TreeNode.

Expand

Event caused by expanding TreeNode.

Unknown

Event cause is unknown.

Table 14-16. NodeLabelEditEventArgs properties

Property

Description

CancelEdit

Read-write Boolean value indicating if the edit has been canceled.

Label

Returns the new text associated with the node.

Node

Returns the node whose text is to be edited.

Look at the tvw_BeforeExpand event handler from Example 14-3 and Example 14-4, reproduced here:

figs/csharpicon.gif

private void tvw_BeforeExpand(object sender,
 TreeViewCancelEventArgs e)
{
 tvw.BeginUpdate( );
 foreach (TreeNode tn in e.Node.Nodes)
 {
 GetSubDirectoryNodes(tn, cb.Checked);
 }
 tvw.EndUpdate( );
}

figs/vbicon.gif

private sub tvw_BeforeExpand(ByVal sender as object, _
 ByVal e as TreeViewCancelEventArgs)
 tvw.BeginUpdate( )
 dim tn as TreeNode
 for each tn in e.Node.Nodes
 GetSubDirectoryNodes(tn, cb.Checked)
 next
 tvw.EndUpdate( )
end sub

You see that the two passed-in arguments are an object, named Sender, representing the source of the event and an event argument of type TreeViewCancelEventArgs. The contents of the method are bookended by BeginUpdate and an EndUpdate method calls so that performance will not suffer when the call to GetSubDirectoryNodes adds nodes to the tree.

The Nodes collection of the node that is about to be expanded is iterated via e.Node.Nodesi.e., the Nodes collection of the Node property of the event argument e. For each node in this collection, GetSubDirectoryNodes is called, passing to it the current node in the iteration, as well as the value of the Show Files checkbox. This expands the tree one level deep at a time, which occurs nearly instantaneously for all but the most extensive hierarchies or the slowest network connections.





Programming. NET Windows Applications
Programming .Net Windows Applications
ISBN: 0596003218
EAN: 2147483647
Year: 2003
Pages: 148
Simiral book on Amazon

Flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net