Header javaperspective.com
JavaPerspective.com  >   Advanced Tutorials  >   1. Advanced GUI Features  >   1.2. Trees

1.2. Trees
Last updated: 1 January 2013.

This tutorial will show you how to use trees in Java.

A tree is an instance of the class JTree. It is a component that displays data in expandable nodes. The following picture shows a frame containing a tree in the Mac OS X look and feel:


Tree example 1



1.2.1. How to create a tree

A node is an instance of the class DefaultMutableTreeNode. To build the tree shown above, I created a root node (Beginner tutorials) to which I added child nodes. Then I passed the root node to the JTree constructor as shown below:

import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTree;
import javax.swing.SwingUtilities;
import javax.swing.tree.DefaultMutableTreeNode;

public final class Trees extends JFrame {

   
private JTree tree;

   
public Trees(){
         
init();
          addComponents
();

          setDefaultCloseOperation
(JFrame.EXIT_ON_CLOSE);
          setVisible
(true);
   
}


   
private void addComponents() {
         
// Create the root node
         
DefaultMutableTreeNode rootNode = new DefaultMutableTreeNode("Beginner tutorials");

         
// -------------------------------------------------------------------------------------------------------------------------------------

          // Create the "What is Java" node
         
DefaultMutableTreeNode whatIsJavaNode = new DefaultMutableTreeNode("1. What is Java?");

         
// Create "The technologies" node and add it to the "What is Java" node
         
DefaultMutableTreeNode theTechnologiesNode = new DefaultMutableTreeNode("1.1. The technologies");
          whatIsJavaNode.add
(theTechnologiesNode);

         
// Create the "How does Java work" node and add it to the "What is Java" node
         
DefaultMutableTreeNode howDoesJavaWorkNode = new DefaultMutableTreeNode("1.2. How does Java work?");
          whatIsJavaNode.add
(howDoesJavaWorkNode);

         
// Add the "What is Java" node to the root node
         
rootNode.add(whatIsJavaNode);

         
// -------------------------------------------------------------------------------------------------------------------------------------

          // Create the "Getting started" node
         
DefaultMutableTreeNode gettingStartedNode = new DefaultMutableTreeNode("2. Getting started");

         
// Create the "How to install the JDK" node and add it to the "Getting started" node
         
DefaultMutableTreeNode howToInstallTheJdkNode = new DefaultMutableTreeNode("2.1. How to install the JDK");
          gettingStartedNode.add
(howToInstallTheJdkNode);

         
// Create the "Hello World program" node and add it to the "Getting started" node
         
DefaultMutableTreeNode helloWorldProgramNode = new DefaultMutableTreeNode("2.2. Hello World program");
          gettingStartedNode.add
(helloWorldProgramNode);

         
// Add the "Getting started" node to the root node
         
rootNode.add(gettingStartedNode);

         
// -------------------------------------------------------------------------------------------------------------------------------------

          // Create the "Java basics" node
         
DefaultMutableTreeNode javaBasicsNode = new DefaultMutableTreeNode("3. Java basics");

         
// Create "The Java keywords" node and add it to the "Java basics" node
         
DefaultMutableTreeNode theJavaKeywordsNode = new DefaultMutableTreeNode("3.1. The Java keywords");
          javaBasicsNode.add
(theJavaKeywordsNode);

         
// Create "The Java primitive types" node and add it to the "Java basics" node
         
DefaultMutableTreeNode theJavaPrimitiveTypesNode = new DefaultMutableTreeNode("3.2. The Java primitive types");
          javaBasicsNode.add
(theJavaPrimitiveTypesNode);

         
// Create the "Variables and blocks" node and add it to the "Java basics" node
         
DefaultMutableTreeNode variablesAndBlocksNode = new DefaultMutableTreeNode("3.3. Variables and blocks");
          javaBasicsNode.add
(variablesAndBlocksNode);

         
// Create the "Constants and enum types" node and add it to the "Java basics" node
         
DefaultMutableTreeNode constantsAndEnumTypesNode = new DefaultMutableTreeNode("3.4. Constants and enum types");
          javaBasicsNode.add
(constantsAndEnumTypesNode);

         
// Create the "Operators and expressions" node and add it to the "Java basics" node
         
DefaultMutableTreeNode operatorsAndExpressionsNode = new DefaultMutableTreeNode("3.5. Operators and expressions");
          javaBasicsNode.add
(operatorsAndExpressionsNode);

         
// Add the "Java basics" node to the root node
         
rootNode.add(javaBasicsNode);

         
// -------------------------------------------------------------------------------------------------------------------------------------

          // Create the JTree
         
tree = new JTree(rootNode);

         
// Wrap a JScrollPane around the tree
         
JScrollPane scrollPane = new JScrollPane(tree);

         
// Add the JScrollPane to the JFrame
         
add(scrollPane);
   
}


   
private void init() {
         
setTitle("Trees");
          setSize
(450, 250);
          setLocationRelativeTo
(null);
   
}


   
public static void main(String[] args){
         
SwingUtilities.invokeLater(new Runnable() {
               
public void run() {
                     
new Trees();
               
}
          })
;
   
}

}

The class DefaultMutableTreeNode takes an Object argument representing the node's data. In the code above, I passed a string argument to the DefaultMutableTreeNode constructor for each leaf node (a leaf node is a node without children). However, you can use complex objects if it suits your application. For example, you might want to use complex objects whose fields are populated with database information. In that case, you must implement each object's toString method since the value it returns is used to display the corresponding node. Here is an example of a class named MyCustomTreeNode that could be used to represent a node's data:

public final class MyCustomTreeNode {

   
private String chapter;
   
private String title;
   
private String htmlContent;


   
public MyCustomTreeNode(String chapter, String title){
         
this.chapter = chapter;
         
this.title = title;
   
}


   
public String getChapter(){
         
return chapter;
   
}

   
public void setChapter(String chapter){
         
this.chapter = chapter;
   
}

   
public String getHtmlContent(){
         
return htmlContent;
   
}

   
public void setHtmlContent(String htmlContent){
         
this.htmlContent = htmlContent;
   
}

   
public String getTitle(){
         
return title;
   
}

   
public void setTitle(String title){
         
this.title = title;
   
}

   
public String toString(){
         
return (chapter + " " + title);
   
}

}

As an example, the statement that creates the Java basics node would be the following:

DefaultMutableTreeNode javaBasicsNode = new DefaultMutableTreeNode(new MyCustomTreeNode("3.", "Java basics"));

When the tree is being displayed, the method convertValueToText provided by the class JTree is called to render each node. In fact, convertValueToText simply calls the method toString on each object that is wrapped in a DefaultMutableTreeNode instance (in this case, the method toString provided by the class MyCustomTreeNode is called). If you cannot override the toString method or if you want more control over the nodes rendering, you can subclass JTree and override its method convertValueToText for fine grained control over each node's rendering. For example, the class MyCustomTree shown below subclasses JTree and overrides its method convertValueToText so that only the leaf nodes are displayed in lower case letters, whereas the parent nodes are displayed in upper case letters.

import javax.swing.JTree;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.TreeNode;

public final class MyCustomTree extends JTree {

   
public MyCustomTree(TreeNode treeNode){
         
super(treeNode);
   
}


   
public String convertValueToText(Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus){

         
DefaultMutableTreeNode treeNode = (DefaultMutableTreeNode) value;
          Object object = treeNode.getUserObject
();

         
if(object instanceof MyCustomTreeNode){
               
MyCustomTreeNode myCustomTreeNode = (MyCustomTreeNode) object;
               
if(leaf)
                     
return (myCustomTreeNode.getChapter() + " " + myCustomTreeNode.getTitle());
               
else
                      return
(myCustomTreeNode.getChapter().toUpperCase() + " " + myCustomTreeNode.getTitle().toUpperCase());
         
}

         
return value.toString();
   
}

}

In the above example, I used only one argument (leaf) in the implementation of the method convertValueToText but you can also use the other arguments (selected, expanded, row and hasFocus) to specify how the nodes should be rendered. The statement that builds the tree is now the following:

// Create the JTree
tree = new MyCustomTree(rootNode);


1.2.2. How to customize a tree's appearance

You can customize a tree's appearance in several ways, especially if you are using the Java look and feel (L&F). It seems that the other L&Fs ignore certain types of customization. If you don't set the L&F, the Java L&F is used by default on Linux and Windows systems. On Mac systems, the native Apple L&F is used by default. To set the Java L&F programmatically, just add the following statement to your code before drawing any Swing component:

UIManager.setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName());

The class JTree provides the following methods which allow you to customize a tree's appearance: setRootVisible, setShowsRootHandles, putClientProperty and setCellRenderer.

1.2.3. How to set the selection mode

Several selection modes are available:The selection mode is set by a call to the method setSelectionMode(int mode) provided by the interface TreeSelectionModel. The possible values for the argument mode are TreeSelectionModel.SINGLE_TREE_SELECTION, TreeSelectionModel.CONTIGUOUS_TREE_SELECTION and TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION (the default).

For example, the following sample shows how to set the selection mode to single tree selection:

tree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);


1.2.4. How to listen to node selection events

When the user selects one or more nodes, a selection event is fired. You can listen to node selection events by registering a tree selection listener on the tree. Here is an example:

tree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);

tree.addTreeSelectionListener
(new TreeSelectionListener() {
   
public void valueChanged(TreeSelectionEvent e) {
         
DefaultMutableTreeNode selectedNode = (DefaultMutableTreeNode) tree.getLastSelectedPathComponent();
          Object object = selectedNode.getUserObject
();

         
if(object instanceof MyCustomTreeNode){
               
MyCustomTreeNode myCustomTreeNode = (MyCustomTreeNode) object;
                String htmlContent = myCustomTreeNode.getHtmlContent
();
               
// Do something with the content
                //...
         
}
    }
})
;

In the sample above, I have set the selection mode to SINGLE_TREE_SELECTION before registering the tree selection listener on the tree. If you leave the selection mode to its default value (DISCONTIGUOUS_TREE_SELECTION) or if you set it to CONTIGUOUS_TREE_SELECTION, the tree selection listener has to take into account multiple selection. The next sample shows how to handle selection events when multiple selection is allowed:

tree.addTreeSelectionListener(new TreeSelectionListener() {
   
public void valueChanged(TreeSelectionEvent e) {
         
TreePath[] treePaths = tree.getSelectionPaths();

         
for(TreePath treePath : treePaths){
               
DefaultMutableTreeNode selectedNode = (DefaultMutableTreeNode) treePath.getLastPathComponent();
                Object object = selectedNode.getUserObject
();

               
if(object instanceof MyCustomTreeNode){
                     
MyCustomTreeNode myCustomTreeNode = (MyCustomTreeNode) object;
                      String htmlContent = myCustomTreeNode.getHtmlContent
();
                     
// Do something with the content
                      //...
               
}
          }
    }
})
;


1.2.5. How to add, remove and edit nodes dynamically

This section explains how to add, remove and edit nodes dynamically, that is, after the tree has been displayed.

So far, to create a tree, I have used the constructor JTree(TreeNode root) wherein root is a DefaultMutableTreeNode instance. Although the class DefaultMutableTreeNode provides methods to insert and remove nodes dynamically (add, insert, remove), when those methods are called, no event is fired. If you want events to be fired when nodes are inserted or removed, you need to provide a tree model and use the constructor public JTree(TreeModel newModel) to create the tree.

A tree model is an object that implements the interface TreeModel. You can use the class DefaultTreeModel which implements the interface TreeModel or, if the class DefaultTreeModel does not meet your needs, you can implement the interface TreeModel directly. However, using DefaultTreeModel allows you to provide a tree model without having to implement each method of the interface TreeModel.

The following sample shows how to create a tree with the constructor public JTree(TreeModel newModel) where the argument newModel is an instance of the class DefaultTreeModel:

// Create the root node
DefaultMutableTreeNode rootNode = new DefaultMutableTreeNode("Beginner tutorials");

// Add children to the root node
// ...
// ...

// Create the tree model
treeModel = new DefaultTreeModel(rootNode);

// Create the JTree
tree = new JTree(treeModel);

// Make the tree nodes editable
tree.setEditable(true);

As you can see, I called the method setEditable provided by the class JTree in order to make the tree nodes editable. The class DefaultTreeModel provides the following methods to add and remove nodes dynamically:For example, here is the statement that adds a new node at the end of the Java basics node:

treeModel.insertNodeInto(new DefaultMutableTreeNode("New node"), javaBasicsNode, javaBasicsNode.getChildCount());

Additionally, if you want to listen to events that are fired when nodes are added, removed or edited, you need to register a tree model listener on the tree model. The next sample shows how to listen to node editing events:

treeModel.addTreeModelListener(new TreeModelListener() {

   
public void treeNodesChanged(TreeModelEvent e) {
         
DefaultMutableTreeNode editedNode = (DefaultMutableTreeNode) e.getTreePath().getLastPathComponent();

         
int[] childIndices = e.getChildIndices();

         
// If the edited node is not the root node, childIndices is not null. Otherwise, childIndices is null
         
if(childIndices != null)
               
editedNode = (DefaultMutableTreeNode) editedNode.getChildAt(childIndices[0]);

          System.out.println
("The new value of the node is : " + editedNode.getUserObject());
   
}

   
public void treeNodesInserted(TreeModelEvent e) {

    }

   
public void treeNodesRemoved(TreeModelEvent e) {

    }

   
public void treeStructureChanged(TreeModelEvent e) {

    }

})
;


1.2.6. How to improve performance by loading nodes lazily

In the preceding examples, all the nodes contained in the tree are provided before the tree has been displayed. If the nodes' data is read from files or if it is given by costly database requests, the tree may take too long to be initialized. To improve performance, you can display the expandable nodes without loading their children. The children will be loaded only when the user expands their parent node.

To achieve that, all you need to do is listen to TreeWillExpand events. A TreeWillExpand event is an event that is fired when an expandable node is about to be expanded or collapsed. The code below registers a TreeWillExpandListener on the tree and implements the method treeWillExpand(TreeExpansionEvent e) so that an expandable node loads its children the first time it is expanded.

import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTree;
import javax.swing.SwingUtilities;
import javax.swing.event.TreeExpansionEvent;
import javax.swing.event.TreeWillExpandListener;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.ExpandVetoException;

public final class Trees extends JFrame {

   
private JTree tree;

   
public Trees(){
         
init();
          addComponents
();

          setDefaultCloseOperation
(JFrame.EXIT_ON_CLOSE);
          setVisible
(true);
   
}


   
private void addComponents() {
         
// Create the root node
         
DefaultMutableTreeNode rootNode = new DefaultMutableTreeNode("Beginner tutorials");

         
// Create the "What is Java" node and add it to the root node
         
DefaultMutableTreeNode whatIsJavaNode = new DefaultMutableTreeNode("1. What is Java?");
          rootNode.add
(whatIsJavaNode);

         
// Create the "Getting started" node and add it to the root node
         
DefaultMutableTreeNode gettingStartedNode = new DefaultMutableTreeNode("2. Getting started");
          rootNode.add
(gettingStartedNode);

         
// Create the "Java basics" node and add it to the root node
         
DefaultMutableTreeNode javaBasicsNode = new DefaultMutableTreeNode("3. Java basics");
          rootNode.add
(javaBasicsNode);

         
// Create the JTree
         
tree = new JTree(rootNode, true);

         
// Register the TreeWillExpandListener
         
tree.addTreeWillExpandListener(new TreeWillExpandListener() {

               
public void treeWillExpand(TreeExpansionEvent e) throws ExpandVetoException {
                     
DefaultMutableTreeNode node = (DefaultMutableTreeNode) e.getPath().getLastPathComponent();
                     
if(node.getChildCount() == 0){
                           
if(node.getUserObject().toString().startsWith("1.")){
                                 
node.add(new DefaultMutableTreeNode("1.1. The technologies", false));
                                  node.add
(new DefaultMutableTreeNode("1.2. How does Java work?", false));
                           
}
                           
else if(node.getUserObject().toString().startsWith("2.")){
                                 
node.add(new DefaultMutableTreeNode("2.1. How to install the JDK", false));
                                  node.add
(new DefaultMutableTreeNode("2.2. Hello World program", false));
                           
}
                           
else if(node.getUserObject().toString().startsWith("3.")){
                                 
node.add(new DefaultMutableTreeNode("3.1. The Java keywords", false));
                                  node.add
(new DefaultMutableTreeNode("3.2. The Java primitive types", false));
                                  node.add
(new DefaultMutableTreeNode("3.3. Variables and blocks", false));
                                  node.add
(new DefaultMutableTreeNode("3.4. Constants and enum types", false));
                                  node.add
(new DefaultMutableTreeNode("3.5. Operators and expressions", false));
                           
}
                      }
                }

               
public void treeWillCollapse(TreeExpansionEvent e) throws ExpandVetoException {

                }

          })
;

         
// Wrap a JScrollPane around the tree
         
JScrollPane scrollPane = new JScrollPane(tree);

         
// Add the JScrollPane to the JFrame
         
add(scrollPane);
   
}


   
private void init() {
         
setTitle("Trees");
          setSize
(450, 250);
          setLocationRelativeTo
(null);
   
}


   
public static void main(String[] args){
         
SwingUtilities.invokeLater(new Runnable() {
               
public void run() {
                     
new Trees();
               
}
          })
;
   
}

}

You might have noticed that to create the tree, I used the constructor JTree(TreeNode root, boolean asksAllowsChildren) instead of the constructor JTree(TreeNode root) because with the latter, nodes without children are automatically displayed as leaf nodes and therefore cannot be expanded. In the code above, the call new JTree(rootNode, true) creates a tree whose nodes are displayed as leaf nodes only when specified, that is, when they are created with the constructor DefaultMutableTreeNode(Object userObject, boolean allowsChildren) wherein the value of allowsChildren is false.


You are here :  JavaPerspective.com  >   Advanced Tutorials  >   1. Advanced GUI Features  >   1.2. Trees
Next tutorial :  JavaPerspective.com  >   Advanced Tutorials  >   1. Advanced GUI Features  >   1.3. Progress bars

Copyright © 2013. JavaPerspective.com. All rights reserved.  ( Terms | Contact | About ) 
Java is a trademark of Oracle Corporation
Image 1 Image 2 Image 3 Image 4 Image 5 Image 6 Image 7