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.
|
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).
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. |
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. |
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. |
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. |
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. |
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
Example 14-3. TreeView control in C# (TreeViews.cs)
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)
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:
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;
' 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:
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.
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):
tvw.Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right | AnchorStyles.Bottom;
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:
tvw.BeforeExpand += new TreeViewCancelEventHandler(tvw_BeforeExpand);
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:
cb.Location = new Point((Width - cb.Width) * 2 / 10, tvw.Bottom + 25);
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:
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;
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:
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:
btnToggle.Location = new Point((Width - cb.Width) * 7 / 10, cb.Top);
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:
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:
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.
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.
|
The next line calls the Clear method of the TreeNodeCollection class.
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.
|
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:
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); } }
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:
DirectoryInfo di = new DirectoryInfo(parentNode.FullPath); if ((di.Attributes & FileAttributes.Directory) = = 0) { return; }
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.
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:
parentNode.Nodes.Clear( ); string[ ] arSubs = Directory.GetDirectories(parentNode.FullPath);
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.
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:
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:
if (getFileNames) {
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:
string[ ] files = Directory.GetFiles(parentNode.FullPath);
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:
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; }
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:
tvw.BeforeExpand += new TreeViewCancelEventHandler(tvw_BeforeExpand);
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:
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.
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. |
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. |
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. |
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:
private void tvw_BeforeExpand(object sender, TreeViewCancelEventArgs e) { tvw.BeginUpdate( ); foreach (TreeNode tn in e.Node.Nodes) { GetSubDirectoryNodes(tn, cb.Checked); } tvw.EndUpdate( ); }
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.