< Day Day Up > |
With the JTree [201] class, you can display hierarchical data. A JTree object doesn't actually contain your data; it simply provides a view of the data. Like any nontrivial Swing component, the tree gets data by querying its data model. Figure 91 is a picture of a tree.
Figure 91. A tree in the Java look and feel.
As the figure shows, JTree displays its data vertically. Each row displayed by the tree contains exactly one item of data, which is called a node . Every tree has a root node from which all nodes descend. By default, the tree displays the root node, but you can decree otherwise . A node can either have children or not. We refer to nodes that can have childrenwhether or not they currently have childrenas branch nodes. Nodes that can't have children are leaf nodes. Branch nodes can have any number of children. Typically, the user can expand and collapse branch nodesmaking their children visible or invisibleby clicking them. By default, all branch nodes except the root node start out collapsed . A program can detect changes in branch nodes' expansion state by listening for tree expansion or tree-will-expand events, as described in How to Write a Tree Expansion Listener (page 710) and How to Write a Tree-Will-Expand Listener (page 718) in Chapter 10. Creating a TreeFigure 92 is a picture of an application, the top half of which displays a tree in a scroll pane. Figure 92. The TreeDemo application.
Try This:
The following code, taken from TreeDemo.java , creates the JTree object and puts it in a scroll pane: //Where instance variables are declared: private JTree tree; ... public TreeDemo() { ... DefaultMutableTreeNode top = new DefaultMutableTreeNode("The Java Series"); createNodes(top); tree = new JTree(top); ... JScrollPane treeView = new JScrollPane(tree); ... } The code creates an instance of DefaultMutableTreeNode [203] to serve as the root node for the tree. It then creates the rest of the nodes in the tree. After that, it creates the tree, specifying the root node as an argument to the JTree constructor. Finally, it puts the tree in a scroll pane, a common tactic because showing the full, expanded tree would otherwise require too much space.
Here's the code that creates the nodes under the root node: private void createNodes(DefaultMutableTreeNode top) { DefaultMutableTreeNode category = null; DefaultMutableTreeNode book = null; category = new DefaultMutableTreeNode("Books for Java Programmers"); top.add(category); //original Tutorial book = new DefaultMutableTreeNode(new BookInfo ("The Java Tutorial: A Short Course on the Basics", "tutorial.html")); category.add(book); //Tutorial Continued book = new DefaultMutableTreeNode(new BookInfo ("The Java Tutorial Continued: The Rest of the JDK", "tutorialcont.html")); category.add(book); //JFC Swing Tutorial book = new DefaultMutableTreeNode(new BookInfo ("The JFC Swing Tutorial: A Guide to Constructing GUIs", "swingtutorial.html")); category.add(book); //...add more books for programmers... category = new DefaultMutableTreeNode( "Books for Java Implementers"); top.add(category); //VM book = new DefaultMutableTreeNode(new BookInfo ("The Java Virtual Machine Specification", "vm.html")); category.add(book); //Language Spec book = new DefaultMutableTreeNode(new BookInfo ("The Java Language Specification", "jls.html")); category.add(book); } The argument to the DefaultMutableTreeNode constructor is the user object an object that contains or points to the data associated with the tree node. The user object can be a string, or it can be a custom object. If you implement a custom object, you should implement its toString method so that it returns the string to be displayed for that node. For example, the BookInfo class used in the previous code snippet is a custom class that holds two pieces of data: the name of a book and the URL for an HTML file describing the book. The toString method is implemented to return the book name. Thus, each node associated with a BookInfo object displays a book name. Note: You can specify text formatting in a tree node by putting HTML tags in the string for the node. See Using HTML in Swing Components (page 43) in Chapter 3 for details. To summarize, you can create a tree by invoking the JTree constructor, specifying the root node as an argument. You should probably put the tree inside a scroll pane so that it won't take up too much space. You don't have to do anything to make the tree nodes expand and collapse in response to user clicks. However, you do have to add some code to make the tree respond when the user selects a nodeby clicking the node, for example. Responding to Node SelectionResponding to tree node selections is simple. You implement a tree selection listener and register it on the tree. Here's the selection- related code from the TreeDemo program: //Where the tree is initialized: tree.getSelectionModel().setSelectionMode (TreeSelectionModel.SINGLE_TREE_SELECTION); //Listen for when the selection changes. tree.addTreeSelectionListener(this); ... public void valueChanged(TreeSelectionEvent e) { DefaultMutableTreeNode node = (DefaultMutableTreeNode) tree.getLastSelectedPathComponent(); if (node == null) return; Object nodeInfo = node.getUserObject(); if (node.isLeaf()) { BookInfo book = (BookInfo)nodeInfo; displayURL(book.bookURL); } else { displayURL(helpURL); } } The preceding code performs these tasks :
For more details about handling tree selection events, see How to Write a Tree Selection Listener (page 715) in Chapter 10. Customizing a Tree's DisplayFigure 93 is a picture of some tree nodes, as drawn by the Java, Windows, and GTK+ look and feel implementations . Figure 93. Tree nodes with the (a) Java, (b) Windows, and (c) GTK+ look-and-feel implementations.
A tree conventionally displays an icon and some text for each node. You can customize these, as we'll show shortly. A tree typically also performs some look-and-feel-specific painting to indicate relationships between nodes. You can customize this painting in a limited way. First, you can use tree.setRootVisible(true) to show the root node or tree.setRootVisible(false) to hide it. Second, you can use tree.setShowsRoot- Handles(true) to request that a tree's top-level nodesthe root node (if it's visible) or its children (if not)have handles that let them be expanded or collapsed. Third, if you're using the Java look and feel, you can customize whether lines are drawn to show relationships between tree nodes. By default, the Java look and feel draws angled lines between nodes. By setting the JTree.lineStyle client property of a tree, you can specify a different convention. For example, to request that the Java look and feel use only horizontal lines to group nodes, use the following code: tree.putClientProperty("JTree.lineStyle", "Horizontal"); To specify that the Java look and feel should draw no lines, use this code: tree.putClientProperty("JTree.lineStyle", "None"); The snapshots in Figure 94 show the results of setting the JTree.lineStyle property when using the Java look and feel. Figure 94. The JTree.lineStyle property can be set to (a) Angled (default), (b) Horizontal , and (c) None .
No matter what the look and feel, the default icon displayed by a node is determined by whether the node is a leaf and, if not, whether it's expanded. For example, in the Windows and Motif look-and-feel implementations, the default icon for each leaf node is a dot; in the Java look and feel, the default leaf icon is a paper-like symbol. In all the look-and-feel implementations we've shown, branch nodes are marked with folder-like symbols. Some look and feels might have different icons for expanded branches versus collapsed branches. You can easily change the default icon used for leaf, expanded branch, or collapsed branch nodes. To do so, first create an instance of DefaultTreeCellRenderer . [206] Next , specify the icons to use by invoking one or more of these renderer methods : setLeafIcon (for leaf nodes), setOpenIcon (for expanded branch nodes), setClosedIcon (for collapsed branch nodes). If you want the tree to display no icon for a type of node, then specify null for the icon. Once you've set up the icons, use the tree's setCellRenderer method to specify that the DefaultTreeCellRenderer paint its nodes. Here's an example, taken from an application called TreeIconDemo :
ImageIcon leafIcon = createImageIcon("images/middle.gif"); if (leafIcon != null) { DefaultTreeCellRenderer renderer = new DefaultTreeCellRenderer(); renderer.setLeafIcon(leafIcon); tree.setCellRenderer(renderer); }
Figure 95. The TreeIconDemo application.
If you want finer control over the node icons or you want to provide tool tips, you can do so by creating a subclass of DefaultTreeCellRenderer and overriding the getTreeCellRendererComponent method. Because DefaultTreeCellRenderer is a subclass of JLabel , you can use any JLabel methodsuch as setIcon to customize the DefaultTreeCellRenderer .
//...where the tree is initialized: //Enable tool tips. ToolTipManager.sharedInstance().registerComponent(tree); ImageIcon tutorialIcon = createImageIcon("images/middle.gif"); if (tutorialIcon != null) { tree.setCellRenderer(new MyRenderer(tutorialIcon)); } ... class MyRenderer extends DefaultTreeCellRenderer { Icon tutorialIcon; public MyRenderer(Icon icon) { tutorialIcon = icon; } public Component getTreeCellRendererComponent( JTree tree, Object value, boolean sel, boolean expanded, boolean leaf, int row, boolean hasFocus) { super.getTreeCellRendererComponent( tree, value, sel, expanded, leaf, row, hasFocus); if (leaf && isTutorialBook(value)) { setIcon(tutorialIcon); setToolTipText("This book is in the Tutorial series."); } else { setToolTipText(null); //no tool tip } return this; } protected boolean isTutorialBook(Object value) { DefaultMutableTreeNode node = (DefaultMutableTreeNode)value; BookInfo nodeInfo = (BookInfo)(node.getUserObject()); String title = nodeInfo.bookName; if (title.indexOf("Tutorial") >= 0) { return true; } return false; } } The result is as shown in Figure 96. Figure 96. The TreeIconDemo2 application.
You might be wondering how a cell renderer works. When a tree paints each node, neither the JTree nor its look-and-feel-specific implementation actually contains the code that paints the node. Instead, the tree uses the cell renderer's painting code to paint the node. For example, to paint a leaf node that has the string "The Java Programming Language," the tree asks its cell renderer to return a component that can paint a leaf node with that string. If the cell renderer is a DefaultTreeCellRenderer , then it returns a label that paints the default leaf icon followed by the string. A cell renderer only paints; it cannot handle events. If you want to add event handling to a tree, you need to register your handler on either the tree or, if the handling occurs only when a node is selected, the tree's cell editor . For information about cell editors, see Concepts: Editors and Renderers (page 398). That section discusses table cell editors and renderers, which are similar to tree cell editors and renderers. Dynamically Changing a TreeFigure 97 shows an application called DynamicTreeDemo that lets you add nodes to and remove nodes from a visible tree. You can also edit the text in each node. Figure 97. The DynamicTreeDemo application.
rootNode = new DefaultMutableTreeNode("Root Node"); treeModel = new DefaultTreeModel(rootNode); treeModel.addTreeModelListener(new MyTreeModelListener()); tree = new JTree(treeModel); tree.setEditable(true); tree.getSelectionModel().setSelectionMode (TreeSelectionModel.SINGLE_TREE_SELECTION); tree.setShowsRootHandles(true); By explicitly creating the tree's model, the code guarantees that it is an instance of DefaultTreeModel . [210] That way, we know all the methods that the tree model supports. For example, we know that we can invoke the model's insertNodeInto method, even though that method is not required by the TreeModel [211] interface.
To make the text in the tree's nodes editable, we invoke setEditable(true) on the tree. When the user has finished editing a node, the model generates a tree model event that tells any listenersincluding the JTree that tree nodes have changed. Note that although DefaultMutableTreeNode has methods for changing a node's content, changes should go through the DefaultTreeModel cover methods. Otherwise, the tree model events won't be generated, and listeners such as the tree won't know about the updates. To be notified of node changes, we can implement a TreeModelListener . [212] Here's an example of a tree model listener that detects when the user has typed in a new name for a tree node:
class MyTreeModelListener implements TreeModelListener { public void treeNodesChanged(TreeModelEvent e) { DefaultMutableTreeNode node; node = (DefaultMutableTreeNode) (e.getTreePath().getLastPathComponent()); /* * If the event lists children, then the changed * node is the child of the node we've already * gotten. Otherwise, the changed node and the * specified node are the same. */ try { int index = e.getChildIndices()[0]; node = (DefaultMutableTreeNode) (node.getChildAt(index)); } catch (NullPointerException exc) {} System.out.println("The user has finished editing the node."); System.out.println("New value: " + node.getUserObject()); } public void treeNodesInserted(TreeModelEvent e) { } public void treeNodesRemoved(TreeModelEvent e) { } public void treeStructureChanged(TreeModelEvent e) { } } Here's the code that the Add button's event handler uses to add a new node to the tree: treePanel.addObject("New Node " + newNodeSuffix++); ... public DefaultMutableTreeNode addObject(Object child) { DefaultMutableTreeNode parentNode = null; TreePath parentPath = tree.getSelectionPath(); if (parentPath == null) { //There's no selection. Default to the root node. parentNode = rootNode; } else { parentNode = (DefaultMutableTreeNode) (parentPath.getLastPathComponent()); } return addObject(parentNode, child, true); } ... public DefaultMutableTreeNode addObject(DefaultMutableTreeNode parent, Object child, boolean shouldBeVisible) { DefaultMutableTreeNode childNode = new DefaultMutableTreeNode(child); ... treeModel.insertNodeInto(childNode, parent, parent.getChildCount()); //Make sure the user can see the lovely new node. if (shouldBeVisible) { tree.scrollPathToVisible(new TreePath(childNode.getPath())); } return childNode; } The code creates a node, inserts it into the tree model, and then, if appropriate, requests that the nodes above it be expanded and the tree scrolled so that the new node is visible. To insert the node into the model, the code uses the insertNodeInto method provided by the DefaultTreeModel class. Creating a Data ModelIf DefaultTreeModel doesn't suit your needs, then you'll need to write a custom data model. Your data model must implement the TreeModel interface. TreeModel specifies methods for getting a particular node of the tree, getting the number of children of a particular node, determining whether a node is a leaf, notifying the model of a change in the tree, and adding and removing tree model listeners. Interestingly, the TreeModel interface accepts any kind of object as a tree node. It doesn't require that nodes be represented by DefaultMutableTreeNode objects, or even that nodes implement the TreeNode [213] interface. Thus, if the TreeNode interface isn't suitable for your tree model, feel free to devise your own representation for tree nodes. For example, if you have a pre-existing hierarchical data structure, you don't need to duplicate it or force it into the TreeNode mold. You just need to implement your tree model so that it uses the information in the existing data structure.
Figure 98. The GenealogyExample application.
You can find the custom tree model implementation in GenealogyModel.java . Because the model is implemented as an Object subclass instead of, say, a subclass of DefaultTreeModel , it must implement the TreeModel interface directly. This requires implementing methods for getting information about nodes, such as which is the root and what are the children of a particular node. In the case of GenealogyModel , each node is represented by an object of type Person , a custom class that doesn't implement TreeNode . A tree model must also implement methods for adding and removing tree model listeners and must fire TreeModelEvent s to those listeners when the tree's structure or data changes. For example, when the user instructs GenealogyExample to switch from showing ancestors to showing descendants, the tree model makes the change and then fires an event to inform its listeners (such as the tree component). The Tree APIThe tree API is quite extensive . Tables 101 through 104 list just a bit of the API. For more information about the tree API, see the API documentation for JTree [215] and for the various classes and interfaces in the tree package. [216] Also refer to The JComponent Class (page 53) in Chapter 3 for information on the API that JTree inherits from its superclass.
Table 101. Tree-Related Classes and Interfaces
Table 102. Creating and Setting up a Tree
Table 103. Implementing Selection
Table 104. Showing and Hiding Nodes
Examples That Use TreesThis table lists examples that use JTree and where those examples are described.
|
< Day Day Up > |