Putting It All Together with FileMgr

Both the ListViewDemo and TreeViewDemo apps were intended as simple demos of the ListView and TreeView controls. Even so, the ListViewDemo program uses the list view to effectively give the user access to the contents of a directory, and the TreeViewDemo program demonstrates how the tree view control gives the user a good overview of their directory structure. If we combine these two programs, we can generate a powerful disk copy utility. In fact, this utility would closely resemble Windows Explorer, which comes with 32-bit versions of Windows.

We'll create such a utility, called FileMgr, as an example of an almost-real-world app. FileMgr will have a TreeView control on the left and a ListView control on the right. The tree view will be used to display the directory hierarchy. As the user selects a directory in the tree view, the display of the list view on the right will immediately update to display the files in that directory. The user will be able to select any number of files from the list view and drag them over to the tree view. Dropping the selected files on one of the nodes will cause the FileMgr app to copy the files to the directory at the specified node. Once the files are copied, the user will be given the option of deleting the source files. An edit window across the bottom of the FileMgr window will be used to display the copy and delete commands as they are carried out.

Forms Designer Work

Begin by copying a TreeView control to the left side and a ListView control to the right side of the form created by the Windows app builder. Place an Edit control along the bottom. Expand the TreeView and ListView controls so that they take up most of the available space in the form, leaving a comfortable margin between all three controls. The TreeView control doesn't need as much room horizontally as the ListView control. Therefore, I assigned about one third of the horizontal space to the TreeView control and two thirds to the ListView control. You might want to peek ahead to Figure 8-8 to see what these controls look like in the Forms Designer.

Anchor the TreeView control to the top, bottom, and left of the form window. Anchor the ListView control to the top, bottom, and right of the form. Anchoring these two controls to both the top and bottom will let the Windows Foundation Classes for Java (WFC) take care of resizing the controls vertically as the form is resized; however, we will resize them horizontally to maintain the 1/3 to 2/3 ratio. Finally, anchor the Edit control to the left, right, and bottom of the form.

Setting the TreeView control properties

First, rename the TreeView object to treeView. Since the white background of the TreeView control looks a bit garish compared to the gray background of the form, set the TreeView control's background color to Control (gray). Since we plan to implement in-process drag-and-drop, set the control's allowDrop flag to true.

I didn't worry about using images in the TreeViewDemo demo app; however, in this more realistic app we will need to adorn our TreeView control with images.

You can assign two different images to a TreeView control. One image is used for the currently selected node and the other for all other nodes. Normally, a small image of a partially opened folder is used for the selected node and an image of a closed folder is used for the rest. To find such an image, I searched my hard disk for all .ico (icon) files. The two files named OPENFOLD.ICO and CLSDFOLD.ICO appeared to be exactly what I wanted. I created a subdirectory of the project directory named images and copied these two files into that directory. If you can't find suitable images either lying about on your hard disk or somewhere on the Internet, you can create them in Microsoft Paint or some other graphics program. Make the images 16-by-16 pixels in size. Of course, you can always use the images included with this app on the companion CD-ROM.

Drag an ImageList control from the Toolbox, and drop it somewhere on the Forms Designer. Add the two folder images to the ImageList using the Image Editor.

Returning to the TreeView control, set its imageList property to imageList (the name of the image list you just created); set its imageIndex property to be the index of the closed folder image in the image list; and set its selectedImageIndex property to be the index of the open folder image.

Setting the ListView control properties

First rename the ListView control to listView. Again set the background color to Control to match the background of the TreeView control and the form. Set the control's view property to Report; this will ensure that the file details appear next to the file name. Add three column headers, and set their text to File Name, File Size, and Last Modified. Leave the column properties as they are except for the File Size column. Since this column will be used to display numbers, set its textAlign property to Right rather than the default Left.

Set the fullRowSelect property of the ListView to true. That way, when the user selects any one of the properties the entire row will be selected. I set the gridLines property to true because I prefer to see the gray gridlines; however, you can set this property as you please.

Since I personally think that icons don't add that much to the file list, I did not bother to define an image list for the ListView control.

Setting the Edit control properties

Rename the Edit control to edit. Clear its text property setting so that it is blank. Since this Edit control is used only to receive output, set the readOnly property to true. Finally, I set the background color to Control to match the other two controls and the form.

The resulting form in the Forms Designer is shown in Figure 8-8.

Java Click to view at full size.

Screenshot-8. The FileMgr app in the Forms Designer.


The code for FileMgr appears in all its glory 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.*;
 * This class can take a variable number of parameters on the command
 * line. Program execution begins with the main() method. The class
 * constructor is not invoked unless an object of type 'Form1' is
 * created in the main() method.
public class Form1 extends Form
 String directory;
 public Form1(String[] args)
 // Required for Visual J Plus Plus Form Designer support
 // start either from the current directory
 // or from the path specified in the argument list
 directory = File.getCurrentDirectory();
 if (args.length == 1)
 directory = args[0];
 // now populate the treeView with the directory
 // tree starting at the current directory
 treeView.addNode(expandTreeNode("", directory));
 // populate the list with the files in the
 // current directory
 * Create the fileView list.
 private static final ListItem[] clear = new ListItem[0];
 void createList()
 // first clear out the list view contents by passing
 // it a zero length array of list items
 // reset background color after clearing the list
 // now create a list of just the files (no directories)
 List fileList = createSortedList(directory, false);
 // now add them to the list view
 * Expand the directory dirName within path.
 TreeNode expandTreeNode(String path, String dirName)
 // create a node with that directory name
 TreeNode node = new MyTreeNode(dirName, path);
 // combine the path with the directory name
 path = File.combine(path, dirName);
 // create the directory list
 List dirList = createSortedList(path, true);
 // add the directories (don't add the files)
 addDirsToNode(path, node, dirList);
 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
 ListItem li = (ListItem)itemEnumerator.nextItem();
 String dirName = li.getText();
 // 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 ListItem objects to listView.
 void addFilesToListView(List list)
 // iterate through the list
 IEnumerator itemEnumerator = list.getItemEnumerator();
 while (itemEnumerator.hasMoreItems())
 // add each file name to the node
 ListItem li = (ListItem)itemEnumerator.nextItem();
 * Create a list of ListItems containing either the
 * names of the directories or the files contained in
 * the directory specified by path.
 static final String[] nullStringArray = new String[]{null, null};
 List createSortedList(String path, boolean wantDirectory)
 List list = new List();
 // 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(".") ||
 // ignore those things that we don't want
 if (File.isDirectory(File.combine(path, fileName))
 == wantDirectory)
 // add the entry to the list (we only need
 // the file size and time of last modification
 // for the files, not the directories)
 String[] info = nullStringArray;
 if (wantDirectory == false)
 // this is a file, get the extra information
 info = new String[2];
 info[0] = Long.toString(fe.getSize());
 Time t = fe.getLastWriteTime();
 info[1] = t.toString();
 list.addItem(new ListItem(fileName, -1, info));
 // now sort the list
 list.sort(new FileNameComparer());
 return list;
 * 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 = ((ListItem)o1).getText();
 String f2 = ((ListItem)o2).getText();
 return StringSorter.compare(f1, f2,
 StringSorter.STRINGSORT | StringSorter.IGNORECASE);
 * As the form resizes, allocate 1/3 of the horizontal
 * space to the treeView and 2/3 to the listView.
 private void Form1_resize(Object source, Event e)
 // get the size of the form (don't let it get smaller
 // than some minimum)
 Point formSize = getSize();
 if (formSize.x < 100)
 formSize.x = 100;
 // divide the width 1/3 vs. 2/3
 int treeWidth = (formSize.x - 30) / 3;
 int listWidth = treeWidth * 2;
 // set the treeWidth
 Point treeSize = treeView.getSize();
 treeSize.x = treeWidth;
 // now position and size the listView
 // (positioning is necessary because the listView
 // is right justified in the form)
 Point listSize = listView.getSize();
 listSize.x = listWidth;
 Point listLoc = listView.getLocation();
 listLoc.x = treeWidth + 20;
 listView.setLocation(listLoc); }
 * As the selected tree element changes, update the
 * right side of the display.
 private void treeView_afterSelect(Object source, TreeViewEvent e)
 MyTreeNode tn = (MyTreeNode)treeView.getSelectedNode();
 directory = tn.getFullPath();
 * Begin the drag operation by copying the selected files
 * onto the Clipboard using the "FileName" descriptor.
 TreeNode previousNode = null;
 private void listView_itemDrag(Object source, ItemDragEvent e)
 // save the currently selected node so that we can return
 // there after the drag-and-drop operation is done
 previousNode = treeView.getSelectedNode();
 // now get the selected files and store them into a
 // MemoryStream (the way Windows Explorer does it)
 ListItem[] selected = listView.getSelectedItems();
 MemoryStream ms = new MemoryStream();
 for (int i = 0; i < selected.length; i++)
 String fileName = selected[i].getText();
 fileName = File.combine(directory, fileName) + ";";
 byte[] buffer = fileName.getBytes();
 // store the MemoryStream onto the Clipboard with the
 // type name "FileMgr"
 DataObject dobj = new DataObject("FileMgr", ms);
 // now perform the drag-and-drop operation
 doDragDrop(dobj, DragDropEffect.COPY);
 // clear out the previousNode field
 previousNode = null;
 * Process the drop operation by reading the FileName
 * entries out and copying them to the selected directory.
 private void treeView_dragDrop(Object source, DragEvent e)
 // read the filenames out of the DragEvent
 IDataObject data = e.data;
 MemoryStream ms = (MemoryStream)data.getData("FileMgr");
 byte[] buffer = ms.toByteArray();
 String files = new String(buffer);
 // loop through the files passed
 int start = 0;
 int end;
 String targetDirectory = directory;
 while ((end = files.indexOf(";", start)) != -1)
 // get the next file
 String file = files.substring(start, end);
 // create the destination file name by extracting
 // the file name from the source path and prepending
 // the target directory name
 String dest = File.combine(targetDirectory,
 // now perform the copy - alert if it fails
 String s = file + "->" + dest;
 edit.setText("copying " + s);
 File.copy(file, dest);
 edit.setText("copied " + s);
 catch (Exception ex)
 MessageBox.show("Copy failed", "Error",
 MessageBox.ICONERROR | MessageBox.OK);
 // return the display to the source node
 if (previousNode != null)
 if (MessageBox.show("Delete " + file + "?",
 MessageBox.ICONQUESTION | MessageBox.YESNO)
 == DialogResult.YES)
 // File.delete(file);
 edit.setText("deleted " + file);
 catch (Exception ex)
 MessageBox.show("Delete failed", "Error",
 MessageBox.ICONERROR | MessageBox.OK);
 // refresh source directory list
 // start looking again after the previous ";"
 start = end + 1;
 * As the mouse drags over a tree node, make the tree node active
 * so the user can see its contents.
 private static Point point = new Point();
 private void treeView_dragOver(Object source, DragEvent e)
 // get the location of the mouse pointer on the screen
 point.x = e.x;
 point.y = e.y;
 // now convert this into the location within the
 // treeView object
 point = treeView.pointToClient(point);
 // find the node at that location…
 TreeNode node = treeView.getNodeAt(point);
 if (node != null)
 // and make it active
 * Delete the files selected in the listView.
 private void listView_keyDown(Object source, KeyEvent e)
 if (e.getKeyCode() == Key.DELETE)
 // delete all of the files currently selected
 // in the listView
 ListItem[] list = listView.getSelectedItems();
 for (int i = 0; i < list.length; i++)
 String fileName = list[i].getText();
 fileName = File.combine(directory, fileName);
 edit.setText("deleting " + fileName);
 // File.delete(fileName);
 edit.setText("deleted " + fileName);
 // refresh the listView
 * 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();
 ListView listView = new ListView();
 ColumnHeader columnHeader1 = new ColumnHeader();
 ColumnHeader columnHeader2 = new ColumnHeader();
 ColumnHeader columnHeader3 = new ColumnHeader();
 Edit edit = new Edit();
 ImageList imageList = new ImageList();
 private void initForm()
 // …generated by the 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));
class MyTreeNode extends TreeNode
 String path;
 MyTreeNode(String name, String path)
 this.path = File.combine(path, name);
 String getPath()
 return path;

As you might have expected, the first part of FileMgr looks like a combination of the code for ListViewDemo and TreeViewDemo. The Form1() constructor starts by calling initForm(). It then initializes the directory string variable, which contains a reference to the current directory, returned from File.getCurrentDirectory(), or the argument passed to the program if there is one. (As always, supporting the single argument is primarily to support someone dragging a directory onto the program. I don't really expect anyone to type in a directory path when executing the program from the command line of an MS-DOS box.) The Form1() constructor then populates the tree view by calling addNode() and passing it the base directory, and populates the list view by calling createList().

These first few functions work more or less the same as their cousins shown earlier in this chapter. I did convert createSortedList() back to creating a single list of either the directories or the files in a directory and not both. This is because the tree view needs the directories and the list view needs the files, but neither needs both. Generating a list of both directories and files seemed to slow down the population of the tree view unnecessarily.

Notice also that createSortedList() creates a list of ListItem objects since this is what the list view requires. In the case of the tree view, addFilesToNode() doesn't use the ListItem objects other than to call ListItem.getText() to retrieve the file name of each list item.

Resizing the two views dynamically

Starting with Form1_resize(), we run into all new code. The anchoring mechanism built into WFC is great at handling the resizing of objects within a form as the user resizes the form, but it can't handle all circumstances under which objects need to be resized. In particular, I wanted the tree view to always occupy the left third of the form and the list view to occupy the right two-thirds. To maintain this ratio, I had to write my own function. Notice that I am only concerned with the horizontal sizing; WFC can handle the vertical resizing of the two views just fine.

Using the Properties window of the Forms Designer, I attached Form1_resize() to the resize event of Form1. This event occurs repeatedly as the form is resized. Form1_resize() begins by asking the form exactly how large it is. The function then makes sure that the horizontal size of the function is not less than 100. This sets the minimum width of the app to 100 pixels—anything less than that and you really can't see either the tree view elements or the list view elements.

Once the minimum has been established, Form1_resize() calculates the width of the tree view and of the list view, assigning the results to the variables treeWidth and listWidth. The widths are calculated by taking one-third and two-thirds of the form width minus 30. The function subtracts 30 from the form width because it knows that there is a 10-pixel-wide border on both the left and right side of the form plus it intends to put a similar 10-pixel border between the two lists.

Setting the width of the treeView object is merely a matter of asking the treeView object how big it is now, and then replacing the current width with the calculated width.

Setting the width of the listView object is a little more complicated, because in addition to setting its width, Form1_resize() must position the listView object 10 pixels to the right of the right border of the treeView object. The extra 10 pixels are to maintain a 10-pixel border between the two views.

Updating the file list dynamically

I created the treeView_afterSelect() method to handle the afterSelect event of the treeView object. This event occurs when the user selects a new node in the tree view hierarchy. The "after" in the event name comes into play because this method is invoked after WFC has updated the display to reflect the selection.

The treeView_afterSelect() method asks the treeView which node is selected by calling getSelectedNode(). You'll notice that I've coded treeView_afterSelect() to convert the TreeNode object returned from getSelectedNode() into something called MyTreeNode before using it.

What is MyTreeNode?

Just as the ListView class is normally populated by ListItem objects, so the TreeView class is populated by objects of class TreeNode. It is the text associated with the TreeNode that is displayed in the tree view. The problem is that the TreeNode object contains only the name of the directory. When the user selects a TreeNode, however, we need the entire path to that directory in order to know how to display the contents of that directory in the list view on the right.

One solution to this problem would be to store the entire path in the TreeNode object before we attach it to the TreeView object. This is not satisfactory, however, because doing so would cause the TreeView object to display this path information on every node. Such information would be annoying because it would be redundant. The path to a particular directory is made obvious by the tree view display itself.

A better solution is to create our own class that extends TreeNode by storing the path information that we need. The class MyTreeNode, defined at the bottom of the Form1.java file, adds a data member path and a method getPath() that returns the path information to the base TreeNode class. In every other respect, MyTreeNode is a TreeNode, so it has the ability to be attached to a TreeView.

This class creation is an important trick to remember.

It is perfectly legal to extend a WFC class to add your own data members or methods and then use that class in place of the base class.

As the tree nodes are being constructed, Form1.expandTreeNode() adds MyTreeNode objects containing both the directory name and the path to that directory to the tree view. Thus, when the user selects a node he or she is actually selecting an object of class MyTreeNode. (Remember that because MyTreeNode extends the class TreeNode, an object of class MyTreeNode is also of class TreeNode.) The method treeView_afterSelect() calls the method getSelectedNode() which returns a TreeNode class; however, since the objects returned are the same MyTreeNode objects we added earlier, treeView_afterSelect() can convert the node back into a MyTreeNode. In this way, treeView_afterSelect() can retrieve the path information stored in the object previously. It can use the path information to create the list view file display.

Handling drag-and-drop

The next three methods in FileMgr—listView_itemDrag(), treeView_dragDrop(), and treeView_dragOver()—support the dragging of files from the list view to a directory in the tree view. (FileMgr does not support the dragging of directories.)

I used the Properties window to attach listView_itemDrag() to the itemDrag event of the ListView control. This event occurs when the user selects one or more files in the list view and then begins moving the mouse with the left mouse button held down, thereby initiating the drag operation. You saw how to implement a drop method in with the RTFEditor app by calling getData() with the data object passed in the DragEvent object to return an array of String objects. The listView_itemDrag() method in effect reverses that process. First, listView_itemDrag() fetches a list of the items currently selected in the list view by calling getSelectedItems(). It then creates a MemoryStream object to which it writes the full path name of each file followed by a semicolon to act as a file name terminator.

Remember, the MemoryStream class in Visual J++ is equivalent to the strstream class in C++; they are both memory buffers that act like files.

Once all the file names have been written to the buffer, a DataObject object of type FileMgr is created to contain the MemoryStream object. The DataObject is then passed to the function doDragDrop(), which initiates the drag operation.

It is the method doDragDrop() that changes the cursor to indicate to the user that a drag-and-drop operation is in process. Control does not return from the doDragDrop() call until the user has released the left mouse button.

Similarly, I used the Properties window to assign the treeView_dragDrop() method to handle the dragDrop event of the tree view. This event occurs when the user lets go of the left mouse button to "drop" the dragged files. The treeView_dragDrop() method is similar to the dragDrop() method in . The treeView_dragDrop() method first gets the DataObject object out of the DragEvent event passed to it. It then gets the FileMgr memory stream from which it begins reading file names under the assumption that the file names are terminated with a semicolon. Each file is then copied to the currently selected directory using the File.copy() method. Once the file has been successfully copied, treeView_dragDrop() displays a message box giving the user the option of deleting the source file. Whether the file is deleted or not, the source directory is redisplayed in the list view to show the user any changes.

The delete operation has been commented out so you don't inadvertently delete important files. If you would like to enable this feature, uncomment the call to File.delete().

The final key function to the drag-and-drop operation is the treeView_dragOver() method. I used the Properties window to assign this method to handle the dragOver event of the TreeView control. The dragOver event occurs repeatedly as the user moves the mouse within the tree view during a drag operation. Contained within the DragEvent passed to treeView_dragOver() are the screen coordinates of the mouse as it moves. For our purposes, we would like the program to use these coordinates to highlight which tree node the mouse is pointing at. That way, the user has positive feedback as to which node she is about to drop the file(s) on.

The mouse coordinates contained in the DragEvent object are relative to the upper-left corner of the screen. Before treeView_dragOver() can use them, it must call treeView.pointToClient() to transform the screen-relative mouse coordinates to coordinates relative to the tree view. (This transformation involves subtracting the coordinates of the upper left-hand corner of the frame within the screen and then subtracting the location of the tree view within the frame.) The treeView_dragOver() method then calls getNodeAt(), passing the transformed mouse coordinates, to find out which tree node the user is pointing at with the mouse. If the user is not pointing at a tree node, getNodeAt() returns a null. If getNodeAt() returns a null, treeView_dragOver() returns without taking any action. However, if getNodeAt() does return a tree node, treeView_dragOver() makes this the selected node by calling setSelectedNode(). This method will highlight the node, change its image from the closed folder to the open folder, and update the ListView control to display the files in the directory corresponding to that node.

Handling the Delete key

To complete the FileMgr capabilities, I added one further feature. If the user presses the Delete key with the mouse pointer in the list view, FileMgr will delete any files selected. I did this both because I felt that the app needed such a function and to demonstrate the handling of key events.

To implement this functionality, use the Properties window to define the listView_keyDown() method to handle the event that occurs when the user presses a key. Since this method is attached to the ListView control, this event will only be passed to the list view if the mouse pointer is somewhere within the list view's display area.

The KeyEvent object passed to listView_keyDown() includes several data fields of interest, including the keyData field, which contains the ANSI equivalent of the key that was pressed. For example, keyData contains an "a" when the A key is pressed. However, the keyDown event also occurs for keys that have no ANSI equivalent, such as the Shift key or the Control key. The Delete key falls into this category.

For these keys, the programmer must rely on the key code. The key code is a unique number assigned to each key on the PC keyboard. (Actually, it's semi-unique, if that's a word, since if your keyboard has two Delete keys, as most do, then both will have the same key code.) Rather than memorize these key codes, programmers should used the enumeration class Key which contains constants representing a complete list of all of the key codes, including those for ANSI character keys like the A key.

The member Key.DELETE refers to the key code for the Delete key. Our implementation of the listView_keyDown() method ignores any key besides the Delete key. Once the Delete key is detected, the function iterates through the selected files in the list view and deletes them using the File.delete() function.

Once again, this function has been commented out to avoid accidental deletion of critical files. If you want to enable this feature, uncomment the call to File.delete().

completed program

Screenshot-9 shows the complete program displaying my \temp directory. You can see the file structure displayed on the left. Most of the folders are displayed with the closed folder icon; however, the currently selected folder, EyeControl, is displayed with the open folder icon. The display on the right shows the contents of that folder. The edit box at the bottom of the apps shows that I have just copied the Form1.java file from \temp\EyeControl to \temp.

Java Click to view at full size.

Screenshot-9. The completed FileMgr program displaying the contents of my \temp directory.

Why use the IEnumerator interface?

Back in addDirsToNode() and addFilesToListView() you might have noticed the use of the IEnumerator interface, whereas in programs in previous chapters (such as ComboBox1 in ) I simply called list.getItems() to return an array of the objects contained within the list and then manipulated that list. The use of IEnumerator in the examples in this chapter has been primarily for educational purposes, but it has certain advantages.

The fact that the List.getItems() call returns an array of objects leads one to believe that a List is implemented as an array. This might be, but is not necessarily the case. Suppose I created a list class that maintained its contents in a linked list or a push down stack or any number of other possible data structures. It might be a slow process to create an array of references to these objects. Thus, while the getItems() function returns a data structure that is convenient for the programmer to use (an array), it might not be convenient for the list class to generate.

The IEnumerator interface is completely generic. It makes few assumptions about the underlying data structure of the list. Whether an array, a linked list, or a push down stack, the programmer of the list class should be able to generate an efficient enumeration class which implements the three methods required by IEnumerator:

Thus, while using IEnumerator might not be quite as convenient for the app programmer, it allows the library programmer the flexibility to implement the underlying list in the most efficient means possible. Comments