Trees

One of Swing’s advanced components is JTree. Trees are good for representing hierarchical information, like the contents of a disk drive or a company’s organizational chart. As with all Swing components, the data model is distinct from the visual representation. This means you can do things like update the data model and trust that the visual component will be updated properly.

JTree is powerful and complex. It’s so complicated, in fact, that the classes that support JTree have their own package, javax.swing.tree . However, if you accept the defaults options for almost everything, JTree is very easy to use. Figure 15.6 shows a JTreerunning in a Swing application that we’ll describe a little later.

The JTree class in action

Figure 15-6. The JTree class in action

Nodes and Models

A tree’s data model is made up of interconnected nodes. A node has a name, typically, a parent, and some number of children (possibly 0). In Swing, a node is represented by the TreeNode interface. Nodes that can be modified are represented by MutableTreeNode. A concrete implementation of this interface is DefaultMutableTreeNode. One node, called the root node, usually resides at the top of the hierarchy.

A tree’s data model is represented by the TreeModel interface. Swing provides an implementation of this interface called DefaultTreeModel . You can create a DefaultTreeModel by passing a root TreeNode to its constructor.

You could create a TreeModel with just one node like this:

TreeNode root = new DefaultMutableTreeNode("Root node");
TreeModel model = new DefaultTreeModel(root);

Here’s another example with a real hierarchy. The root node contains two nodes, Node 1 and Group. The Group node contains Node 2 and Node 3 as subnodes.

MutableTreeNode root = new DefaultMutableTreeNode("Root node");
MutableTreeNode group = new DefaultMutableTreeNode("Group");
root.insert(group, 0);
root.insert(new DefaultMutableTreeNode("Node 1"), 1);
group.insert(new DefaultMutableTreeNode("Node 2"), 0);
group.insert(new DefaultMutableTreeNode("Node 3"), 1);

Once you’ve got your nodes organized, you can create a TreeModel in the same way as before:

TreeModel model = new DefaultTreeModel(root);

Save a Tree

Once you have a tree model, creating a JTree is simple:

JTree tree = new JTree(model);

The JTree behaves like a souped-up JList. As Figure 15.6 shows, the JTree automatically shows nodes with no children as a sheet of paper, while nodes that contain other nodes are shown as folders. You can expand and collapse nodes by clicking on the little knobs to the left of the folder icons. You can also expand and collapse nodes by double-clicking on them. You can select nodes; multiple selections are possible using the Shift and Ctrl keys. And, like a JList, you should put a JTree in a JScrollPane if you want it to scroll.

Tree Events

A tree fires off several flavors of events. You can find out when nodes have been expanded and collapsed, when nodes are about to be expanded or collapsed (because the user has clicked on them), and when selections occur. Three distinct event listener interfaces handle this information: TreeExpansionListener, TreeWillExpandListener, and TreeSelectionListener.

Tree selections are a tricky business. You can select any combination of nodes by using the Control key and clicking on nodes. Tree selections are described by a TreePath, which describes how to get from the root node to the selected nodes.

The following example registers an event listener that prints out the last selected node:

tree.addTreeSelectionListener(new TreeSelectionListener( ) {
  public void valueChanged(TreeSelectionEvent e) {
    TreePath tp = e.getNewLeadSelectionPath( );
    System.out.println(tp.getLastPathComponent( ));
  }
});

A Complete Example

This section contains a complete example that showcases the following tree techniques:

  • Construction of a tree model, using DefaultMutableTreeNode

  • Creation and display of a JTree

  • Listening for tree selection events

  • Modifying the tree’s data model while the JTree is showing

Here’s the source code for the example:

//file: PartsTree.java
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.tree.*;

public class PartsTree {
  public static void main(String[] args) {
    // create a hierarchy of nodes
    MutableTreeNode root = new DefaultMutableTreeNode("Parts");
    MutableTreeNode beams = new DefaultMutableTreeNode("Beams");
    MutableTreeNode gears = new DefaultMutableTreeNode("Gears");
    root.insert(beams, 0);
    root.insert(gears, 1);
    beams.insert(new DefaultMutableTreeNode("1x4 black"), 0);
    beams.insert(new DefaultMutableTreeNode("1x6 black"), 1);
    beams.insert(new DefaultMutableTreeNode("1x8 black"), 2);
    beams.insert(new DefaultMutableTreeNode("1x12 black"), 3);
    gears.insert(new DefaultMutableTreeNode("8t"), 0);
    gears.insert(new DefaultMutableTreeNode("24t"), 1);
    gears.insert(new DefaultMutableTreeNode("40t"), 2);
    gears.insert(new DefaultMutableTreeNode("worm"), 3);
    gears.insert(new DefaultMutableTreeNode("crown"), 4);
    
    // create a JFrame to hold the tree
    JFrame f = new JFrame("PartsTree v1.0");
    f.addWindowListener(new WindowAdapter( ) {
      public void windowClosing(WindowEvent e) { System.exit(0); }
    });
    f.setSize(200, 200);
    f.setLocation(200, 200);
    
    // create the JTree
    final DefaultTreeModel model = new DefaultTreeModel(root);
    final JTree tree = new JTree(model);
    
    // create a text field and button to modify the data model
    final JTextField nameField = new JTextField("16t");
    final JButton button = new JButton("Add a part");
    button.setEnabled(false);
    button.addActionListener(new ActionListener( ) {
      public void actionPerformed(ActionEvent e) {
        TreePath tp = tree.getSelectionPath( );
        MutableTreeNode insertNode =
            (MutableTreeNode)tp.getLastPathComponent( );
        int insertIndex = 0;
        if (insertNode.getParent( ) != null) {
          MutableTreeNode parent =
              (MutableTreeNode)insertNode.getParent( );
          insertIndex = parent.getIndex(insertNode) + 1;
          insertNode = parent;
        }
        MutableTreeNode node =
            new DefaultMutableTreeNode(nameField.getText( ));
        model.insertNodeInto(node, insertNode, insertIndex);
      }
    });
    JPanel addPanel = new JPanel(new GridLayout(2, 1));
    addPanel.add(nameField);
    addPanel.add(button);
    
    // listen for selections
    tree.addTreeSelectionListener(new TreeSelectionListener( ) {
      public void valueChanged(TreeSelectionEvent e) {
        TreePath tp = e.getNewLeadSelectionPath( );
        button.setEnabled(tp != null);
      }
    });
    
    // put it all together
    f.getContentPane( ).add(new JScrollPane(tree));
    f.getContentPane( ).add(addPanel, BorderLayout.SOUTH);
    f.setVisible(true);
  }
}

The example begins by creating a node hierarchy. The root node is called Parts. It contains two subnodes, Beams and Gears, as shown:

MutableTreeNode root = new DefaultMutableTreeNode("Parts");
MutableTreeNode beams = new DefaultMutableTreeNode("Beams");
MutableTreeNode gears = new DefaultMutableTreeNode("Gears");
root.insert(beams, 0);
root.insert(gears, 1);

The Beams and Gears nodes contain a handful of items each.

The Add a part button inserts a new item into the tree at the level of the current node, and just after it. You can specify the name of the new node by typing it in the text field above the button. To determine where the node should be added, the current selection is first obtained, in the anonymous inner ActionListener:

TreePath tp = tree.getSelectionPath( );
MutableTreeNode insertNode =
  (MutableTreeNode)tp.getLastPathComponent( );

The new node should be added to the parent node of the current node, so it ends up being a sibling of the current node. The only hitch here is that if the current node is the root node, it won’t have a parent. If a parent does exist, we determine the index of the currently selected node, and then add the new node at the next index:

int insertIndex = 0;
if (insertNode.getParent( ) != null) {
  MutableTreeNode parent =
      (MutableTreeNode)insertNode.getParent( );
  insertIndex = parent.getIndex(insertNode) + 1;
  insertNode = parent;
}
MutableTreeNode node =
    new DefaultMutableTreeNode(nameField.getText( ));
model.insertNodeInto(node, insertNode, insertIndex);

You must add the new node to the tree’s data model, using insertNodeInto( ) , not to the MutableTableNode itself. The model notifies the JTree that it needs to update itself.

We have another event handler in this example, one that listens for tree selection events. Basically, we want to enable our Add a part button only if a current selection exists:

tree.addTreeSelectionListener(new TreeSelectionListener( ) {
  public void valueChanged(TreeSelectionEvent e) {
    TreePath tp = e.getNewLeadSelectionPath( );
    button.setEnabled(tp != null);
  }
});

When you first start this application, the button is disabled. As soon as you select something, it is enabled and you can add nodes to the tree with abandon. If you want to see the button disabled again, you can unselect everything by holding the Control key and clicking on the current selection.

Get Learning Java now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.