Trees


Every computer user who uses a hierarchical file system has encountered tree displays such as the one in Figure 6-5. Of course, directories and files form only one of the many examples of treelike organizations. Programmers are familiar with inheritance trees for classes. Many tree structures arise in everyday life, such as the hierarchy of countries, states, and cities shown in Figure 6-6.

Figure 6-5. A directory tree


Figure 6-6. A hierarchy of countries, states, and cities


As programmers, we often have to display these tree structures. Fortunately, the Swing library has a JTRee class for this purpose. The Jtree class (together with its helper classes) takes care of laying out the tree and processing user requests for expanding and collapsing nodes. In this section, you will learn how to put the Jtree class to use.

As with the other complex Swing components, we must focus on the common and useful cases and cannot cover every nuance. If you want to achieve an unusual effect, we recommend that you consult Graphic Java 2 by David M. Geary [Prentice-Hall 1999], Core Java Foundation Classes by Kim Topley [Prentice-Hall 1998], or Core Swing: Advanced Programming by Kim Topley [Prentice-Hall 1999].

Before going any further, let's settle on some terminology (see Figure 6-7). A tree is composed of nodes. Every node is either a leaf or it has child nodes. Every node, with the exception of the root node, has exactly one parent. A tree has exactly one root node. Sometimes you have a collection of trees, each of which has its own root node. Such a collection is called a forest.

Figure 6-7. Tree terminology


Simple Trees

In our first example program, we simply display a tree with a few nodes (see Figure 6-9 on page 340). As with most other Swing components, the Jtree component follows the model-view-controller pattern. You provide a model of the hierarchical data, and the component displays it for you. To construct a Jtree, you supply the tree model in the constructor:

 TreeModel model = . . .; JTree tree = new JTree(model); 

Figure 6-9. A simple tree


NOTE

There are also constructors that construct trees out of a collection of elements:

 JTree(Object[] nodes) JTree(Vector<?> nodes) JTree(Hashtable<?, ?> nodes) // the values become the nodes 

These constructors are not very useful. They merely build a forest of trees, each with a single node. The third constructor seems particularly useless since the nodes appear in the essentially random order given by the hash codes of the keys.


How do you obtain a tree model? You can construct your own model by creating a class that implements the treeModel interface. You see later in this chapter how to do that. For now, we stick with the DefaultTreeModel that the Swing library supplies.

To construct a default tree model, you must supply a root node.

 TreeNode root = . . .; DefaultTreeModel model = new DefaultTreeModel(root); 

treeNode is another interface. You populate the default tree model with objects of any class that implements the interface. For now, we use the concrete node class that Swing supplies, namely, DefaultMutableTreeNode. This class implements the MutableTreeNode interface, a subinterface of TReeNode (see Figure 6-8).

Figure 6-8. Tree classes


A default mutable tree node holds an object, the user object. The tree renders the user objects for all nodes. Unless you specify a renderer, the tree simply displays the string that is the result of the toString method.

In our first example, we use strings as user objects. In practice, you would usually populate a tree with more expressive user objects. For example, when displaying a directory tree, it makes sense to use File objects for the nodes.

You can specify the user object in the constructor, or you can set it later with the setUserObject method.

 DefaultMutableTreeNode node = new DefaultMutableTreeNode("Texas"); node.setUserObject("California"); 

Next, you establish the parent/child relationships between the nodes. Start with the root node, and use the add method to add the children:

 DefaultMutableTreeNode root = new DefaultMutableTreeNode("World"); DefaultMutableTreeNode country = new DefaultMutableTreeNode("USA"); root.add(country); DefaultMutableTreeNode state = new DefaultMutableTreeNode("California"); country.add(state); 

Figure 6-9 illustrates how the tree will look.

Link up all nodes in this fashion. Then, construct a DefaultTreeModel with the root node. Finally, construct a Jtree with the tree model.

 DefaultTreeModel treeModel = new DefaultTreeModel(root); JTree tree = new JTree(treeModel); 

Or, as a shortcut, you can simply pass the root node to the Jtree constructor. Then the tree automatically constructs a default tree model:

 JTree tree = new JTree(root); 

Example 6-4 contains the complete code.

Example 6-4. SimpleTree.java
  1. import java.awt.*;  2. import java.awt.event.*;  3. import javax.swing.*;  4. import javax.swing.tree.*;  5.  6. /**  7.    This program shows a simple tree.  8. */  9. public class SimpleTree 10. { 11.    public static void main(String[] args) 12.    { 13.       JFrame frame = new SimpleTreeFrame(); 14.       frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 15.       frame.setVisible(true); 16.    } 17. } 18. 19. /** 20.    This frame contains a simple tree that displays a 21.    manually constructed tree model. 22. */ 23. class SimpleTreeFrame extends JFrame 24. { 25.    public SimpleTreeFrame() 26.    { 27.       setTitle("SimpleTree"); 28.       setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT); 29. 30.       // set up tree model data 31. 32.       DefaultMutableTreeNode root 33.          = new DefaultMutableTreeNode("World"); 34.       DefaultMutableTreeNode country 35.          = new DefaultMutableTreeNode("USA"); 36.       root.add(country); 37.       DefaultMutableTreeNode state 38.          = new DefaultMutableTreeNode("California"); 39.       country.add(state); 40.       DefaultMutableTreeNode city 41.          = new DefaultMutableTreeNode("San Jose"); 42.       state.add(city); 43.       city = new DefaultMutableTreeNode("Cupertino"); 44.       state.add(city); 45.       state = new DefaultMutableTreeNode("Michigan"); 46.       country.add(state); 47.       city = new DefaultMutableTreeNode("Ann Arbor"); 48.       state.add(city); 49.       country = new DefaultMutableTreeNode("Germany"); 50.       root.add(country); 51.       state = new DefaultMutableTreeNode("Schleswig-Holstein"); 52.       country.add(state); 53.       city = new DefaultMutableTreeNode("Kiel"); 54.       state.add(city); 55. 56.       // construct tree and put it in a scroll pane 57. 58.       JTree tree = new JTree(root); 59.       Container contentPane = getContentPane(); 60.       contentPane.add(new JScrollPane(tree)); 61.    } 62. 63.    private static final int DEFAULT_WIDTH = 300; 64.    private static final int DEFAULT_HEIGHT = 200; 65. } 

When you run the program, the tree first looks as in Figure 6-10. Only the root node and its children are visible. Click on the circle icons (the handles) to open up the subtrees. The line sticking out from the handle icon points to the right when the subtree is collapsed, and it points down when the subtree is expanded (see Figure 6-11). We don't know what the designers of the Metal look and feel had in mind, but we think of the icon as a door handle. You push down on the handle to open the subtree.

Figure 6-10. The initial tree display


Figure 6-11. Collapsed and expanded subtrees


NOTE

Of course, the display of the tree depends on the selected look and feel. We just described the Metal look and feel. In the Windows and Motif look and feel, the handles have the more familiar looka "-" or "+" in a box (see Figure 6-12).

Figure 6-12. A tree with the Windows look and feel



Up to JDK 1.3, the Metal look and feel does not display the tree outline by default (see Figure 6-13). As of JDK 1.4, the default line style is "angled."

Figure 6-13. A tree with no connecting lines


In JDK 1.4, use the following magic incantation to turn off the lines joining parents and children:

 tree.putClientProperty("JTree.lineStyle", "None"); 

Conversely, to make sure that the lines are shown, use

 tree.putClientProperty("JTree.lineStyle", "Angled"); 

Another line style, "Horizontal", is shown in Figure 6-14. The tree is displayed with horizontal lines separating only the children of the root. We aren't quite sure what it is good for.

Figure 6-14. A tree with the horizontal line style


By default, there is no handle for collapsing the root of the tree. If you like, you can add one with the call

 tree.setShowsRootHandles(true); 

Figure 6-15 shows the result. Now you can collapse the entire tree into the root node.

Figure 6-15. A tree with a root handle


Conversely, you can hide the root altogether. You do that to display a forest, a set of trees, each of which has its own root. You still must join all trees in the forest to a common root. Then, you hide the root with the instruction

 tree.setRootVisible(false); 

Look at Figure 6-16. There appear to be two roots, labeled "USA" and "Germany." The actual root that joins the two is made invisible.

Figure 6-16. A forest


Let's turn from the root to the leaves of the tree. Note that the leaves have a different icon from the other nodes (see Figure 6-17).

Figure 6-17. Leaf and folder icons


When the tree is displayed, each node is drawn with an icon. There are actually three kinds of icons: a leaf icon, an opened non-leaf icon, and a closed non-leaf icon. For simplicity, we refer to the last two as folder icons.

The node renderer needs to know which icon to use for each node. By default, the decision process works like this: If the isLeaf method of a node returns true, then the leaf icon is used. Otherwise, a folder icon is used.

The isLeaf method of the DefaultMutableTreeNode class returns TRue if the node has no children. Thus, nodes with children get folder icons, and nodes without children get leaf icons.

Sometimes, that behavior is not appropriate. Suppose we added a node "Montana" to our sample tree, but we're at a loss as to what cities to add. We would not want the state node to get a leaf icon because conceptually only the cities are leaves.

The Jtree class has no idea which nodes should be leaves. It asks the tree model. If a childless node isn't automatically a conceptual leaf, you can ask the tree model to use a different criterion for leafiness, namely, to query the "allows children" node property.

For those nodes that should not have children, call

 node.setAllowsChildren(false); 

Then, tell the tree model to ask the value of the "allows children" property to determine whether a node should be displayed with a leaf icon. You use the setAsksAllowsChildren method of the DefaultTreeModel class to set this behavior:

 model.setAsksAllowsChildren(true); 

With this decision criterion, nodes that allow children get folder icons, and nodes that don't allow children get leaf icons.

Alternatively, if you construct the tree by supplying the root node, supply the setting for the "asks allows children" property in the constructor.

 JTree tree = new JTree(root, true); // nodes that don't allow children get leaf icons 


 javax.swing.JTree 1.2 

  • Jtree(TreeModel model)

    constructs a tree from a tree model.

  • Jtree(TreeNode root)

  • Jtree(TreeNode root, boolean asksAllowChildren)

    construct a tree with a default tree model that displays the root and its children.

    Parameters:

    root

    The root node

     

    asksAllowsChildren

    true to use the "allows children" node property for determining whether a node is a leaf


  • void setShowsRootHandles(boolean b)

    If b is true, then the root node has a handle for collapsing or expanding its children.

  • void setRootVisible(boolean b)

    If b is true, then the root node is displayed. Otherwise, it is hidden.


 javax.swing.tree.TreeNode 1.2 

  • boolean isLeaf()

    returns true if this node is conceptually a leaf.

  • boolean getAllowsChildren()

    returns true if this node can have child nodes.


 javax.swing.tree.MutableTreeNode 1.2 

  • void setUserObject(Object userObject)

    sets the "user object" that the tree node uses for rendering.


 javax.swing.tree.TreeModel 1.2 

  • boolean isLeaf(Object node)

    returns TRue if node should be displayed as a leaf node.


 javax.swing.tree.DefaultTreeModel 1.2 

  • void setAsksAllowsChildren(boolean b)

    If b is true, then nodes are displayed as leaves when their getAllowsChildren method returns false. Otherwise, they are displayed as leaves when their isLeaf method returns true.


 javax.swing.tree.DefaultMutableTreeNode 1.2 

  • DefaultMutableTreeNode(Object userObject)

    constructs a mutable tree node with the given user object.

  • void add(MutableTreeNode child)

    adds a node as the last child of this node.

  • void setAllowsChildren(boolean b)

    If b is true, then children can be added to this node.


 javax.swing.JComponent 1.2 

  • void putClientProperty(Object key, Object value)

    adds a key/value pair to a small table that each component manages. This is an "escape hatch" mechanism that some Swing components use for storing look-and-feelspecific properties.

Editing Trees and Tree Paths

In the next example program, you see how to edit a tree. Figure 6-18 shows the user interface. If you click the Add Sibling or Add Child button, the program adds a new node (with title New) to the tree. If you click the Delete button, the program deletes the currently selected node.

Figure 6-18. Editing a tree


To implement this behavior, you need to find out which tree node is currently selected. The JTRee class has a surprising way of identifying nodes in a tree. It does not deal with tree nodes, but with paths of objects, called tree paths. A tree path starts at the root and consists of a sequence of child nodessee Figure 6-19.

Figure 6-19. A tree path


You may wonder why the Jtree class needs the whole path. Couldn't it just get a treeNode and keep calling the getParent method? In fact, the Jtree class knows nothing about the TReeNode interface. That interface is never used by the treeModel interface; it is only used by the DefaultTreeModel implementation. You can have other tree models in which the nodes do not implement the treeNode interface at all. If you use a tree model that manages other types of objects, then those objects may not have getParent and getChild methods. They would of course need to have some other connection to each other. It is the job of the tree model to link nodes together. The JTRee class itself has no clue about the nature of their linkage. For that reason, the Jtree class always needs to work with complete paths.

The treePath class manages a sequence of Object (not treeNode!) references. A number of Jtree methods return treePath objects. When you have a tree path, you usually just need to know the terminal node, which you get with the getLastPathComponent method. For example, to find out the currently selected node in a tree, you use the getSelectionPath method of the Jtree class. You get a treePath object back, from which you can retrieve the actual node.

 TreePath selectionPath = tree.getSelectionPath(); DefaultMutableTreeNode selectedNode    = (DefaultMutableTreeNode) selectionPath.getLastPathComponent(); 

Actually, because this particular query is so common, there is a convenience method that gives the selected node immediately.

 DefaultMutableTreeNode selectedNode    = (DefaultMutableTreeNode) tree.getLastSelectedPathComponent(); 

This method is not called getSelectedNode because the tree does not know that it contains nodesits tree model deals only with paths of objects.

NOTE

Tree paths are one of two ways in which the JTRee class describes nodes. Quite a few Jtree methods take or return an integer index, the row position. A row position is simply the row number (starting with 0) of the node in the tree display. Only visible nodes have row numbers, and the row number of a node changes if other nodes before it are expanded, collapsed, or modified. For that reason, you should avoid row positions. All Jtree methods that use rows have equivalents that use tree paths instead.


Once you have the selected node, you can edit it. However, do not simply add children to a tree node:

 selectedNode.add(newNode); // NO! 

If you change the structure of the nodes, you change the model but the associated view is not notified. You could send out a notification yourself, but if you use the insertNodeInto method of the DefaultTreeModel class, the model class takes care of that. For example, the following call appends a new node as the last child of the selected node and notifies the tree view.

 model.insertNodeInto(newNode, selectedNode, selectedNode.getChildCount()); 

The analogous call removeNodeFromParent removes a node and notifies the view:

 model.removeNodeFromParent(selectedNode); 

If you keep the node structure in place but you changed the user object, you should call the following method:

 model.nodeChanged(changedNode); 

The automatic notification is a major advantage of using the DefaultTreeModel. If you supply your own tree model, you have to implement automatic notification by hand. (See Core Java Foundation Classes by Kim Topley for details.)

CAUTION

The DefaultTreeModel class has a reload method that reloads the entire model. However, don't call reload simply to update the tree after making a few changes. When the tree is regenerated, all nodes beyond the root's children are collapsed again. It is quite disconcerting to your users if they have to keep expanding the tree after every change.


When the view is notified of a change in the node structure, it updates the display but it does not automatically expand a node to show newly added children. In particular, if a user in our sample program adds a new child node to a node whose children are currently collapsed, then the new node is silently added to the collapsed subtree. This gives the user no feedback that the command was actually carried out. In such a case, you should make a special effort to expand all parent nodes so that the newly added node becomes visible. You use the makeVisible method of the JTRee class for this purpose. The makeVisible method expects a tree path leading to the node that should become visible.

Thus, you need to construct a tree path from the root to the newly inserted node. To get a tree path, you first call the getPathToRoot method of the DefaultTreeModel class. It returns a TReeNode[] array of all nodes from a node to the root node. You pass that array to a treePath constructor.

For example, here is how you make the new node visible:

 TreeNode[] nodes = model.getPathToRoot(newNode); TreePath path = new TreePath(nodes); tree.makeVisible(path); 

NOTE

It is curious that the DefaultTreeModel class feigns almost complete ignorance about the treePath class, even though its job is to communicate with a Jtree. The Jtree class uses tree paths a lot, and it never uses arrays of node objects.


But now suppose your tree is contained inside a scroll pane. After the tree node expansion, the new node may still not be visible because it falls outside the viewport. To overcome that problem, call

 tree.scrollPathToVisible(path); 

instead of calling makeVisible. This call expands all nodes along the path, and it tells the ambient scroll pane to scroll the node at the end of the path into view (see Figure 6-20).

Figure 6-20. The scroll pane scrolls to display a new node


By default, tree nodes cannot be edited. However, if you call

 tree.setEditable(true); 

then the user can edit a node simply by double-clicking, editing the string, and pressing the ENTER key. Double-clicking invokes the default cell editor, which is implemented by the DefaultCellEditor class (see Figure 6-21). It is possible to install other cell editors, but we defer our discussion of cell editors until the section on tables, where cell editors are more commonly used.

Figure 6-21. The default cell editor


Example 6-5 shows the complete source code of the tree editing program. Run the program, add a few nodes, and edit them by double-clicking them. Observe how collapsed nodes expand to show added children and how the scroll pane keeps added nodes in the viewport.

Example 6-5. TreeEditTest.java
   1. import java.awt.*;   2. import java.awt.event.*;   3. import javax.swing.*;   4. import javax.swing.tree.*;   5.   6. /**   7.    This program demonstrates tree editing.   8. */   9. public class TreeEditTest  10. {  11.    public static void main(String[] args)  12.    {  13.       JFrame frame = new TreeEditFrame();  14.       frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);  15.       frame.setVisible(true);  16.    }  17. }  18.  19. /**  20.    A frame with a tree and buttons to edit the tree.  21. */  22. class TreeEditFrame extends JFrame  23. {  24.    public TreeEditFrame()  25.    {  26.       setTitle("TreeEditTest");  27.       setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);  28.  29.       // construct tree  30.  31.       TreeNode root = makeSampleTree();  32.       model = new DefaultTreeModel(root);  33.       tree = new JTree(model);  34.       tree.setEditable(true);  35.  36.       // add scroll pane with tree  37.  38.       JScrollPane scrollPane = new JScrollPane(tree);  39.       add(scrollPane, BorderLayout.CENTER);  40.  41.       makeButtons();  42.    }  43.  44.    public TreeNode makeSampleTree()  45.    {  46.       DefaultMutableTreeNode root = new DefaultMutableTreeNode("World");  47.       DefaultMutableTreeNode country = new DefaultMutableTreeNode("USA");  48.       root.add(country);  49.       DefaultMutableTreeNode state = new DefaultMutableTreeNode("California");  50.       country.add(state);  51.       DefaultMutableTreeNode city = new DefaultMutableTreeNode("San Jose");  52.       state.add(city);  53.       city = new DefaultMutableTreeNode("San Diego");  54.       state.add(city);  55.       state = new DefaultMutableTreeNode("Michigan");  56.       country.add(state);  57.       city = new DefaultMutableTreeNode("Ann Arbor");  58.       state.add(city);  59.       country = new DefaultMutableTreeNode("Germany");  60.       root.add(country);  61.       state = new DefaultMutableTreeNode("Schleswig-Holstein");  62.       country.add(state);  63.       city = new DefaultMutableTreeNode("Kiel");  64.       state.add(city);  65.       return root;  66.    }  67.  68.    /**  69.       Makes the buttons to add a sibling, add a child, and  70.       delete a node.  71.    */  72.    public void makeButtons()  73.    {  74.       JPanel panel = new JPanel();  75.       JButton addSiblingButton = new JButton("Add Sibling");  76.       addSiblingButton.addActionListener(new  77.          ActionListener()  78.          {  79.             public void actionPerformed(ActionEvent event)  80.             {  81.                DefaultMutableTreeNode selectedNode  82.                   = (DefaultMutableTreeNode) tree.getLastSelectedPathComponent();  83.  84.                if (selectedNode == null) return;  85.  86.                DefaultMutableTreeNode parent  87.                   = (DefaultMutableTreeNode) selectedNode.getParent();  88.  89.                if (parent == null) return;  90.  91.                DefaultMutableTreeNode newNode = new DefaultMutableTreeNode("New");  92.  93.                int selectedIndex = parent.getIndex(selectedNode);  94.                model.insertNodeInto(newNode, parent, selectedIndex + 1);  95.  96.                // now display new node  97.  98.                TreeNode[] nodes = model.getPathToRoot(newNode);  99.                TreePath path = new TreePath(nodes); 100.                tree.scrollPathToVisible(path); 101.             } 102.          }); 103.       panel.add(addSiblingButton); 104. 105.       JButton addChildButton = new JButton("Add Child"); 106.       addChildButton.addActionListener(new 107.          ActionListener() 108.          { 109.             public void actionPerformed(ActionEvent event) 110.             { 111.                DefaultMutableTreeNode selectedNode = (DefaultMutableTreeNode) 112.                   tree.getLastSelectedPathComponent(); 113. 114.                if (selectedNode == null) return; 15. 116.                DefaultMutableTreeNode newNode = new DefaultMutableTreeNode("New"); 117.                model.insertNodeInto(newNode, selectedNode, selectedNode.getChildCount()); 118. 119.                // now display new node 120. 121.                TreeNode[] nodes = model.getPathToRoot(newNode); 122.                TreePath path = new TreePath(nodes); 123.                tree.scrollPathToVisible(path); 124.             } 125.          }); 126.       panel.add(addChildButton); 127. 128.       JButton deleteButton = new JButton("Delete"); 129.       deleteButton.addActionListener(new 130.          ActionListener() 131.          { 132.             public void actionPerformed(ActionEvent event) 133.             { 134.                DefaultMutableTreeNode selectedNode 135.                   = (DefaultMutableTreeNode) tree.getLastSelectedPathComponent(); 136. 137.                if (selectedNode != null && selectedNode.getParent() != null) 138.                   model.removeNodeFromParent(selectedNode); 139.             } 140.          }); 141.       panel.add(deleteButton); 142.       add(panel, BorderLayout.SOUTH); 143.    } 144. 145.    private DefaultTreeModel model; 146.    private JTree tree; 147.    private static final int DEFAULT_WIDTH = 400; 148.    private static final int DEFAULT_HEIGHT = 200; 149. } 


 javax.swing.JTree 1.2 

  • treePath getSelectionPath()

    gets the path to the currently selected node, or the path to the first selected node if multiple nodes are selected. Returns null if no node is selected.

  • Object getLastSelectedPathComponent()

    gets the node object that represents the currently selected node, or the first node if multiple nodes are selected. Returns null if no node is selected.

  • void makeVisible(TreePath path)

    expands all nodes along the path.

  • void scrollPathToVisible(TreePath path)

    expands all nodes along the path and, if the tree is contained in a scroll pane, scrolls to ensure that the last node on the path is visible.


 javax.swing.tree.TreePath 1.2 

  • Object getLastPathComponent()

    gets the last object on this path, that is, the node object that the path represents.


 javax.swing.tree.TreeNode 1.2 

  • treeNode getParent()

    returns the parent node of this node.

  • treeNode getChildAt(int index)

    looks up the child node at the given index. The index must be between 0 and getChildCount() - 1.

  • int getChildCount()

    returns the number of children of this node.

  • Enumeration children()

    returns an enumeration object that iterates through all children of this node.


 javax.swing.tree.DefaultTreeModel 1.2 

  • void insertNodeInto(MutableTreeNode newChild, MutableTreeNode parent, int index)

    inserts newChild as a new child node of parent at the given index and notifies the tree model listeners.

  • void removeNodeFromParent(MutableTreeNode node)

    removes node from this model and notifies the tree model listeners.

  • void nodeChanged(TreeNode node)

    notifies the tree model listeners that node has changed.

  • void nodesChanged(TreeNode parent, int[] changedChildIndexes)

    notifies the tree model listeners that all child nodes of parent with the given indexes have changed.

  • void reload()

    reloads all nodes into the model. This is a drastic operation that you should use only if the nodes have changed completely because of some outside influence.

Node Enumeration

Sometimes you need to find a node in a tree by starting at the root and visiting all children until you have found a match. The DefaultMutableTreeNode class has several convenience methods for iterating through nodes.

The breadthFirstEnumeration and depthFirstEnumeration methods return enumeration objects whose nextElement method visits all children of the current node, using either a breadth-first or depth-first traversal. Figure 6-22 shows the traversals for a sample treethe node labels indicate the order in which the nodes are traversed.

Figure 6-22. Tree traversal orders


Breadth-first enumeration is the easiest to visualize. The tree is traversed in layers. The root is visited first, followed by all of its children, then followed by the grandchildren, and so on.

To visualize depth-first enumeration, imagine a rat trapped in a tree-shaped maze. It rushes along the first path until it comes to a leaf. Then, it backtracks and turns around to the next path, and so on.

Computer scientists also call this postorder traversal because the search process visits the children before visiting the parents. The postOrderTraversal method is a synonym for depthFirstTraversal. For completeness, there is also a preOrderTraversal, a depth-first search that enumerates parents before the children.

Here is the typical usage pattern:


Enumeration breadthFirst = node.breadthFirstEnumeration();
while (breadthFirst.hasMoreElements())
   do something with breadthFirst.nextElement();

Finally, a related method, pathFromAncestorEnumeration, finds a path from an ancestor to a given node and then enumerates the nodes along that path. That's no big dealit just keeps calling getParent until the ancestor is found and then presents the path in reverse order.

In our next example program, we put node enumeration to work. The program displays inheritance trees of classes. Type the name of a class into the text field on the bottom of the frame. The class and all of its superclasses are added to the tree (see Figure 6-23).

Figure 6-23. An inheritance tree


In this example, we take advantage of the fact that the user objects of the tree nodes can be objects of any type. Because our nodes describe classes, we store Class objects in the nodes.

Of course, we don't want to add the same class object twice, so we need to check whether a class already exists in the tree. The following method finds the node with a given user object if it exists in the tree.

 public DefaultMutableTreeNode findUserObject(Object obj) {    Enumeration e = root.breadthFirstEnumeration();    while (e.hasMoreElements())    {       DefaultMutableTreeNode node = (DefaultMutableTreeNode) e.nextElement();       if (node.getUserObject().equals(obj))          return node;    }    return null; } 

Rendering Nodes

In your applications, you will often need to change the way in which a tree component draws the nodes. The most common change is, of course, to choose different icons for nodes and leaves. Other changes might involve changing the font of the node labels or drawing images at the nodes. All these changes are made possible by installing a new tree cell renderer into the tree. By default, the Jtree class uses DefaultTreeCellRenderer objects to draw each node. The DefaultTreeCellRenderer class extends the JLabel class. The label contains the node icon and the node label.

NOTE

The cell renderer does not draw the "handles" for expanding and collapsing subtrees. The handles are part of the look and feel, and it is recommended that you not change them.


You can customize the display in three ways.

  1. You can change the icons, font, and background color used by a DefaultTreeCellRenderer. These settings are used for all nodes in the tree.

  2. You can install a renderer that extends the DefaultTreeCellRenderer class and vary the icons, fonts, and background color for each node.

  3. You can install a renderer that implements the treeCellRenderer interface, to draw a custom image for each node.

Let us look at these possibilities one by one. The easiest customization is to construct a DefaultTreeCellRenderer object, change the icons, and install it into the tree:

 DefaultTreeCellRenderer renderer = new DefaultTreeCellRenderer(); renderer.setLeafIcon(new ImageIcon("blue-ball.gif")); // used for leaf nodes renderer.setClosedIcon(new ImageIcon("red-ball.gif")); // used for collapsed nodes renderer.setOpenIcon(new ImageIcon("yellow-ball.gif")); // used for expanded nodes tree.setCellRenderer(renderer); 

You can see the effect in Figure 6-23. We just use the "ball" icons as placeholderspresumably your user interface designer would supply you with appropriate icons to use for your applications.

We don't recommend that you change the font or background color for an entire treethat is really the job of the look and feel.

However, it can be useful to change the font for individual nodes in a tree to highlight some of them. If you look carefully at Figure 6-23, you will notice that the abstract classes are set in italics.

To change the appearance of individual nodes, you install a tree cell renderer. Tree cell renderers are very similar to the list cell renderers we discussed earlier in this chapter. The treeCellRenderer interface has a single method

 Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected,    boolean expanded, boolean leaf, int row, boolean hasFocus) 

The gettreeCellRendererComponent method of the DefaultTreeCellRenderer class returns thisin other words, a label. (The DefaultTreeCellRenderer class extends the JLabel class.) To customize the component, extend the DefaultTreeCellRenderer class. Override the getTReeCellRendererComponent method as follows: Call the superclass method, so that it can prepare the label data. Customize the label properties, and finally return this.


class MyTreeCellRenderer extends DefaultTreeCellRenderer
{
   public Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected,
      boolean expanded, boolean leaf, int row, boolean hasFocus)
   {
      super.getTreeCellRendererComponent(tree, value, selected, expanded, leaf, row, hasFocus);
      DefaultMutableTreeNode node = (DefaultMutableTreeNode) value;
      look at node.getUserObject();
      Font font = appropriate font;
      setFont(font);
      return this;
   }
};

CAUTION

The value parameter of the gettreeCellRendererComponent method is the node object, not the user object! Recall that the user object is a feature of the DefaultMutableTreeNode, and that a Jtree can contain nodes of an arbitrary type. If your tree uses DefaultMutableTreeNode nodes, then you must retrieve the user object in a second step, as we did in the preceding code sample.


CAUTION

The DefaultTreeCellRenderer uses the same label object for all nodes, only changing the label text for each node. If you change the font for a particular node, you must set it back to its default value when the method is called again. Otherwise, all subsequent nodes will be drawn in the changed font! Look at the code in Example 6-6 to see how to restore the font to the default.


We do not show an example for a tree cell renderer that draws arbitrary graphics. If you need this capability, you can adapt the list cell renderer in Example 6-3; the technique is entirely analogous.

Let's put tree cell renderers to work. Example 6-6 shows the complete source code for the class tree program. The program displays inheritance hierarchies, and it customizes the display to show abstract classes in italics. You can type the name of any class into the text field at the bottom of the frame. Press the ENTER key or click the Add button to add the class and its superclasses to the tree. You must enter the full package name, such as java.util.ArrayList.

This program is a bit tricky because it uses reflection to construct the class tree. This work is contained inside the addClass method. (The details are not that important. We use the class tree in this example because inheritance trees yield a nice supply of trees without laborious coding. If you display trees in your own applications, you will have your own source of hierarchical data.) The method uses the breadth-first search algorithm to find whether the current class is already in the tree by calling the findUserObject method that we implemented in the preceding section. If the class is not already in the tree, we add the superclasses to the tree, then make the new class node a child and make that node visible.

The ClassNameTreeCellRenderer sets the class name in either the normal or italic font, depending on the ABSTRACT modifier of the Class object. We don't want to set a particular font because we don't want to change whatever font the look and feel normally uses for labels. For that reason, we use the font from the label and derive an italic font from it. Recall that only a single shared JLabel object is returned by all calls. We need to hang on to the original font and restore it in the next call to the gettreeCellRendererComponent method.

Finally, note how we change the node icons in the ClassTreeFrame constructor.

Example 6-6. ClassTree.java

[View full width]

   1. import java.awt.*;   2. import java.awt.event.*;   3. import java.lang.reflect.*;   4. import java.util.*;   5. import javax.swing.*;   6. import javax.swing.event.*;   7. import javax.swing.tree.*;   8.   9. /**  10.    This program demonstrates cell rendering by showing  11.    a tree of classes and their superclasses.  12. */  13. public class ClassTree  14. {  15.    public static void main(String[] args)  16.    {  17.       JFrame frame = new ClassTreeFrame();  18.       frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);  19.       frame.setVisible(true);  20.    }  21. }  22.  23. /**  24.    This frame displays the class tree, a text field and  25.    add button to add more classes into the tree.  26. */  27. class ClassTreeFrame extends JFrame  28. {  29.    public ClassTreeFrame()  30.    {  31.       setTitle("ClassTree");  32.       setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);  33.  34.       // the root of the class tree is Object  35.       root = new DefaultMutableTreeNode(java.lang.Object.class);  36.       model = new DefaultTreeModel(root);  37.       tree = new JTree(model);  38.  39.       // add this class to populate the tree with some data  40.       addClass(getClass());  41.  42.       // set up node icons  43.       ClassNameTreeCellRenderer renderer = new ClassNameTreeCellRenderer();  44.       renderer.setClosedIcon(new ImageIcon("red-ball.gif"));  45.       renderer.setOpenIcon(new ImageIcon("yellow-ball.gif"));  46.       renderer.setLeafIcon(new ImageIcon("blue-ball.gif"));  47.       tree.setCellRenderer(renderer);  48.  49.       add(new JScrollPane(tree), BorderLayout.CENTER);  50.  51.       addTextField();  52.    }  53.  54.    /**  55.       Add the text field and "Add" button to add a new class.  56.    */  57.    public void addTextField()  58.    {  59.       JPanel panel = new JPanel();  60.  61.       ActionListener addListener = new  62.          ActionListener()  63.          {  64.             public void actionPerformed(ActionEvent event)  65.             {  66.                // add the class whose name is in the text field  67.                try  68.                {  69.                   String text = textField.getText();  70.                   addClass(Class.forName(text)); // clear text field to indicate success  71.                   textField.setText("");  72.                }  73.                catch (ClassNotFoundException e)  74.                {  75.                   JOptionPane.showMessageDialog(null, "Class not found");  76.                }  77.             }  78.          };  79.  80.       // new class names are typed into this text field  81.       textField = new JTextField(20);  82.       textField.addActionListener(addListener);  83.       panel.add(textField);  84.  85.       JButton addButton = new JButton("Add");  86.       addButton.addActionListener(addListener);  87.       panel.add(addButton);  88.  89.       add(panel, BorderLayout.SOUTH);  90.    }  91.  92.    /**  93.       Finds an object in the tree.  94.       @param obj the object to find  95.       @return the node containing the object or null  96.       if the object is not present in the tree  97.    */  98.    public DefaultMutableTreeNode findUserObject(Object obj)  99.    { 100.       // find the node containing a user object 101.       Enumeration e = root.breadthFirstEnumeration(); 102.       while (e.hasMoreElements()) 103.       { 104.          DefaultMutableTreeNode node = (DefaultMutableTreeNode) e.nextElement(); 105.          if (node.getUserObject().equals(obj)) 106.             return node; 107.       } 108.       return null; 109.    } 110. 111.    /** 112.       Adds a new class and any parent classes that aren't 113.       yet part of the tree 114.       @param c the class to add 115.       @return the newly added node. 116.    */ 117.    public DefaultMutableTreeNode addClass(Class c) 118.    { 119.       // add a new class to the tree 120. 121.       // skip non-class types 122.       if (c.isInterface() || c.isPrimitive()) return null; 123. 124.       // if the class is already in the tree, return its node 125.       DefaultMutableTreeNode node = findUserObject(c); 126.       if (node != null) return node; 127. 128.       // class isn't present--first add class parent recursively 129. 130.       Class s = c.getSuperclass(); 131. 132.       DefaultMutableTreeNode parent; 133.       if (s == null) 134.          parent = root; 135.       else 136.          parent = addClass(s); 137. 138.       // add the class as a child to the parent 139.       DefaultMutableTreeNode newNode = new DefaultMutableTreeNode(c); 140.       model.insertNodeInto(newNode, parent, parent.getChildCount()); 141. 142.       // make node visible 143.       TreePath path = new TreePath(model.getPathToRoot(newNode)); 144.       tree.makeVisible(path); 145. 146.       return newNode; 147.    } 148. 149.    private DefaultMutableTreeNode root; 150.    private DefaultTreeModel model; 151.    private JTree tree; 152.    private JTextField textField; 153.    private static final int DEFAULT_WIDTH = 400; 154.    private static final int DEFAULT_HEIGHT = 300; 155. } 156. 157. /** 158.    This class renders a class name either in plain or italic. 159.    Abstract classes are italic. 160. */ 161. class ClassNameTreeCellRenderer extends DefaultTreeCellRenderer 162. { 163.    public Component getTreeCellRendererComponent(JTree tree, Object value, boolean  selected, 164.       boolean expanded, boolean leaf, int row, boolean hasFocus) 165.    { 166.       super.getTreeCellRendererComponent(tree, value, selected, expanded, leaf, row,  hasFocus); 167.       // get the user object 168.       DefaultMutableTreeNode node = (DefaultMutableTreeNode) value; 169.       Class c = (Class) node.getUserObject(); 170. 171.       // the first time, derive italic font from plain font 172.       if (plainFont == null) 173.       { 174.          plainFont = getFont(); 175.          // the tree cell renderer is sometimes called with a label that has a null font 176.          if (plainFont != null) italicFont = plainFont.deriveFont(Font.ITALIC); 177.       } 178. 179.       // set font to italic if the class is abstract, plain otherwise 180.       if ((c.getModifiers() & Modifier.ABSTRACT) == 0) 181.          setFont(plainFont); 182.       else 183.          setFont(italicFont); 184.       return this; 185.    } 186. 187.    private Font plainFont = null; 188.    private Font italicFont = null; 189. } 


 javax.swing.tree.DefaultMutableTreeNode 1.2 

  • Enumeration breadthFirstEnumeration()

  • Enumeration depthFirstEnumeration()

  • Enumeration preOrderEnumeration()

  • Enumeration postOrderEnumeration()

    return enumeration objects for visiting all nodes of the tree model in a particular order. In breadth-first traversal, children that are closer to the root are visited before those that are farther away. In depth-first traversal, all children of a node are completely enumerated before its siblings are visited. The postOrderEnumeration method is a synonym for depthFirstEnumeration. The preorder traversal is identical to the postorder traversal except that parents are enumerated before their children.


 javax.swing.tree.TreeCellRenderer 1.2 

  • Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus)

    returns a component whose paint method is invoked to render a tree cell.

    Parameters:

    tree

    The tree containing the node to be rendered

     

    value

    The node to be rendered

     

    selected

    true if the node is currently selected

     

    expanded

    true if the children of the node are visible

     

    leaf

    TRue if the node needs to be displayed as a leaf

     

    row

    The display row containing the node

     

    hasFocus

    true if the node currently has input focus



 javax.swing.tree.DefaultTreeCellRenderer 1.2 

  • void setLeafIcon(Icon icon)

  • void setOpenIcon(Icon icon)

  • void setClosedIcon(Icon icon)

    set the icon to show for a leaf node, an expanded node, and a collapsed node.

Listening to Tree Events

Most commonly, a tree component is paired with some other component. When the user selects tree nodes, some information shows up in another window. See Figure 6-24 for an example. When the user selects a class, the instance and static variables of that class are displayed in the text area to the right.

Figure 6-24. A class browser


To obtain this behavior, you install a tree selection listener. The listener must implement the treeSelectionListener interface, an interface with a single method

 void valueChanged(TreeSelectionEvent event) 

That method is called whenever the user selects or deselects tree nodes.

You add the listener to the tree in the normal way:

 tree.addTreeSelectionListener(listener); 

You can specify whether the user is allowed to select a single node, a contiguous range of nodes, or an arbitrary, potentially discontiguous, set of nodes. The JTRee class uses a treeSelectionModel to manage node selection. You need to retrieve the model to set the selection state to one of SINGLE_TREE_SELECTION, CONTIGUOUS_TREE_SELECTION, or DISCONTIGUOUS_TREE_SELECTION. (Discontiguous selection mode is the default.) For example, in our class browser, we want to allow selection of only a single class:

 int mode = TreeSelectionModel.SINGLE_TREE_SELECTION; tree.getSelectionModel().setSelectionMode(mode); 

Apart from setting the selection mode, you need not worry about the tree selection model.

NOTE

How the user selects multiple items depends on the look and feel. In the Metal look and feel, hold down the CTRL key while clicking on an item to add the item to the selection, or to remove it if it was currently selected. Hold down the SHIFT key while clicking on an item to select a range of items, extending from the previously selected item to the new item.


To find out the current selection, you query the tree with the getSelectionPaths method:

 TreePath[] selectedPaths = tree.getSelectionPaths(); 

If you restricted the user to a single selection, you can use the convenience method getSelectionPath, which returns the first selected path, or null if no path was selected.

CAUTION

The treeSelectionEvent class has a getPaths method that returns an array of treePath objects, but that array describes selection changes, not the current selection.


Example 6-7 puts tree selection to work. This program builds on Example 6-6; however, to keep the program short, we did not use a custom tree cell renderer. In the frame constructor, we restrict the user to single item selection and add a tree selection listener. When the valueChanged method is called, we ignore its event parameter and simply ask the tree for the current selection path. As always, we need to get the last node of the path and look up its user object. We then call the getFieldDescription method, which uses reflection to assemble a string with all fields of the selected class. Finally, that string is displayed in the text area.

Example 6-7. ClassBrowserTest.java
   1. import java.awt.*;   2. import java.awt.event.*;   3. import java.lang.reflect.*;   4. import java.util.*;   5. import javax.swing.*;   6. import javax.swing.event.*;   7. import javax.swing.tree.*;   8.   9. /**  10.    This program demonstrates tree selection events.  11. */  12. public class ClassBrowserTest  13. {  14.    public static void main(String[] args)  15.    {  16.       JFrame frame = new ClassBrowserTestFrame();  17.       frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);  18.       frame.setVisible(true);  19.    }  20. }  21.  22. /**  23.    A frame with a class tree, a text area to show the properties  24.    of the selected class, and a text field to add new classes.  25. */  26. class ClassBrowserTestFrame extends JFrame  27. {  28.    public ClassBrowserTestFrame()  29.    {  30.       setTitle("ClassBrowserTest");  31.       setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);  32.  33.       // the root of the class tree is Object  34.       root = new DefaultMutableTreeNode(java.lang.Object.class);  35.       model = new DefaultTreeModel(root);  36.       tree = new JTree(model);  37.  38.       // add this class to populate the tree with some data  39.       addClass(getClass());  40.  41.       // set up selection mode  42.       tree.addTreeSelectionListener(new  43.          TreeSelectionListener()  44.          {  45.             public void valueChanged(TreeSelectionEvent event)  46.             {  47.                // the user selected a different node--update description  48.                TreePath path = tree.getSelectionPath();  49.                if (path == null) return;  50.                DefaultMutableTreeNode selectedNode  51.                   = (DefaultMutableTreeNode) path.getLastPathComponent();  52.                Class c = (Class) selectedNode.getUserObject();  53.                String description = getFieldDescription(c);  54.                textArea.setText(description);  55.             }  56.          });  57.       int mode = TreeSelectionModel.SINGLE_TREE_SELECTION;  58.       tree.getSelectionModel().setSelectionMode(mode);  59.  60.       // this text area holds the class description  61.       textArea = new JTextArea();  62.  63.       // add tree and text area  64.       JPanel panel = new JPanel();  65.       panel.setLayout(new GridLayout(1, 2));  66.       panel.add(new JScrollPane(tree));  67.       panel.add(new JScrollPane(textArea));  68.  69.       add(panel, BorderLayout.CENTER);  70.  71.       addTextField();  72.    }  73.  74.    /**  75.       Add the text field and "Add" button to add a new class.  76.    */  77.    public void addTextField()  78.    {  79.       JPanel panel = new JPanel();  80.  81.       ActionListener addListener = new  82.          ActionListener()  83.          {  84.             public void actionPerformed(ActionEvent event)  85.             {  86.                // add the class whose name is in the text field  87.                try  88.                {  89.                   String text = textField.getText();  90.                   addClass(Class.forName(text));  91.                   // clear text field to indicate success  92.                   textField.setText("");  93.                }  94.                catch (ClassNotFoundException e)  95.                {  96.                   JOptionPane.showMessageDialog(null, "Class not found");  97.                }  98.             }  99.          }; 100. 101.       // new class names are typed into this text field 102.       textField = new JTextField(20); 103.       textField.addActionListener(addListener); 104.       panel.add(textField); 105. 106.       JButton addButton = new JButton("Add"); 107.       addButton.addActionListener(addListener); 108.       panel.add(addButton); 109. 110.       add(panel, BorderLayout.SOUTH); 111.    } 112. 113.    /** 114.       Finds an object in the tree. 115.       @param obj the object to find 116.       @return the node containing the object or null 117.       if the object is not present in the tree 118.    */ 119.    public DefaultMutableTreeNode findUserObject(Object obj) 120.    { 121.       // find the node containing a user object 122.       Enumeration e = root.breadthFirstEnumeration(); 123.       while (e.hasMoreElements()) 124.       { 125.          DefaultMutableTreeNode node = (DefaultMutableTreeNode) e.nextElement(); 126.          if (node.getUserObject().equals(obj)) 127.             return node; 128.       } 129.       return null; 130.    } 131. 132.    /** 133.       Adds a new class and any parent classes that aren't 134.       yet part of the tree 135.       @param c the class to add 136.       @return the newly added node. 137.    */ 138.    public DefaultMutableTreeNode addClass(Class c) 139.    { 140.       // add a new class to the tree 141. 142.       // skip non-class types 143.       if (c.isInterface() || c.isPrimitive()) return null; 144. 145.       // if the class is already in the tree, return its node 146.       DefaultMutableTreeNode node = findUserObject(c); 147.       if (node != null) return node; 148. 149.       // class isn't present--first add class parent recursively 150. 151.       Class s = c.getSuperclass(); 152. 153.       DefaultMutableTreeNode parent; 154.       if (s == null) 155.          parent = root; 156.       else 157.          parent = addClass(s); 158. 159.       // add the class as a child to the parent 160.       DefaultMutableTreeNode newNode = new DefaultMutableTreeNode(c); 161.       model.insertNodeInto(newNode, parent, parent.getChildCount()); 162. 163.       // make node visible 164.       TreePath path = new TreePath(model.getPathToRoot(newNode)); 165.       tree.makeVisible(path); 166. 167.       return newNode; 168.    } 169. 170.    /** 171.       Returns a description of the fields of a class. 172.       @param the class to be described 173.       @return a string containing all field types and names 174.    */ 175.    public static String getFieldDescription(Class c) 176.    { 177.       // use reflection to find types and names of fields 178.       StringBuilder r = new StringBuilder(); 179.       Field[] fields = c.getDeclaredFields(); 180.       for (int i = 0; i < fields.length; i++) 181.       { 182.          Field f = fields[i]; 183.          if ((f.getModifiers() & Modifier.STATIC) != 0) r.append("static "); 184.          r.append(f.getType().getName()); 185.          r.append(" "); 186.          r.append(f.getName()); 187.          r.append("\n"); 188.       } 189.       return r.toString(); 190.    } 191. 192.    private DefaultMutableTreeNode root; 193.    private DefaultTreeModel model; 194.    private JTree tree; 195.    private JTextField textField; 196.    private JTextArea textArea; 197.    private static final int DEFAULT_WIDTH = 400; 198.    private static final int DEFAULT_HEIGHT = 300; 199. } 


 javax.swing.JTree 1.2 

  • treePath getSelectionPath()

  • treePath[] getSelectionPaths()

    return the first selected path, or an array of paths to all selected nodes. If no paths are selected, both methods return null.


 javax.swing.event.TreeSelectionListener 1.2 

  • void valueChanged(TreeSelectionEvent event)

    is called whenever nodes are selected or deselected.


 javax.swing.event.TreeSelectionEvent 1.2 

  • treePath getPath()

  • treePath[] getPaths()

    get the first path or all paths that have changed in this selection event. If you want to know the current selection, not the selection change, you should call Jtree.getSelectionPaths instead.

Custom Tree Models

In the final example, we implement a program that inspects the contents of a variable, just like a debugger does (see Figure 6-25).

Figure 6-25. An object inspection tree


Before going further, compile and run the example program. Each node corresponds to an instance variable. If the variable is an object, expand it to see its instance variables. The program inspects the contents of the frame window. If you poke around a few of the instance variables, you should be able to find some familiar classes. You'll also gain some respect for how complex the Swing user interface components are under the hood.

What's remarkable about the program is that the tree does not use the DefaultTreeModel. If you already have data that is hierarchically organized, you may not want to build a duplicate tree and worry about keeping both trees synchronized. That is the situation in our casethe inspected objects are already linked to each other through the object references, so there is no need to replicate the linking structure.

The treeModel interface has only a handful of methods. The first group of methods enables the Jtree to find the tree nodes by first getting the root, then the children. The Jtree class calls these methods only when the user actually expands a node.

 Object getRoot() int getChildCount(Object parent) Object getChild(Object parent, int index) 

This example shows why the treeModel interface, like the Jtree class itself, does not need an explicit notion of nodes. The root and its children can be any objects. The TReeModel is responsible for telling the Jtree how they are connected.

The next method of the treeModel interface is the reverse of getChild:

 int getIndexOfChild(Object parent, Object child) 

Actually, this method can be implemented in terms of the first threesee the code in Example 6-8.

The tree model tells the JTRee which nodes should be displayed as leaves:

 boolean isLeaf(Object node) 

If your code changes the tree model, then the tree needs to be notified so that it can redraw itself. The tree adds itself as a treeModelListener to the model. Thus, the model must support the usual listener management methods:

 void addTreeModelListener(TreeModelListener l) void removeTreeModelListener(TreeModelListener l) 

You can see implementations for these methods in Example 6-8.

When the model modifies the tree contents, it calls one of the four methods of the treeModelListener interface:

 void treeNodesChanged(TreeModelEvent e) void treeNodesInserted(TreeModelEvent e) void treeNodesRemoved(TreeModelEvent e) void treeStructureChanged(TreeModelEvent e) 

The TReeModelEvent object describes the location of the change. The details of assembling a tree model event that describes an insertion or removal event are quite technical. You only need to worry about firing these events if your tree can actually have nodes added and removed. In Example 6-8, we show you how to fire one event: replacing the root with a new object.

TIP

To simplify the code for event firing, we use the javax.swing.EventListenerList convenience class that collects listeners. See Volume 1, Chapter 8 for more information on this class.


Finally, if the user edits a tree node, your model is called with the change:

 void valueForPathChanged(TreePath path, Object newValue) 

If you don't allow editing, this method is never called.

If you don't need to support editing, then constructing a tree model is easily done. Implement the three methods

 Object getRoot() int getChildCount(Object parent) Object getChild(Object parent, int index) 

These methods describe the structure of the tree. Supply routine implementations of the other five methods, as in Example 6-8. You are then ready to display your tree.

Now let's turn to the implementation of the example program. Our tree will contain objects of type Variable.

NOTE

Had we used the DefaultTreeModel, our nodes would have been objects of type DefaultMutableTreeNode with user objects of type Variable.


For example, suppose you inspect the variable

 Employee joe; 

That variable has a type Employee.class, a name "joe", and a value, the value of the object reference joe. We define a class Variable that describes a variable in a program:

 Variable v = new Variable(Employee.class, "joe", joe); 

If the type of the variable is a primitive type, you must use an object wrapper for the value.

 new Variable(double.class, "salary", new Double(salary)); 

If the type of the variable is a class, then the variable has fields. Using reflection, we enumerate all fields and collect them in an ArrayList. Since the getFields method of the Class class does not return fields of the superclass, we need to call getFields on all superclasses as well. You can find the code in the Variable constructor. The getFields method of our Variable class returns the array of fields. Finally, the toString method of the Variable class formats the node label. The label always contains the variable type and name. If the variable is not a class, the label also contains the value.

NOTE

If the type is an array, then we do not display the elements of the array. This would not be difficult to do; we leave it as the proverbial "exercise for the reader."


Let's move on to the tree model. The first two methods are simple.

 public Object getRoot() {    return root; } public int getChildCount(Object parent) {    return ((Variable) parent).getFields().size(); } 

The getChild method returns a new Variable object that describes the field with the given index. The getType and getName method of the Field class yield the field type and name. By using reflection, you can read the field value as f.get(parentValue). That method can throw an IllegalAccessException. However, we made all fields accessible in the Variable constructor, so this won't happen in practice.

Here is the complete code of the getChild method.

 public Object getChild(Object parent, int index) {    ArrayList fields = ((Variable) parent).getFields();    Field f = (Field) fields.get(index);    Object parentValue = ((Variable) parent).getValue();    try    {       return new Variable(f.getType(), f.getName(), f.get(parentValue));    }    catch (IllegalAccessException e)    {       return null;    } } 

These three methods reveal the structure of the object tree to the Jtree component. The remaining methods are routinesee the source code in Example 6-8.

There is one remarkable fact about this tree model: It actually describes an infinite tree. You can verify this by following one of the WeakReference objects. Click on the variable named referent. It leads you right back to the original object. You get an identical subtree, and you can open its WeakReference object again, ad infinitum. Of course, you cannot store an infinite set of nodes. The tree model simply generates the nodes on demand as the user expands the parents.

This example concludes our discussion on trees. We move on to the table component, another complex Swing component. Superficially, trees and tables don't seem to have much in common, but you will find that they both use the same concepts for data models and cell rendering.

Example 6-8. ObjectInspectorTest.java

[View full width]

   1. import java.awt.*;   2. import java.awt.event.*;   3. import java.lang.reflect.*;   4. import java.util.*;   5. import javax.swing.*;   6. import javax.swing.event.*;   7. import javax.swing.tree.*;   8.   9. /**  10.    This program demonstrates how to use a custom tree  11.    model. It displays the fields of an object.  12. */  13. public class ObjectInspectorTest  14. {  15.    public static void main(String[] args)  16.    {  17.       JFrame frame = new ObjectInspectorFrame();  18.       frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);  19.       frame.setVisible(true);  20.    }  21. }  22.  23. /**  24.    This frame holds the object tree.  25. */  26. class ObjectInspectorFrame extends JFrame  27. {  28.    public ObjectInspectorFrame()  29.    {  30.       setTitle("ObjectInspectorTest");  31.       setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);  32.  33.       // we inspect this frame object  34.  35.       Variable v = new Variable(getClass(), "this", this);  36.       ObjectTreeModel model = new ObjectTreeModel();  37.       model.setRoot(v);  38.  39.       // construct and show tree  40.  41.       tree = new JTree(model);  42.       add(new JScrollPane(tree), BorderLayout.CENTER);  43.    }  44.  45.    private JTree tree;  46.    private static final int DEFAULT_WIDTH = 400;  47.    private static final int DEFAULT_HEIGHT = 300;  48. }  49.  50. /**  51.    This tree model describes the tree structure of a Java  52.    object. Children are the objects that are stored in instance  53.    variables.  54. */  55. class ObjectTreeModel implements TreeModel  56. {  57.    /**  58.       Constructs an empty tree.  59.    */  60.    public ObjectTreeModel()  61.    {  62.       root = null;  63.    }  64.  65.    /**  66.       Sets the root to a given variable.  67.       @param v the variable that is being described by this tree  68.    */  69.    public void setRoot(Variable v)  70.    {  71.       Variable oldRoot = v;  72.       root = v;  73.       fireTreeStructureChanged(oldRoot);  74.    }  75.  76.    public Object getRoot()  77.    {  78.       return root;  79.    }  80.  81.    public int getChildCount(Object parent)  82.    {  83.       return ((Variable) parent).getFields().size();  84.    }  85.  86.    public Object getChild(Object parent, int index)  87.    {  88.       ArrayList<Field> fields = ((Variable) parent).getFields();  89.       Field f = (Field) fields.get(index);  90.       Object parentValue = ((Variable) parent).getValue();  91.       try  92.       {  93.          return new Variable(f.getType(), f.getName(), f.get(parentValue));  94.       }  95.       catch (IllegalAccessException e)  96.       {  97.          return null;  98.       }  99.    } 100. 101.    public int getIndexOfChild(Object parent, Object child) 102.    { 103.       int n = getChildCount(parent); 104.       for (int i = 0; i < n; i++) 105.          if (getChild(parent, i).equals(child)) 106.             return i; 107.       return -1; 108.    } 109. 110.    public boolean isLeaf(Object node) 111.    { 112.       return getChildCount(node) == 0; 113.    } 114. 115.    public void valueForPathChanged(TreePath path, 116.       Object newValue) 117.    {} 118. 119.    public void addTreeModelListener(TreeModelListener l) 120.    { 121.       listenerList.add(TreeModelListener.class, l); 122.    } 123. 124.    public void removeTreeModelListener(TreeModelListener l) 125.    { 126.       listenerList.remove(TreeModelListener.class, l); 127.    } 128. 129.    protected void fireTreeStructureChanged(Object oldRoot) 130.    { 131.       TreeModelEvent event = new TreeModelEvent(this, new Object[] {oldRoot}); 132.       EventListener[] listeners = listenerList.getListeners(TreeModelListener.class); 133.       for (int i = 0; i < listeners.length; i++) 134.          ((TreeModelListener) listeners[i]).treeStructureChanged(event); 135.    } 136. 137.    private Variable root; 138.    private EventListenerList listenerList = new EventListenerList(); 139. } 140. 141. /** 142.    A variable with a type, name, and value. 143. */ 144. class Variable 145. { 146.    /** 147.       Construct a variable 148.       @param aType the type 149.       @param aName the name 150.       @param aValue the value 151.    */ 152.    public Variable(Class aType, String aName, Object aValue) 153.    { 154.       type = aType; 155.       name = aName; 156.       value = aValue; 157.       fields = new ArrayList<Field>(); 158. 159.       // find all fields if we have a class type except we don't expand strings and  null values 160. 161.       if (!type.isPrimitive() && !type.isArray() && !type.equals(String.class) &&  value != null) 162.       { 163.          // get fields from the class and all superclasses 164.          for (Class c = value.getClass(); c != null; c = c.getSuperclass()) 165.          { 166.             Field[] fs = c.getDeclaredFields(); 167.             AccessibleObject.setAccessible(fs, true); 168. 169.             // get all nonstatic fields 170.             for (Field f : fs) 171.                if ((f.getModifiers() & Modifier.STATIC) == 0) 172.                   fields.add(f); 173.          } 174.       } 175.    } 176. 177.    /** 178.       Gets the value of this variable. 179.       @return the value 180.    */ 181.    public Object getValue() { return value; } 182. 183.    /** 184.       Gets all nonstatic fields of this variable. 185.       @return an array list of variables describing the fields 186.    */ 187.    public ArrayList<Field> getFields() { return fields; } 188. 189.    public String toString() 190.    { 191.       String r = type + " " + name; 192.       if (type.isPrimitive()) r += "=" + value; 193.       else if (type.equals(String.class)) r += "=" + value; 194.       else if (value == null) r += "=null"; 195.       return r; 196.    } 197. 198.    private Class type; 199.    private String name; 200.    private Object value; 201.    private ArrayList<Field> fields; 202. } 


 javax.swing.tree.TreeModel 1.2 

  • Object getRoot()

    returns the root node.

  • int getChildCount(Object parent)

    gets the number of children of the parent node.

  • Object getChild(Object parent, int index)

    gets the child node of the parent node at the given index.

  • int getIndexOfChild(Object parent, Object child)

    gets the index of the child node in the parent node, or -1 if child is not a child of parent in this tree model.

  • boolean isLeaf(Object node)

    returns true if node is conceptually a leaf of the tree.

  • void addTreeModelListener(TreeModelListener l)

  • void removeTreeModelListener(TreeModelListener l)

    add and remove listeners that are notified when the information in the tree model changes.

  • void valueForPathChanged(TreePath path, Object newValue)

    is called when a cell editor has modified the value of a node.

    Parameters:

    path

    The path to the node that has been edited

     

    newValue

    The replacement value returned by the editor



 javax.swing.event.TreeModelListener 1.2 

  • void treeNodesChanged(TreeModelEvent e)

  • void treeNodesInserted(TreeModelEvent e)

  • void treeNodesRemoved(TreeModelEvent e)

  • void treeStructureChanged(TreeModelEvent e)

    are called by the tree model when the tree has been modified.


 javax.swing.event.TreeModelEvent 1.2 

  • treeModelEvent(Object eventSource, TreePath node)

    constructs a tree model event.

    Parameters:

    eventSource

    The tree model generating this event

     

    node

    The path to the node that is being changed




    Core JavaT 2 Volume II - Advanced Features
    Building an On Demand Computing Environment with IBM: How to Optimize Your Current Infrastructure for Today and Tomorrow (MaxFacts Guidebook series)
    ISBN: 193164411X
    EAN: 2147483647
    Year: 2003
    Pages: 156
    Authors: Jim Hoskins

    flylib.com © 2008-2017.
    If you may any questions please contact us: flylib@qtcs.net