General Java
Building a Tree Viewer
Building a Tree Viewer
Apr. 1, 1999 12:00 AM
A Tree for All Occasions
The Java Foundation Class, also known as Swing, in addition to augmenting, enhancing and generally implementing platform-independent replacements of AWT components, added the JTree class to its repertoire of new GUI components. Swing's JTree supports a Windows Explorer-style (outliner-style) tree that makes it easy to graphically render data with hierarchical relationships. A limited number of graphical attributes as supplied by the default look-and-feel provided with Swing - e.g., the icon representing the nodes - is also readily configurable.
This article describes an implementation similar to JTree that can be used in Java 1.0.2 or when using Swing may not be an option. This implementation also adds features not found in the default JTree. These include the option of rendering a tree vertically or horizontally, and of aligning it left, center or right. In addition, subtrees can be interactively moved from one branch to another using drag-and-drop. I also describe how some of these extra features can be migrated to Swing when it becomes more popular in the future. The source code in this article requires the Callbackable, CallbackList, Widget, PositionableGridConstraints and PositionableGridLayout classes introduced in previous issues of JDJ.
Creating a Tree - The TreeNode Class
A tree is essentially a set of hierarchically related nodes. In this implementation, each node is an instance of the TreeNode class. A user object may be stored in a TreeNode, including any AWT or composite AWT Component. While TreeNode doesn't contain any rendering code, the TreeViewer class stores the icon for each node in its representative TreeNode instance. Thus each TreeNode can be represented with a unique icon if necessary.
An instance of the TreeNode class stores references to its parent and children. A null reference for the parent means that this node is the root of the entire tree. A null reference for its children means that the node is a leaf. Listing 1 shows the implementation of a TreeNode class.
The following code fragment shows how to create the tree shown in Figure 1:
TreeNode root = new TreeNode( null, null, null, "Root", null );
new TreeNode( root, null, null,
"Level 1.1", null );
new TreeNode ( root, null, null,
"Level 1.2", null );
new TreeNode ( root, null, null,
"Level 1.3", null );
new TreeNode ( root.getChild(1),
null, null, "Level 2.1", null );
new TreeNode ( root.getChild(1),
null, null, "Level 2.2", null );
new TreeNode ( root.getChild(1),
null, null, "Level 2.3", null );
The first line creates the root of the tree; note the null passed in the first parameter, which indicates no parent. Lines 2 to 4 create the second level; note that root is passed as the first parameter in this case. Lines 5 to 7 create the third level; note that the child of the root with index 1 is used for the first parameter.
Walking the Tree - The TreeWalker Class
While a collection of hierarchically linked TreeNode instances implements the structure of a tree, the TreeWalker implements an abstract class for traversing the tree. In other words, given a tree, an extension of the TreeWalker will traverse it in either a breadth or depth first approach, performing an action on each node as it goes. Listing 2 shows the code of the TreeWalker class. When creating an instance of a TreeNode, an instance of a TreeWalker extension is usually passed as the last parameter of the TreeNode constructor. Listing 3 shows the TreePrinter class, which is an extension of TreeWalker for printing each node of a tree.
A sample printout of the tree in Figure 1 using the TreePrinter will yield the console output seen in Listing 4.
Rendering the Tree - The TreeViewer Class
TreeViewer is an extension of the TreeWalker class used specifically for providing an interactive graphical front-end to a tree. The TreeViewer class takes a tree, traverses it and renders each node on any AWT display surface - e.g., Canvas or Panel. As noted before, the AWT Component stored in each TreeNode instance is used to render the node. For example, if a Button is to be displayed for a specific node in a tree, then an instance of the Button should be added to the TreeViewer, giving its location in the tree with respect to a parent node. The TreeViewer also manages the relocation of subtrees when the user initiates a drag-and-drop action.
TreeViewer contains the TreeViewerPanel class that uses the custom LayoutManager introduced in the article "Implementing a Grid Layout Manager with Positionable Components" (JDJ Vol. 3, Issue 12) to lay out the tree. The depth of a node in a tree hierarchy determines its vertical position in the grid. To determine its width, the TreeViewer recursively walks the tree (using the walkDepth method) to determine the grid position of each node relative to its neighbor on the left. After adjusting for alignment and orientation to determine its exact location on the grid, the node, as well as the lines leading to it from its parent, is drawn.
Actions on the nodes are left to the AWT Components representing the nodes themselves to handle. However, Tree relies on the Widget class introduced in the Widget-izing AWT Components to implement drop-and-drag. Because some AWT Components (notably the Button) recognize only the action event in some implementation of Java 1.0.2, the drag-and-drop capability is also implemented in the TreeViewerPanel. In general, a node or a subtree may be dragged by holding the mouse button just outside of the Button representing the node or the root of the subtree, and then moving the mouse. If the particular AWT implementation supports dragging inside the Component (e.g., Button), dragging may be accomplished by holding the mouse button down inside the Component itself and then moving the mouse. When the user moves a subtree, the callback determines the drop site, removes the subtree from the original location and attaches it to the new parent (MouseDragCallback). The complete TreeViewer implementation is shown in Listing 5.
Using the Tree Class to Display a Tree
Creating a visual representation of a tree using the TreeViewer class parallels that of creating a tree structure using the TreeNode class. Listing 6 shows an application that creates an interactive tree, which is center aligned and vertically oriented. The application requires two command line parameters: the first to specify whether the tree is to be displayed "horizontally" or "vertically"; the second to determine whether it is to be displayed "left," "center" or "right" align. The root will be created by default. Click on a button to create a child for it. To move a node, hold down the mouse button just outside the Button representing the node and drag it to the node that will become the new parent of the subtree. An entire subtree may also be moved this way.Figure 2: This figure shows a vertically oriented, center-aligned tree
using widgetized Buttons as nodes. Also shown is a red rectangle indicating that
the "1" Button is being dragged. Dragging is accomplished by holding down the
mouse
button-represented here by the white cursor-near the Button to be dragged.
The red
rectangle will trace out the drag path while the mouse button is continued to
be held
down. Releasing the mouse button on top of any other Buttons moves the
entire subtree
rooted at "1" to the next location.
TreeViewer and JTree
TreeViewer and TreeNode are analogs of Swing's JTree and TreeNode. Both TreeViewer and JTree are the respective renderers and event handlers in each implementation.
By default, JTree provides only a Windows Explorer-style tree. In reality, JTree delegates most of its look-and-feel decisions to an instance of the BasicUI, so it is possible to customize the renderer by extending the supplied BasicTreeUI class. As Swing gradually becomes the GUI toolkit of choice for Java, the code use in this implementation of a general tree viewer may be migrated.
Conclusion
In this article we introduced a TreeViewer implementation with extra features that are not in Java's Swing-supplied implementation. In general, TreeViewer could be a powerful tool for visualizing hierarchically organized structures. A future article will explore the use of TreeViewer in viewing the organization of an AWT graphical user interface design - a step that will take us one step closer to implementing a GUI layout editor.
Download Source Code
The program in this article requires the callback and widget code from the Implementing Callback and Widget-izing AWT articles published in the April and June 1998 issues of JDJ (Vol. 3, Issues 4 and 5). It also requires the positionable grid layout code from the December issue of JDJ (Vol. 3, Issue 12). Full source code for this article (including a Java 1.1 version) can be downloaded free from www.wigitek.com. A version that supports the collapsing of subtrees for Java 1.0 and 1.1 is also available from Wigitek Corporation at the same Web site.
About Daniel DeeDaniel Dee has more than 10 years of experience working in the development of GUI software toolkits, starting with X Windows and then Java, since their inception. He is currently the president of Wigitek Corporation, a company providing software tools and consulting services for the development Java-based data-driven dynamic graphics software. He received an MS degree in Computer System Engineering from the University of Massachusetts.