- The Tree Control
- Tree Appearance
- The TreeNode Interface
- The MutableTreeNode Interface
- The DefaultMutableTreeNode Class
- The TreePath Class
- What is a Leaf?
- Tree Expansion and Traversal
- Expanding and Collapsing Nodes under Program Control
- Tree Expansion Events
- Making Nodes Visible
- Controlling Node Expansion and Collapse
- Tree Model Events
- Implementation Plan for the File System Control
- File System Tree Control Implementation
- Using the File System Tree Control
- Custom Tree Rendering and Editing
- Customizing the Default Tree Cell Renderer
- ToolTips and Renderers
- Custom Cell Editors
- Controlling Which Nodes Can Be Edited
- Controlling Editability by Subclassing JTree
- Programmatic Control of Editors
- Editing Trees with Custom User Objects
- The valueForPathChanged Method
- The Tree Implementation
- The Cell Editor
- The Cell Renderer
- Summary
Tree Model Events
All of the methods in the previous sections operate by generating TreeMod-elEvents that can be caught by registering a TreeModelListener on the tree's DefaultTreeModel object. Usually, Swing and AWT events are not highly specific about exactly what has changedthe listener is expected to query the source of the event to find out what happened. This is acceptable for simple controls like lists, combo boxes, text fields, and buttons where only one thing can have happened. But in the case of a tree, many modifications of different types can be made. If a single event that said "something has changed" were posted, it would be necessary to scan the whole tree to try and detect what happened. This would, of course, be time-consuming and would require the listener to remember a large amount of state in order to be able to perform a comparison at all.
Because of this, the TreeModelListener must implement four methods, each of which receives a TreeModelEvent:
public void treeNodesChanged(TreeModelEvent) public void treeStructureChanged(TreeModelEvent) public void treeNodesInserted(TreeModelEvent) public void treeNodesRemoved(TreeModelEvent)
These listener methods are entered as a result of following Default-TreeModel methods being called:
treeNodesChanged |
nodeChangedor nodesChangedwas called |
treeStructureChanged |
reload(either variant) or nodeStructureChanged was called |
treeNodesInserted |
nodesWereInserted was called |
treeNodesRemoved |
nodesWereRemovedwas called |
Splitting at this level is not sufficient, however, because it doesn't tell the listener what was added, removed, or changed. This information is contained in the TreeModelEvent, which has five methods that can be used to extract details of the change to the model:
public Object getSource() public TreePath getTreePath() public Object[] getPath() public Object[] getChildren() public int[] getChildIndices()
Which of these methods returns meaningful values and what they mean depends on the type of event. All of the events return a valid value to get-Source, which is a reference to the affected tree model (i.e., the Default-TreeModel instance for the affected JTree). The getTreePath and getPath method both return the node in the tree that is affected by the change, either in terms of its TreePath or as the array of Objects that makes up the TreePath. In the case of an event delivered to treeStructureChanged, only get-Path and getTreePath return any useful information. These methods return the node and below which the change occurred. This TreePath corresponds to the node that was originally passed to reload or nodeStructure-Changed. The getChildren and getChildIndices methods both return null.
For treeNodesChanged, the values returned by getPath and getTree-Path again correspond to the node that is the parent of the node or nodes that were affected. The getChildIndices and getChildren methods return arrays that contain one entry for each affected child of that node. The indices in the array returned by getChildIndices are in ascending numerical order and the array returned by getChildren is sorted so that corresponding entries in these two arrays matchthat is, the index in entry n of the index array corresponds to the child in entry n of the child array.
Similarly, getPath and getTreePath return the parent of the affected nodes in both treeNodesRemoved and treeNodesInserted and the get-ChildIndices and getChildren methods retrieve sorted and matching arrays of the affected children. Note, however, that in the case of treeNodes-Removed, these children have already been removed from the parent node and the indices indicate where they used to reside in the parent's list of children, whereas for treeNodesInserted, these indices describe where the children have been insertedthat is, they describe the final state of the model.
Most applications won't need to handle these events because the JTree look-and-feel class registers itself as a listener of the tree's model and updates the tree's appearance based on the events that it receives. You can, however, register a listener of your own if you want the tree control to cause your application to react in some special way to changes in its structure. You'll see examples of this in the rest of this chapter.
A File System Tree Control
The most obvious thing to use a tree control for is to show the content of a machine's file system, in the same way as the Windows Explorer does in the left of its two display panes. Since the file system and JTree are both inherently hierarchical, you might expect it to be a simple matter to create a tree control that maps a file system but, as you'll see in this section, there are a few interesting issues to tackle along the way. As the description of the implementation of this component progresses, you'll see some of the real-world problems that come up when creating new components, including the need to sometimes work around problems in the classes that the new control is based on.
Before looking at how to implement a file system control, let's look at what the finished product looks like. Figure 101 shows the control mounted in a frame and running on a Windows platform. If you want to try this out for yourself, use a command like the following:
java JFCBook.Chapter10.FileTreeTest C:\
You can replace the string "C:\" with the name of any directory on your systemthis is the directory that becomes the root of the file tree.
When you run this example, you should see the directory whose name was supplied on the command line at the top of the display, followed by all of its subdirectories. Like Windows Explorer, this control only shows directories, not files. Windows Explorer shows files in its right-hand pane, which is almost identical to the Swing JFileChooser. Indeed, once you have this control, you can use it, together with the JFileChooser and the JSplit-Pane component that you saw in the last chapter to create an application that looks very much like Windows Explorer itself except, of course, that it also runs on many versions of UNIX (and other platforms).
If you move the mouse over any directory with an expansion icon to the left of it and click on the icon or double-click on the directory icon or name, it opens to reveal its subdirectories. If those subdirectories have other subdirectories, they will also have expansion icons next to them. A directory that doesn't have any subdirectories (i.e., a directory that is empty or contains only files) cannot be expanded. Again, to see the contents, you would need to connect this control to a JFileChooser and have it display the directory's contents.
Core Note
The Swing JTree component doesn't work ideally with respect to displaying the icon for a node that has no children. If you're using a look-and-feel with this problem, you'll find that all of the subdirectories seem to have contents but, when you click on one that actually only has files in it, the icon sign disappears. All of the standard look-and-feels currently behave this way.