TreeView Control

Like the ListView control, TreeView is another control that you are already familiar with. Open Windows Explorer; the list of directories in the left pane is a tree view. Another example is the Properties window for the MS Visual J Plus Plus Forms Designer.

The ListView control is ideal for displaying large amounts of data along with descriptive information. However, it can't display the hierarchical nature of some information, and this is where the TreeView control comes in. The ability to display hierarchical information makes a tree view the preferred means of displaying directory information.

The following TreeViewDemo program creates a TreeView object to display the hierarchy of a given directory. The program populates the tree view with each file in the directory. Subdirectory branches are populated with the subdirectory's contents.

Forms Designer Work

There isn't much design work to this particular demo. Create a new Windows app in the conventional way, and name this project TreeViewDemo. In the Forms Designer, add a TreeView control from the Toolbox to the form Form1. You will need to rename the new control to treeView, but most of the other properties in the Properties window are already set correctly.

Add a single-line edit control along the bottom of the form. Change its name to edit. Resize the form so that it is considerably taller than it is wide, and then resize the TreeView and Edit controls so that they fill most of the form. Anchor the TreeView control to all four sides of the form. Anchor the Edit control to the left, right, and bottom sides, but not to the top. This will ensure that the Edit control continues to hug the bottom edge if the user resizes the form.

Screenshot-6 shows the resulting form in the Forms Designer window and the properties for the TreeView control.

Java Click to view at full size.

Screenshot-6. The Forms Designer work for the TreeViewDemo app is fairly simple.

Code

Writing the code for the TreeViewDemo app is considerably more complicated than creating the form. The problem lies in the fact that each node that represents a subdirectory of the current directory must be able to be expanded. Any subdirectories within that subdirectory must also be expandable, and so on until the app has built a maze of branches with each branch reaching down into the farthest subdirectory.

Before I show the source code, let me present a coding technique called recursion that helps simplify this problem.

Recursion

Consider for a moment the following problem of calculating the factorial of a number. The factorial of N is equal to N * factorial (N-1). The factorial of 0 is 1. Knowing this, I could write a factorial() function as follows. (I'm not saying this is the best way to implement a factorial; it is merely one way.)

public static int factorial(int N)
{
 // handle the known case
 if (N == 0)
 {
 return 1;
 }
 // call yourself to calculate the factorial
 return N * factorial(N - 1);
}


At first glance, it would appear that by calling itself this function generates an infinite loop, but consider for a second how this function works. Suppose a program calls factorial(5). Since 5 is not equal to 0, the function would calculate 5 * factorial(4), and factorial(4) would then call factorial(3). This process would be repeated until eventually factorial(1) calculated 1 * factorial(0). At this point, factorial(0) would return a 1, which would allow the factorial(1) calculation to continue and return a 1 to allow the factorial(2) calculation to proceed, and so forth. This approach of having a function call itself is called recursion.

Applying recursion to TreeViewDemo

Although the problem of expanding the base node of the tree is very different from calculating a factorial, it is similar in one respect. Once a node for a new subdirectory has been added, the entire process of creating a list of entries representing the contents of that subdirectory must be duplicated. After the new node has been added for the subdirectory, the problem from that point onward is exactly the same as the problem of adding the first directory. That being the case, the easiest approach to solving this problem is for the function to call itself recursively.

Let's see how recursion works to create a tree view. The source code for TreeViewDemo appears as follows:

import com.ms.wfc.app.*;
import com.ms.wfc.core.*;
import com.ms.wfc.ui.*;
import com.ms.wfc.html.*;
import com.ms.wfc.io.*;
import com.ms.wfc.util.*;
/**
 * Demonstrate the TreeView class by adding a node for each file in
 * the specified directory. Expand subdirectories to include their * files as well.
 */
public class Form1 extends Form
{
 public Form1(String[] args)
 {
 // Required for Visual J Plus Plus Form Designer support
 initForm();
 // expand the specified node
 String initialPath = File.getCurrentDirectory();
 if (args.length == 1)
 {
 initialPath = args[0];
 }
 treeView.addNode(expandTreeNode("", initialPath));
 }
 /**
 * Expand the directory dirName within path.
 */
 TreeNode expandTreeNode(String path, String dirName)
 {
 // create a node with that directory name
 TreeNode node = new TreeNode(dirName);
 // combine the path with the directory name
 path = File.combine(path, dirName);
 // now create two lists containing the contents of the
 // directory pointed to by path
 List dirList = new List();
 List fileList= new List();
 createSortedLists(path, dirList, fileList);
 // add the directories…
 addDirsToNode(path, node, dirList);
 // then add the files
 addFilesToNode(node, fileList);
 return node;
 }
 /**
 * Add a list of directories to the node pointed to by path.
 */
 void addDirsToNode(String path, TreeNode node, List list)
 {
 // iterate through the list of directories
 IEnumerator itemEnumerator = list.getItemEnumerator();
 while (itemEnumerator.hasMoreItems())
 {
 // get the directory name from the list item
 String dirName = (String)itemEnumerator.nextItem();
 // now add a new node with that name; expand the
 // node to include the contents of the directory as well
 node.addNode(expandTreeNode(path, dirName));
 }
 }
 /**
 * Add the list of file names to the specified node.
 */
 void addFilesToNode(TreeNode node, List list)
 {
 // iterate through the list
 IEnumerator itemEnumerator = list.getItemEnumerator();
 while (itemEnumerator.hasMoreItems())
 {
 // add each file name to the node
 String fileName = (String)itemEnumerator.nextItem();
 node.addNode(new TreeNode(fileName));
 }
 }
 /**
 * Put each of the elements of path into either the dirList
 * or the fileList and then sort both.
 */
 void createSortedLists(String path,
 List dirList,
 List fileList)
 {
 // create a FileEnumerator in order to iterate through the
 // current path
 FileEnumerator fe = new FileEnumerator(File.combine(path, "*.*"));
 for(;fe.hasMoreFiles(); fe.getNextFile())
 {
 String fileName = fe.getName();
 // ignore the "." and ".." directories
 if (fileName.equals(".") ||
 fileName.equals(".."))
 {
 continue;
 }
 // if this is a directory…
 if (File.isDirectory(File.combine(path, fileName)))
 {
 // then add it to the directory list;…
 dirList.addItem(fileName);
 }
 else
 {
 // otherwise, add it to the file list
 fileList.addItem(fileName);
 }
 }
 // now sort the lists
 dirList.sort(new FileNameComparer());
 fileList.sort(new FileNameComparer());
 }
 /**
 * Implement the IComparer interface to support List.sort().
 */
 class FileNameComparer implements IComparer
 {
 /**
 * Compare o1 to o2; return -1, 0, or 1.
 */
 public int compare(Object o1, Object o2)
 {
 String f1 = (String)o1;
 String f2 = (String)o2;
 return StringSorter.compare(f1, f2,
 StringSorter.STRINGSORT | StringSorter.IGNORECASE);
 }
 }
 .
 .
 .
 /**
 * Invoked when a node in the TreeView is selected.
 */
 private void treeView_afterSelect(Object source, TreeViewEvent e)
 {
 TreeNode node = treeView.getSelectedNode();
 String fileName = node.getText();
 edit.setText(fileName);
 }
 /**
 * NOTE: The following code is required by the Visual J Plus Plus form
 * designer. It can be modified using the form editor. Do not
 * modify it using the code editor.
 */
 Container components = new Container();
 TreeView treeView = new TreeView();
 Edit edit = new Edit();
 private void initForm()
 {
 // …generated by Forms Designer…
 }
 /**
 * The main entry point for the app. *
 * @param args Array of parameters passed to the app
 * via the command line.
 */
 public static void main(String args[])
 {
 app.run(new Form1(args));
 }
}


As always, main() creates an object of class Form1. This time I added the args argument to the Form1 constructor to give the user the option of viewing a directory other than the current directory. After the standard call to initForm(), Form1() creates a string variable initialPath setting it equal to the argument passed to the program, if there is one, or the current directory. Form1() then creates a directory node at that directory by calling expandTreeNode() and attaches the node to the treeView object.

The expandTreeNode() method creates an object of class TreeNode to represent the current directory. Much like the ListView object, you do not attach normal components like a String object directly to a TreeView object. You must first wrap the String in a tree node, and then you attach the tree node to the tree view. Unlike the list view, however, you can also attach a tree node to another tree node. This capability gives the tree view its hierarchical affect.

Once the tree node for the current directory has been created, the expandTreeNode() method calls createSortedLists() to create two lists, one of all the subdirectories in the current directory and the other containing the names of all the files.

The createSortedLists() method is similar to the addFilesToListView() method in the previous example in that it iterates through the contents of the specified directory. Every file it finds (other than the two directories "." and "..", which it ignores) are placed into one of two List objects. If the file represents a directory, it is placed in the dirList object. If it is not a directory, the name is placed in the fileList object. Both lists are then sorted. This approach of maintaining two lists makes createSortedLists() slightly more complicated than its addFilesToListView() predecessor. However, it avoids the need to iterate through the directory twice, once for the subdirectories and again for the non-subdirectories.

Once expandTreeNode() has captured the contents of the current directory in the two lists and sorted them, it adds the contents of the directory list to the current node by calling the addDirsToNode() method and adds the contents of the file list by calling addFilesToNode(). Since the latter is the easier of the two, let's consider it first.

The addFilesToNode() method starts by creating an itemEnumerator interface for the file list. It uses itemEnumerator to iterate through the list until hasMoreItems() returns false. The call to nextItem() returns the current file name string and moves the iterator to the next member in the list. A TreeNode object is built to contain the file name returned from nextItem(), and then the TreeNode object is added to the parent node by calling addNode().

The addDirsToNode() method begins in much the same way, by creating and using an itemEnumerator interface to iterate through the list of subdirectory names. Rather than simply create a TreeNode object to add to the parent node, however, this method calls the expandTreeNode() method to add a new directory node, passing it the name of the subdirectory and the path to the current parent node.

As you know already, expandTreeNode() continues the process by generating a list of all of the files in that subdirectory. Each of the subdirectories of that subdirectory is then passed to expandTreeNode() in turn. In this way, the program uses expandTreeNode() to recursively burrow deeper and deeper into the hierarchy of subdirectories until it eventually reaches the end: the subdirectories that contain only files and no further subdirectories.

I added one event handler function to TreeViewDemo. The afterSelect event occurs after the user has selected a tree node. To select a tree node, the user clicks the plus sign in front of the node name. In this case, the treeView_afterSelect() method retrieves the currently selected node and displays the file name associated with this node in the edit window at the bottom of the form.

Screenshot-7 shows the results of executing TreeViewDemo on my \TEMP directory. As you can see, the directories are listed first. Those nodes that have subnodes are listed with small plus signs. Clicking a plus sign causes the node to expand and show the subnodes within.

Screenshot

Screenshot-7. The results of executing the TreeViewDemo on my \TEMP directory. Comments