| 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. Figure 17-6. A JTree built by parsing an XML document![]() 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 PropertiesThe 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.
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 EventsThe 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.
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:
JTree also generates property change events whenever any of its bound properties are modified. 17.3.3 ConstantsThe 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.
17.3.4 Constructors
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. Figure 17-7. JTrees built from a hashtable and a DefaultTreeModel in Mac and Metal L&Fs![]() 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 MethodsOne 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.
17.3.6 Expansion MethodsFor 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.
17.3.7 Path and Row Methods
17.3.8 Editing Methods
17.3.9 JTree Inner Classes
|