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 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(); }
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.
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:
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 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 class provides cover methods for TreeModel and TreeSelectionModel.
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 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:
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.
At the current stage of development, features that remain to be added
to JTree and its associated classes
include the following:
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
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);
Version 0.5. Last modified 10/6/97.
Copyright © 1995-97 Sun
Microsystems, Inc. All Rights Reserved.