17.3 The JTree Class
Now
that you've seen all the tree models and some of the
default implementations, let's look at the visual
representation we can give them. The JTree class
can build up trees out of several different objects, including a
TreeModel. JTree extends
directly from JComponent and represents the visual
side of any valid tree structure.
As another example of hierarchical
data, let's look at a tree that displays XML
documents. (We'll leave the details of
XML to Brett
McLaughlin and his excellent Java and XML book.
Of course, as our own Bob Eckstein also wrote the XML
Pocket Reference, we'll include a
shameless plug for that, too.) Here's an entirely
contrived XML document that contains several layers of data:
<?xml version="1.0"?>
<simple>
<level1 attr="value" a2="v2">
This is arbitrary data...
<emptytag1 />
<et2 a1="v1"/>
<level2 more="attributes">
<input type="text" name="test"/>
</level2>
</level1>
<!-- one more level to test...--><test/>
<one>
<two>
<three>
<four/>
<five/><fiveA/>
</three>
<six/>
<seven>
<eight/>
</seven>
</two>
<nine/>
</one>
<multi><line>test</line></multi>
</simple>
Figure 17-6 shows the representation of this
document in a JTree.
In this example, we treat XML tags with children as nodes and tags
without children as leaves. Any tag with actual data (not counting
the attributes—more on those later) shows that data in its
label. We create a simple inner class to store the tags as they are
generated by the XML parser. The other inner class,
XMLTreeHandler, fills in the tree model based on
the parser's events.
Here's the source code for
VSX.java, our Very Simple XML example:
// VSX.java
import javax.swing.*;
import javax.swing.tree.*;
import java.util.*;
import java.io.*;
import org.xml.sax.*;
import org.xml.sax.helpers.*;
import javax.xml.parsers.*;
public class VSX {
public TreeModel parse(String filename) {
SAXParserFactory factory = SAXParserFactory.newInstance( );
XMLTreeHandler handler = new XMLTreeHandler( );
try {
// Parse the input.
SAXParser saxParser = factory.newSAXParser( );
saxParser.parse( new File(filename), handler);
}
catch (Exception e) {
System.err.println("File Read Error: " + e);
e.printStackTrace( );
return new DefaultTreeModel(new DefaultMutableTreeNode("error"));
}
return new DefaultTreeModel(handler.getRoot( ));
}
public static class XMLTreeHandler extends DefaultHandler {
private DefaultMutableTreeNode root, currentNode;
public DefaultMutableTreeNode getRoot( ) {
return root;
}
// SAX parser handler methods
public void startElement(String namespaceURI, String lName, String qName,
Attributes attrs) throws SAXException {
String eName = lName; // Element name
if ("".equals(eName)) eName = qName;
Tag t = new Tag(eName, attrs);
DefaultMutableTreeNode newNode = new DefaultMutableTreeNode(t);
if (currentNode == null) {
root = newNode;
}
else {
// Must not be the root node
currentNode.add(newNode);
}
currentNode = newNode;
}
public void endElement(String namespaceURI, String sName, String qName)
throws SAXException {
currentNode = (DefaultMutableTreeNode)currentNode.getParent( );
}
public void characters(char buf[], int offset, int len) throws SAXException {
String s = new String(buf, offset, len).trim( );
((Tag)currentNode.getUserObject( )).addData(s);
}
}
public static class Tag {
private String name;
private String data;
private Attributes attr;
public Tag(String n, Attributes a) { name = n; attr = a; }
public String getName( ) { return name; }
public Attributes getAttributes( ) { return attr; }
public void setData(String d) { data = d; }
public String getData( ) { return data; }
public void addData(String d) {
if (data == null) {
setData(d);
}
else {
data += d;
}
}
public String getAttributesAsString( ) {
StringBuffer buf = new StringBuffer(256);
for (int i = 0; i < attr.getLength( ); i++) {
buf.append(attr.getQName(i));
buf.append("=\"");
buf.append(attr.getValue(i));
buf.append("\"");
}
return buf.toString( );
}
public String toString( ) {
String a = getAttributesAsString( );
return name + ": " + a + (data == null ? "" :" (" + data + ")");
}
}
public static void main(String args[]) {
if (args.length != 1) {
System.err.println("Usage is: java VSX testfile.xml");
System.exit(1);
}
JFrame frame = new JFrame("VSX Test");
VSX parser = new VSX( );
JTree tree = new JTree(parser.parse(args[0]));
frame.getContentPane( ).add(new JScrollPane(tree));
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(300,400);
frame.setVisible(true);
}
}
The parse( ) method does most of the work in this
example. The events from the SAXParser are used to
determine the structure of the tree. Once the document is parsed,
that model is used to build the JTree object that
we display inside a JScrollPane.
17.3.1 Properties
The JTree class contains properties (shown in
Table 17-3) for manually displaying and editing
tree cells if you need this control. The
editable
property specifies whether cells can be edited (i.e., modified by
users). The
toggleClickCount property allows you to specify
how many clicks are required to start editing a tree node. The
cellEditor,
cellRenderer, and
invokesStopCellEditing properties affect the components used
to display and manipulate trees. Setting
invokesStopCellEditing to true
forces changes to a cell to be saved if editing is interrupted.
Table 17-3. JTree properties
accessibleContexto
|
AccessibleContext
|
·
|
|
|
JTree.AccessibleJTree( )
|
anchorSelectionPath1.3
|
TreePath
|
·
|
|
·
|
null
|
cellEditorb
|
TreeCellEditor
|
·
|
|
·
|
null
|
cellRendererb
|
TreeCellRenderer
|
·
|
|
·
|
null
|
collapsed1.4 ,i , o, *
|
boolean
|
|
·
|
|
|
dragEnabled1.4
|
boolean
|
·
|
|
·
|
false
|
editableb, o
|
boolean
|
|
·
|
·
|
false
|
editing
|
boolean
|
|
·
|
|
false
|
editingPath
|
TreePath
|
·
|
|
|
null
|
expanded1.4 ,i ,o, *
|
boolean
|
|
·
|
|
|
expandsSelectedPaths
|
boolean
|
·
|
|
·
|
true
|
fixedRowHeight
|
boolean
|
|
·
|
|
true
|
invokesStopCellEditingb
|
boolean
|
·
|
|
·
|
false
|
largeModelb
|
boolean
|
|
·
|
·
|
false
|
lastSelectedPathComponent
|
Object
|
·
|
|
|
null
|
leadSelectionPath
|
TreePath
|
·
|
|
·
|
null
|
leadSelectionRow
|
int
|
·
|
|
|
-1
|
maxSelectionRow
|
int
|
·
|
|
|
From selection model
|
minSelectionRow
|
int
|
·
|
|
|
From selection model
|
modelb
|
TreeModel
|
·
|
|
·
|
null
|
opaqueb, o
|
boolean
|
|
·
|
·
|
true
|
preferredScrollableViewportSize
|
Dimension
|
·
|
|
|
From L&F
|
rootVisibleb
|
boolean
|
|
·
|
·
|
true
|
rowCount
|
int
|
·
|
|
|
|
rowHeightb, +
|
int
|
·
|
|
·
|
16
|
rowSelectedi
|
boolean
|
|
·
|
|
|
scrollableTracksViewportHeight
|
boolean
|
·
|
|
|
false
|
scrollableTracksViewportWidth
|
boolean
|
·
|
|
|
false
|
scrollsOnExpandb
|
boolean
|
·
|
|
·
|
true
|
selectionEmpty
|
boolean
|
|
·
|
|
From selection model
|
selectionModelb
|
TreeSelectionModel
|
·
|
|
·
|
DefaultTreeSelec-tionModel( )
|
selectionPath
|
TreePath
|
·
|
|
·
|
From selection model
|
selectionPaths
|
TreePath[]
|
·
|
|
·
|
From selection model
|
selectionRows
|
int[]
|
·
|
|
·
|
From selection model
|
showsRootHandlesb
|
boolean
|
·
|
|
·
|
false
|
toggleClickCount1.3
|
int
|
·
|
|
|
2
|
treeExpansionListeners1.4
|
TreeExpansionList-ener[]
|
·
|
|
|
Empty array
|
treeSelectionListeners1.4
|
TreeSelectionList-ener[]
|
·
|
|
|
Empty array
|
treeWillExpandListeners1.4
|
TreeWillExpand-Listener[]
|
·
|
|
|
Empty array
|
UIb, o
|
TreeUI
|
·
|
|
·
|
From L&F
|
UIClassIDo
|
String
|
·
|
|
|
"TreeUI"
|
visibleRowCountb
|
int
|
·
|
|
·
|
20
|
1.3since 1.3, 1.4since 1.4, bbound, iindexed, ooverridden
*Indexed by int and TreePath.
+A value of -1 indicates variable row heights.
See also properties of the JComponent class
(Table 3-6).
|
The display of the tree itself inside a
scrollpane is managed by the
preferredScrollableViewportSize,
scrollableTracksViewportHeight, and
scrollable-TracksViewportWidth
properties. The preferred
viewport size specifies the desired size
of the viewport showing the tree. Both of the tracking properties are
false to indicate that changing the size of the
viewport containing the tree does not affect the calculated width or
height of the tree. They can be overridden for specialized behavior.
For example, if you placed your tree in a
JScrollPane, and the width of that pane were
suddenly changed so that a given node might not be visible without
scrolling, you could turn on tooltips for the long node and supply a
string that contained the entire path for that node. The tooltip pop
up would show the whole path without scrolling, regardless of the
viewport size.
Several properties give you access to the state of selections for a
tree. Properties such as
anchorSelectionPath,
leadSelectionPath,
leadSelectionRow,
minSelectionRow, and
maxSelectionRow
indicate the location of various selected entries. An anchor path is
the first entry the user clicked (which may or may not be the same as
the min/max selections). You can determine whether a programmatic
selection change visually expands the children of a node with the
expandsSelectedPaths property. The
lastSelectedPath property tracks the most recently
selected path.
If you need more control over selections, you can use the
selectionModel
property. You can modify the current model or even install your own
custom model. Most of the time, however, you can just use
JTree properties like
selectionEmpty
,
selectionPath, selectionPaths,
and selectionRows to let you know exactly which
(if any) nodes in the tree are selected.
The JTree class also provides properties that
allow you to control the tree's appearance
regardless of the display and editing mechanisms used. Many aspects
of the tree's appearance are based on the concept of
a "row," which is a single item
currently displayed in the tree. You can control the row height and
root display style with the
rowHeight
,
fixedRowHeight, rootVisible,
and showsRootHandles properties.
fixedRowHeight specifies that all rows must have
the same height; if it is false, row heights may
vary. rootVisible is true if
the tree's root is displayed; if it is
false, the root is omitted. Its initial value
depends on which constructor you call. If
scrollsOnExpand is true,
expanding any node automatically scrolls the tree so that as many of
the node's children as possible are visible.
showsRootHandles determines whether the one-touch
expand/collapse control (or
"handle") appears for the root
node.
Another interesting property of trees is
largeModel . Some UI managers pay attention to this
property and alter their behavior if it is set to
true, presumably to increase the efficiency of
updates and model events. The size of the tree that merits using this
property depends largely on your application, but if
you're wondering if your tree could benefit from a
large model, try turning the property on and playing with your tree
to see if you notice a performance gain.
Several indexed properties give you a quick view of the state of the
tree. The
rowSelected property tells you whether a
particular row is selected. The expanded and
collapsed properties tell you whether a row is,
well, expanded or collapsed.
SDK 1.4 introduced access to the event listeners attached to the tree
through the
treeExpansionListeners
,
treeSelectionListeners, and
treeWillExpandListeners properties.
Many methods of the JTree class should be
considered accessors for properties we
haven't listed in Table 17-3. This
omission is intentional (though not undebated). We felt it would be
clearer if these methods were discussed with similar methods that
don't fit the
"property" patterns.
17.3.2 Events
The JTree class adds
support for the expansion and selection events shown in Table 17-4. We will look at these events in greater
detail (with examples) in Section 17.6 later in this chapter.
Table 17-4. JTree events
TreeExpansionEvent
|
A tree node has been (or will be) expanded or collapsed.
|
TreeSelectionEvent
|
A row (path) has been selected or, if more than one row can be
selected, a row has been added or removed from the current selection.
|
The TreeEvents class in the code for this chapter
reports all the events generated by the JTree
object. These events are supported by the following methods:
- public void
addTreeExpansionListener(TreeExpansionListener l)
- public void removeTreeExpansionListener(TreeExpansionListener l)
-
Add or remove listeners interested in receiving tree expansion events.
- public void addTreeWillExpandListener (TreeWillExpandListener tel)
- public void removeTreeWillExpandListener (TreeWillExpandListener tel)
-
Add or remove listeners interested in receiving
"tree will expand" events. Note
that no
"TreeWillExpandEvent"
class exists. The methods of the
TreeWillExpandListener interface (discussed later
in this chapter) use TreeExpansionEvent objects.
- public void addTreeSelectionListener(TreeSelectionListener l)
- public void removeTreeSelectionListener(TreeSelectionListener l)
-
Add or remove listeners interested in receiving tree selection events.
- public void fireTreeCollapsed(TreePath collapsedPath)
- public void fireTreeExpanded(TreePath expandedPath)
-
Notify any registered TreeExpansionListener
objects that a path has collapsed or expanded. The
collapsedPath and expandedPath
arguments are used to construct a new
TreeExpansionEvent with this
JTree as the source.
- public void fireTreeWillExpand(TreePath path) throws ExpandVetoException
- public void fireTreeWillCollapse(TreePath path) throws ExpandVetoException
-
Notify registered listeners that a tree node is about to expand or
collapse. The path argument constructs a new
TreeExpansionEvent object sent to the listeners.
The ExpandVetoException class is discussed later
in Section 17.6 of this
chapter.
- protected void fireValueChanged(TreeSelectionEvent selectionEvent)
-
Notify registered TreeSelectionListener objects
that a selection event has occurred. Whenever a listener registers
with this JTree, an event redirector is set up to
grab the selection events coming from the tree selection model and
pass them to the listener with this JTree as the
source. You do not need to worry about the selection model to attach
a listener. You attach the listener to the JTree
itself, and the redirector does the work.
JTree also generates property change events
whenever any of its bound properties are modified.
17.3.3 Constants
The constants provided with the JTree class are
used for reporting the names of bound properties in property change
events and are listed in Table 17-5.
Table 17-5. JTree string constants for property change events
ANCHOR_SELECTION_PATH_PROPERTY1.3
|
ROOT_VISIBLE_PROPERTY
|
CELL_EDITOR_PROPERTY
|
ROW_HEIGHT_PROPERTY
|
CELL_RENDERER_PROPERTY
|
SCROLLS_ON_EXPAND_PROPERTY
|
EDITABLE_PROPERTY
|
SELECTION_MODEL_PROPERTY
|
EXPANDS_SELECTED_PATHS_PROPERTY1.3
|
SHOWS_ROOT_HANDLES_PROPERTY
|
INVOKES_STOP_CELL_EDITING_PROPERTY
|
TOGGLE_CLICK_COUNT_PROPERTY1.3
|
LARGE_MODEL_PROPERTY
|
TREE_MODEL_PROPERTY
|
LEAD_SELECTION_PATH_PROPERTY1.3
|
VISIBLE_ROW_COUNT_PROPERTY
|
1.3since 1.3
|
17.3.4 Constructors
- public JTree( )
-
Create a tree using a DefaultTreeModel object as
its base. You should probably use one of the other constructors to
get an interesting tree. The default tree is populated with some
meaningless sample content.
- public JTree(TreeNode root)
- public JTree(TreeNode root, boolean asksAllowsChildren)
-
Build new trees using the node root as the root of
the tree. These constructors also use the
DefaultTreeModel as their model.
- public JTree(TreeModel model)
-
Build a tree using the model provided. The model
argument contains the root of the tree.
- public JTree(Object value[])
- public JTree(Vector value)
- public JTree(Hashtable value)
-
Build a DefaultTreeModel object and use the inner
class JTree.DynamicUtilTreeNode to populate the
tree using the value argument as children. If any
element in value is itself an
Object[], a Vector, or a
Hashtable, a node is built for that element, and
its contents become children of the node. This recursive process
continues until all elements and their contents are explored.
The last constructor is great for simple data structures that you
want to display as a tree. Figure 17-7 shows the
tree that results when you display a hashtable.
Even though this tree is larger than the tree in Figure 17-1, it takes less code to set it up:
// ObjectTree.java
//
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.tree.*;
import java.util.*;
public class ObjectTree extends JFrame {
JTree tree;
String[][] sampleData = {
{"Amy"}, {"Brandon", "Bailey"},
{"Jodi"}, {"Trent", "Garrett", "Paige", "Dylan"},
{"Donn"}, {"Nancy", "Donald", "Phyllis", "John", "Pat"},
{"Ron"}, {"Linda", "Mark", "Lois", "Marvin"}
};
public ObjectTree( ) {
super("Hashtable Test");
setSize(400, 300);
setDefaultCloseOperation(EXIT_ON_CLOSE);
}
public void init( ) {
Hashtable h = new Hashtable( );
// Build up the hashtable using every other entry in the String[][] as a key,
// followed by a String[] "value."
for (int i = 0; i < sampleData.length; i+=2) {
h.put(sampleData[i][0], sampleData[i + 1]);
}
tree = new JTree(h);
getContentPane( ).add(tree, BorderLayout.CENTER);
}
public static void main(String args[]) {
ObjectTree tt = new ObjectTree( );
tt.init( );
tt.setVisible(true);
}
}
17.3.5 Selection Methods
One of the primary functions the JTree class
provides is programmer access to the
selection status of the tree. (Most of
these functions work with a selection model discussed in
"Tree Selections.")
We'll say more about this later, but selections may
be based on either rows or paths. A row is a displayed element in a
tree; you refer to a row by its index. A path is a list of nodes from
the root to the selected node.
- addSelectionInterval(int row1, int row2)
-
Add the paths between row1 and
row2 to the current selection. It uses
getPathBetweenRows( ) to collect the list of paths
to add.
- public void addSelectionPath(TreePath path)
- public void addSelectionPaths(TreePath paths[])
- public void addSelectionRow(int row)
- public void addSelectionRows(int rows[])
-
Add to the current selection on the tree. If you supply an array of
paths or integers, each of the paths or rows indicated is selected.
If any path or row is not currently visible, it is made visible.
- public void clearSelection( )
-
Clear the current selection completely.
- public boolean isPathSelected(TreePath path)
-
Return true if the given path is in the current
selection. It is the TreePath equivalent of the
get method for the rowSelected property.
- public void removeSelectionInterval(int row1, int row2)
-
Remove the paths between row1 and
row2 from the current selection. It uses
getPathBetweenRows( ) to collect the list of paths
to deselect.
- public void removeSelectionPath(TreePath path)
- public void removeSelectionPaths(TreePath paths[])
- public void removeSelectionRow(int row)
- public void removeSelectionRows(int rows[])
-
Remove pieces of the current selection dictated by the rows or paths
provided as arguments. If a specified path or row is not in the
current selection, it is ignored, and any remaining rows or paths are
deselected.
- public void setSelectionInterval(int row1, int row2)
-
Set the current selection to represent the paths between
row1 and row2. It uses
getPathBetweenRows( ) to collect the list of paths
to select.
- public void setSelectionPath(TreePath path)
- public void setSelectionPaths(TreePath paths[])
- public void setSelectionRow(int row)
- public void setSelectionRows(int rows[])
-
Set the current selection on the tree. If you supply an array of
paths or integers, each of the indicated paths or rows is selected.
If any path or row is not currently visible, it is made visible.
17.3.6 Expansion Methods
For
any entry in your tree, you can check to
see if it is currently expanded or collapsed. A node is considered
expanded if the nodes in its path are also expanded. (This applies to
leaves as well.) You can also programmatically control the collapsing
and expanding of parts of your tree. All of the following methods
accept either a TreePath or a
row (int) argument.
- public void collapsePath(TreePath path)
- public void collapseRow(int row)
-
Collapse the given path or row if needed. (In the case of the path
argument, the last component of the path is collapsed.) Once
collapsed, it tries to make the path visible as well.
- public void expandPath(TreePath path)
- public void expandRow(int row)
-
Expand the given path or row if needed. Once expanded, it tries to
make the path visible as well.
- public boolean isCollapsed(int row)
- public boolean isCollapsed(TreePath path)
-
Return true if any node in the given path or row
is not currently expanded. If every node is expanded, these methods
return false.
- public boolean isExpanded(int row)
- public boolean isExpanded(TreePath path)
-
Return true if the given path or row is currently
fully expanded. If any nodes in the path are not expanded, these
methods return false.
- public boolean hasBeenExpanded(TreePath path)
-
Return true if the path has ever been expanded.
17.3.7 Path and Row Methods
- public
TreePath getClosestPathForLocation(int x, int y)
- public int getClosestRowForLocation(int x, int y)
-
Return the path or row closest to a given location
(x,y) in the component, relative to its upper-left
corner. These methods return null only if nothing
is visible. If you need to be sure that the point
(x,y) is actually inside the bounds for the path
or row returned, you need to check that yourself. The
getPathForLocation( ) and
getRowForLocation( ) methods do a basic check and
return null if the point falls outside the closest
row, if this is all you need.
- public Rectangle getPathBounds(TreePath path)
-
Return the Rectangle object that encompasses the
specified path, if that path is not currently visible. The
scrollPathToVisible( ) method calls this to show a
particular path on the screen. If the path is already visible, this
method returns null.
- public TreePath getPathForLocation(int x, int y)
-
A more restricted version of getClosestPathForLocation( ). If x or y ends up
outside the bounds of the path returned by the closest path call,
this method returns null.
- public TreePath getPathForRow(int row)
-
Return the path associated with the specified row. If
row is an invalid value (less than zero or greater
than the number of rows in the current tree) or is not currently
visible, this method returns null.
- public Rectangle getRowBounds(int row)
-
This method functions like getPathBounds( ) for
the given row.
- public int getRowForLocation(int x, int y)
-
A more restricted version of getClosestRowForLocation( ). If x or y is
outside the bounds of the row returned by the closest row call, this
method returns -1.
- public int getRowForPath(TreePath path)
-
Return the row number for the last component in
path. If any part of path is
not visible, or if path is
null, this method returns -1.
- public boolean isVisible(TreePath path)
-
Return true if the given path is currently
visible. Recall that a visible path is any path you can see in the
tree without expanding a parent node—not necessarily one that
is onscreen at the moment.
- public void makeVisible(TreePath path)
-
Make path visible if it is not already visible.
- public void scrollPathToVisible(TreePath path)
- public void scrollRowToVisible(int row)
-
If the tree is in a scrollpane, scroll the given path or row to make
it appear in the pane. A path is expanded up to its last component,
if need be, to make it visible. (By definition, rows are always
visible.) The tree must be in a scrollable environment (like a
JScrollPane or JViewport) for
this to work.
17.3.8 Editing Methods
- public void cancelEditing( )
-
Cancel editing of a tree cell. If no cell
is being edited, this method has no effect.
- public TreeNode getEditingPath( )
-
Return the path to the element in the tree currently being edited. If
the tree is not being edited, this method returns
null.
- public boolean isEditing( )
-
Return true if the current selection is being
edited.
- public boolean isPathEditable(TreePath path)
-
Return the value of the editable property for a
given path. If it returns true, the path can be
edited. The UI manager calls this method before editing a node so
that a subclass of JTree can override it and say
yes or no based on some appropriate criteria. (For example, you could
allow editing of leaves but not editing of folders.)
- public void startEditingAtPath(TreePath path)
-
Try to start editing the last element in path.
This might fail if the cell editor will not edit that element.
- public boolean stopEditing( )
-
Stop the tree from being edited. If the tree is not being edited,
this method has no effect. It returns true if the
tree was being edited and the cell editor stopped successfully. It
returns false otherwise — for example, if
the tree was not being edited or the editor could not be stopped.
17.3.9 JTree Inner Classes
- protected class JTree.AccessibleJTree
-
This class represents the accessible
implementation for JTree.
- public static class JTree.DynamicUtilTreeNode
-
Various constructors of the JTree class use this
inner class to build tree nodes out of arrays, vectors, and
hashtables.
- protected static class JTree.EmptySelectionModel
-
As its name implies, this inner class provides an implementation of
the TreeSelectionModel interface (by extending
DefaultTreeSelectionModel) that does not allow any
selections.
- protected class JTree.TreeModelHandler
-
Manage the expandedState cache by listening to
expansion and modification events coming from the model.
- protected class JTree.TreeSelectionRedirector
-
This class contains methods for redirecting the source of events.
Typically, this is done when the tree model generates an event, but
the JTree object associated with that model needs
to be listed as the source of the event.
|