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 JTree
running in a Swing application that we’ll describe a
little later.
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);
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.
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( )); } });
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.