Contents

Swinging Duke
Feedback Button
Left ArrowRight Arrow

 

The JTree Class

Swing's Outline Control

Swing's JTree object is a user-interface component that presents arbitrary hierarchical data in an outline format. The classes used to create and manage JTree components are:


The TreeModel Interface

The TreeModel interface is defined as follows:

public interface TreeModel
{
public Object getRoot();
public Object getChild(Object parent, int index);
public int getIndexOfChild(Object parent, Object node);
public int getChildCount(Object parent);
public boolean isLeaf(Object node);

public void addTreeModelListener(TreeModelListener x);
public void removeTreeModelListener(TreeModelListener x);

public void valueForPathChanged(JTreePath path, Object newValue);
}

public interface TreeModelListener extends java.util.EventListener
{
void treeNodesChanged(TreeModelEvent e);
void treeNodesInserted(TreeModelEvent e);
void treeNodesRemoved(TreeModelEvent e);
void treeStructureChanged(TreeModelEvent e);
}

public class TreeModelEvent extends java.util.EventObject
{
TreeModelEvent(TreeModel source, Object[] path, 
   int[] childIndices);
TreeModelEvent(TreeModel source, JTreePath path, 
   int[] childIndices);

public Object[] getPath();
public JTreePath getTreePath();
public int[] getChildIndices();
public Object[] getChildren();
}


How the JTree Component Works

The model defined in the preceding interface provides a tree structure for a JTree component (or some other kind of hierarchy control) to display. The model can decide the data type of each node; for example, a model that encapsulates a file system might use instances of java.io.File as its nodes. A JTree communicates with the model and with clients of the JTree using the nodes as common currency (for example, asking the JTree for its selection returns a set of these nodes).

It is important to note that a TreeModel encapsulates an entire tree, not just its root node. This convention has at least two advantages: It lets the implementer use any object that he or she chooses as a node class, and it simplifies sparse implementations of trees.

The TreeModel provides a reference to the root of a tree and provides information about the rest of the tree structure relatively -- that is, it can return the children or a child count of a given node, and it can report whether or not a given node is a leaf. The children (if any) of each node must be implemented in a defined order. Many models can implement the isLeaf() method to return true if the node has no children, but other models may separate the two concepts -- for instance, a directory in a file system is not a leaf but might not have any children.

When a value has been changed, usually as the result of a UI gesture, valueForPathChanged() is invoked. At this point, it is up to the model to decide whether the value should (in fact) be changed -- and, if so, to create the appropriate event and notify all listeners.


Change Events

A TreeModel uses a finer-grained set of event messages than a typical Swing model. A single event indicating that something about the tree has changed is of little use if the tree is large or complex; a tree component listening for such an event would need to traverse the tree to figure out what had changed. Instead, the TreeModel can send its listeners four different messages that identify more precisely what has changed:

treeNodesChanged():
This message is sent after a node in a tree has changed in some way but has the same parent and the same children. The event message contains the path to the node that changed. Its childIndices array is empty. Example: The name of a file has changed, but it is in the same location in the file system.
 
treeNodesInserted():
This message is sent after nodes have been added to a tree. The event message contains the path to the parent of the new nodes and the indices at which they can be found, in ascending order, in the parent's array of children. Example: A new file has been created in a certain directory.
 
treeNodesRemoved():
This message is sent after nodes are removed from a tree. If a subtree is removed, this event can be posted only once for the root of the removed subtree. The event message contains the path to the former parent and child indices in ascending order of the removed nodes. Example: A group of files has been removed from a certain directory.
 
treeStructureChanged():
This message is sent when a tree structure has changed extensively from a given node down. The event contains the path to node. Its childIndices array is empty. Compare this to the treeNodesChanged() method, which would be sent if a set of nodes had changed in some way but were still the same objects with the same children.

JTree updates the interface accordingly when it receives any of the above events. Thus, by creating the appropriate events and passing them to the listeners, the tree updates itself. If you use JTreeModel as your TreeModel (this is created for you if you create a JTree with a TreeNode) you must message it with one of the following when you alter the TreeNodes: reload(), reload(TreeNode), nodesWereInserted(TreeNode, int[]), nodesWereRemoved(TreeNode, int[]), nodeChanged(TreeNode), or nodesChanged(TreeNode, int[]). Calling one of these methods creates the appropriate event and message the listeners.


JTree's SelectionModel

JTree uses the TreeSelectionModel which is used to represent a set of JTreePath instances. TreeSelectionModel can also maintain an index associated with each JTreePath instance. Mapping a JTreePath to an index is accomplished by way of the RowMapper interface. The RowMapper interface consists of one method: getRowsForPaths(). TreeSelectionModel does not need a RowMapper to manage the set of JTreePaths -- but without one all of the row-based methods behaves as if nothing is selected. When you use the TreeSelectionModel provided by JTree, a RowMapper is set in such a way that that you can ask the TreeSelectionModel that is returned from getSelectionModel() for row-based information. If you want to alter the selection of JTree, it is perfectly legal to manipulate the selection by way of the TreeSelectionModel. The UI updates itself accordingly, as well as expanding the ancestors of paths that are not expanded.

public interface TreeSelectionModel
{
void setSelectionMode(int);
int getSelectionMode();
void setSelectionPath(JTreePath path);
void setSelectionPaths(JTreePath[] paths);
void addSelectionPath(JTreePath path);
void addSelectionPaths(JTreePath[] paths);
void removeSelectionPath(JTreePath path);
void removeSelectionPaths(JTreePath[] paths);

void clearSelection();

JTreePath getSelectionPath();
JTreePath[] getSelectionPaths();
int getSelectionCount();

boolean isPathSelected(JTreePath path);
boolean isSelectionEmpty();

void setRowMapper(RowMapper newMapper);
RowMapper getRowMapper();

int[] getSelectionRows();
int getMinSelectionRow();
int getMaxSelectionRow();

int getLeadSelectionRow();
int getLeadSelectionPath();

boolean isRowSelected(int row)

void resetRowSelection();

void addTreeSelectionListener(TreeSelectionListener x);
void removeTreeSelectionListener(TreeSelectionListener x);
}

TreeSelectionModel provides three basic types of methods to operate on the set of JTreePath instances it maintains. There are two methods for setting the set of path (setSelectionPath() and setSelectionPaths()), two for adding to the set (addSelectionPath() and addSelectionPaths()), and two for removing from the set (removeSelectionPath() and removeSelectionPaths()). You can empty the set by calling clearSelection(). The set of paths that are selected can be determined by calling getSelectionPath(), which returns the first selected path, and getSelectionPaths(), which returns the complete set. The getSelectionCount() method returns the number of paths in the current set.

A path can be tested for inclusion in the set by calling isPathSelected(), and the empty selection set can tested by using isSelectionEmpty(). If an instance implementing RowMapper is provided, the selected rows can be determined by calling getSelectionRows(), the first selected row can be determined by calling getMinSelectionRow(), and the last selected row can be determined by calling getMaxSelectionRow(). You can check to see if a particular row is in the set by calling isRowSelected(). The resetRowSelection() method does not normally need to be called, but is used to update the rows that are selected by messaging the RowMapper for each path in the selection set.


The JTree Component

The JTree class provides cover methods for TreeModel and TreeSelectionModel.


Rendering

In pursuit of speed and a light weight, a JTree renders each of its nodes using a single cell renderer supplied by the client -- an approach similar to that used by JListBox). If none is provided, the JTree uses a simple default renderer that draws its value's response toString() (see the section titled "The TreeNode API").

Tree cell renderers must be components, and they must implement the TreeCellRenderer interface, which is defined as follows:

public interface TreeCellRenderer
{
void getTreeCellRendererComponent(JTree tree, Object value, 
   boolean selected, boolean expanded, boolean leaf, int row, 
   boolean hasFocus);
}

In this interface, the value parameter is the node that is being drawn. The selected flag causes the renderer to draw a cell differently if it is selected, while the hasFocus flag specifies that the Tree has focus. It is up to the renderer to represent the selected and focus states, usually by drawing a background color and a bezel.


Editing

Editing is accomplished by way of the TreeCellEditor interface. TreeCellEditor extends CellEditor with the
method getTreeCellEditorComponent(Object value, boolean isSelected, boolean expanded, boolean
leaf). A default implementation of TreeCellEditor is provided for the basic look and feel. If you wish to
allow editing you need to message JTree with setEditable(true). The sequence of events in editing is:

  1. Let the editor know what is going to be edited by way of getTreeCellEditorComponent().
    Test if the editor will allow editing of this item by messaging it with canEdit(EventObject). The
    editor may conditionally return true based on the event and/or the value being edited. If this method
    returns true, editing is started.
  2. Place the Component inside the tree.
  3. Message the editor to start editing with the method startEditing(). In response to such a method
    the editor will usually select it self, or somehow graphically show that it now has focus.

After this sequence of events, editing can be stopped in a number of ways. Any sequence of events that
would cause another item to be selected in the tree causes the editing to be canceled. You can accomplish
the same thing by invoking cancelEditing() on the TreeCellEditor. Editing is stopped if the tree receives an
editingStopped event, which is created by way of invoking stopEditing() on the tree or on the editor. If
editing has stopped, the TreeModel is messaged with the path of the value that was edited and the new
value. If the model decides to commit the change, it should then update its internal state and create a
nodeChanged event. The tree receives this information and update itself accordingly.


Work in Progress

At the current stage of development, features that remain to be added to JTree and its associated classes
include the following:


The TreeNode API

The TreeNode API consists of two interfaces, TreeNode and MutableTreeNode and an implementation of
both interfaces DefaultMutableTreeNode. TreeNode defines the minimum API that is needed by the
DefaultTreeModel to implement the TreeModel interface. DefaultTreeModel also provides conveneince
methods for creating events and adding, removing, changing nodes that require an implementation of
MutableTreeNode. If you already have an existing hierarchical API it may be easier for you to make those
objects implement the TreeNode interface, or alternatively you could create your own implementation of the
TreeModel interface. While DefaultMutableTreeNode will allow you to add other implementations of
TreeNode to it, it is strongly recommended that you only add DefaultMutableTreeNodes to it. The reason for
this is that DefaultMutableTreeNode contains many convenience methods that will assume the tree
structure is composed entirely of DefaultMutableTreeNode instances, and if you try to use some of these
methods in a tree structure that contains other implementations of TreeNode you may get exceptions.

TreeNode is defined as follows:

public interface TreeNode
{ 
TreeNode getChildAt(int childIndex); 
int getChildCount(); 
TreeNode getParent(); 
int getIndex(TreeNode node); 
boolean getAllowsChildren(); 
boolean isLeaf(); 
Enumeration children(); 
}

MutableTreeNode is defined as follows:

public interface MutableTreeNode extends TreeNode
{ 
void insert(MutableTreeNode child, int index); 
void remove(int index); 
void remove(MutableTreeNode node); 
void setUserObject(Object value); 
void removeFromParent(); 
void setParent(MutableTreeNode parent); 
}

DefaultMutableTreeNode is defined as follows:

public class TreeNode extends Object implements Cloneable, 
   Serializable 
{ 
public TreeNode(); 
public TreeNode(Object userObject); 
public TreeNode(Object userObject, boolean allowsChildren); 

public void insertChildAt(TreeNode newChild, int childIndex); 
public void removeChildAt(int childIndex); 
public TreeNode getParent(); 
public TreeNode getChildAt(int index); 
public int getChildCount(); 
public int getIndex(TreeNode aChild) 

public TreeNode[] getPath();
public Object[] getUserObjectPath();

public Enumeration children(); 

public void setAllowsChildren(boolean allows); 
public boolean getAllowsChildren(); 

public void setUserObject(); 
public Object getUserObject(); 

// Includes convenience methods for adding, removing, replacing 
// nodes 
// Includes methods for querying about ancestors and descendants 
// Includes methods for querying about the tree depth, level, and 
// root 

public Enumeration preorderEnumeration(); 
public Enumeration postorderEnumeration(); 
public Enumeration breadthFirstEnumeration(); 
public Enumeration depthFirstEnumeration(); 
public Enumeration pathFromAncestorEnumeration(); 

// Includes methods for querying about children, siblings, and leaves

}

DefaultMutableTreeNode


Notes

At the current stage of DefaultMutableTreeNode's development, the following notes apply:

DefaultMutableTreeNode is a utility class that simplifies constructing hierarchical models. It provides a number of typical traversals and queries. Because each node has a user hook (for example, userObject), simple models do not have to be subclassed. Sending a toString() message to a DefaultMutableTreeNode forwards that message to the node's userObject, and returns the userObject's response (or null, if the userObject is null).

DefaultMutableTreeNode does not depend on anything else in AWT, so it could eventually be moved to java.util -- or, perhaps a better idea, into the JDK's forthcoming collection classes. JTree provides a default data model that uses DefaultMutableTreeNodes to answer TreeModel methods, and provides a default cell renderer that paints the toString() response of JTree's current value. Consequently, displaying a tree on the screen can be as simple as:

DefaultMutableTreeNode root = new DefaultMutableTreeNode("root");

DefaultMutableTreeNode child = new DefaultMutableTreeNode
   ("Letters"); 
root.add(child);
child.add(new DefaultMutableTreeNode("Able"));
child.add(new DefaultMutableTreeNode("Baker"));
child.add(new DefaultMutableTreeNode("Charlie"));

JTree tree = new JTree(root);

// Put it somewhere the user can see it

// Time passes, we want to add something to the tree

child = new DefaultMutableTreeNode("Numbers");
child.add(new DefaultMutableTreeNode("One"));
child.add(new DefaultMutableTreeNode("Two"));
child.add(new DefaultMutableTreeNode("Three"));

// Notify the model, which will add it and create
// the appropriate event. The tree will
// receive this event update accordingly.

((DefaultTreeModel)tree.getModel()).insertNodeInto(child, root, 0);

  

Arrows


Version 0.5. Last modified 10/6/97.
Copyright © 1995-97 Sun Microsystems, Inc. All Rights Reserved.

Sun's Home Page