- The Tree Control
- Tree Appearance
- The TreeNode Interface
- The MutableTreeNode Interface
- The DefaultMutableTreeNode Class
- The TreePath Class
- What is a Leaf?
- Tree Expansion and Traversal
- Expanding and Collapsing Nodes under Program Control
- Tree Expansion Events
- Making Nodes Visible
- Controlling Node Expansion and Collapse
- Tree Model Events
- Implementation Plan for the File System Control
- File System Tree Control Implementation
- Using the File System Tree Control
- Custom Tree Rendering and Editing
- Customizing the Default Tree Cell Renderer
- ToolTips and Renderers
- Custom Cell Editors
- Controlling Which Nodes Can Be Edited
- Controlling Editability by Subclassing JTree
- Programmatic Control of Editors
- Editing Trees with Custom User Objects
- The valueForPathChanged Method
- The Tree Implementation
- The Cell Editor
- The Cell Renderer
- Summary
The DefaultMutableTreeNode Class
The first thing we're going to do with this class is to show you how to use it to build a simple tree from scratch. DefaultMutableTreeNode has three constructors:
public DefaultMutableTreeNode() public DefaultMutableTreeNode(Object userObject) public DefaultMutableTreeNode(Object userObject, boolean allowsChildren)
The first constructor creates a node with no associated user object; you can associate one with the node later using the setUserObject method. The other two connect the node to the user object that you supply. The second constructor creates a node to which you can attach children, while the third can be used to specify that child nodes cannot be attached by supplying the third argument as false.
Using DefaultMutableTreeNode, you can create nodes for the root and for all of the data you want to represent in the tree, but how do you link them together? You could use the insert method that we saw above, but it is simpler to use the DefaultMutableTreeNode add method:
public void add(MutableTreeNode child);
This method adds the given node as a child of the node against which it is invoked and at the end of the parent's list of children. By using this method, you avoid having to keep track of how many children the parent has. This method, together with the constructors, gives us all you need to create a workable tree. To begin to create a tree, you need a root node:
DefaultMutableTreeNode rootNode = new DefaultMutableTreeNode();
Below the root node, two more nodes are going to be added, one to hold details of the Apollo lunar flights, the other with information on the manned Skylab missions. These two nodes will be given meaningful text labels:
DefaultMutableTreeNode apolloNode = new DefaultMutableTreeNode("Apollo"); DefaultMutableTreeNode skylabNode = new DefaultMutableTreeNode("Skylab");
The nodes are then added directly beneath the root node:
rootNode.add(apolloNode); rootNode.add(skylabNode);
Under each of these nodes, a further node will be added for each mission and beneath each of these a leaf node for each crew member. There's an implementation of this in the example programs that you can run using the command:
java JFCBook.Chapter10.TreeExample1
The result of running this example is shown in Figure 108.
Figure 108 A tree built using DefaultMutableTreeNodes.
This program shows a root folder with no associated label and nodes labeled Apollo and Skylab. Clicking on the expansion icons of either of these opens it to show the numbered missions, and clicking on any of these shows the crew for that flight. Let's look at an extract from the source of this example:
import javax.swing.*; import javax.swing.tree.*; public class TreeExample1 extends JTree { public TreeExample1() { DefaultMutableTreeNode rootNode = new DefaultMutableTreeNode(); DefaultMutableTreeNode apolloNode = new DefaultMutableTreeNode("Apollo"); rootNode.add(apolloNode); DefaultMutableTreeNode skylabNode = new DefaultMutableTreeNode("Skylab"); rootNode.add(skylabNode); // CODE OMITTED this.setModel(new DefaultTreeModel(rootNode)); } public static void main(String[] args) { JFrame f = new JFrame("Tree Example 1"); TreeExample1 t = new TreeExample1(); t.putClientProperty("JTree.lineStyle", "Angled"); t.expandRow(0); f.getContentPane().add(new JScrollPane(t)); f.setSize(300, 300); f.setVisible(true); } }
This class is defined as an extension of JTree, which allows the creation of its data to be encapsulated within it. The root node and all of the child nodes are created and a tree structure is built from the nodes as described earlier. The JTree needs a data model in order to display anything, so the last step of the constructor is to install a model that contains the structure that has just been created:
this.setModel(new DefaultTreeModel(rootNode));
This creates a new DefaultTreeModel and initializes it with our root node, then uses the JTree setModel method to associate the data model with the tree. Since our class is derived from JTree, its default constructor will have been invoked at the start of our constructor. As noted earlier, this creates a tree with a model containing dummy data. When setModel is called at the end of the constructor, this data is overwritten with the real data.
Another way to create a JTree is to directly pass it the root node. If you use this method, it creates a DefaultTreeModel of its own and wraps it around the node that you pass to its constructor. Here's a short example of that:
DefaultMutableTreeNode rootNode = new DefaultMutableTreeNode(); DefaultMutableTreeNode apolloNode = new DefaultMutableTreeNode("Apollo"); DefaultMutableTreeNode skylabNode = new DefaultMutableTreeNode("Skylab"); rootNode.add(apolloNode); rootnode.add(skylabNode); JTree t = new JTree(rootNode);
If you look at the main method in the code extract shown above, you'll notice the following line after the tree was created:
t.expandRow(0);
This line uses the expandRow method to ensure that row 0 of the tree is expanded to display the children that the node on that row contains. In fact, this line is redundant in this example because the root node is expanded by default. You can force a node to be shown in an unexpanded state by calling the collapseRow method, which also requires the index of the row within the tree. We'll say more about the methods that can be used to expand and collapse parts of the tree later in this chapter.
Apart from when you create it, the JTree control doesn't deal with nodes directly. Instead, you can address items in the tree and obtain information about them using either their TreePath or their row number. Let's look at the row number first. The row number refers to the number of the row on the screen at which the node in question appears. There is only one node ever on any row, so specifying the row identifies a node without any ambiguity. Furthermore, provided it's actually displayed, row 0 is always occupied by the root node. The problem with using row numbers is that the row numbers for all of the nodes apart from the root node change as nodes are opened or closed. When you start TreeExample1, the root node is on row 0, the "Apollo" node on row 1, and the "Skylab" node occupies row 2. However, if you click on the expansion icon for "Apollo," the "Skylab" node moves downward and, in this case, becomes row number 9, because the "Apollo" node opens to show seven child nodes, which will occupy rows 2 through 8. Because keeping track of row numbers is not very convenient, it is more usual to address the content of a tree using TreePath objects.