Richard G Baldwin (512) 223-4758, baldwin@austin.cc.tx.us, http://www2.austin.cc.tx.us/baldwin/

Swing, Creating and Using Trees

Java Programming, Lecture Notes # 190, Revised 08/22/99.


Preface

Students in Prof. Baldwin's Advanced Java Programming classes at ACC will be responsible for knowing and understanding all of the material in this lesson beginning with the spring semester of 1999.

This lesson was originally written on October 8, 1998, using the JDK 1.1.6 download package along with Swing 1.0.3. The purpose of this lesson is to illustrate the use of the JTree class and associated classes for creating and using simple trees.

On 3/10/99, the import directives in the sample program were modified for JDK 1.2 compatibility. The name of the modified program is Tree07A.java. The modified program was confirmed to compile and run properly under JDK 1.2.

Introduction

Swing makes it possible to create tree structures similar to Windows Explorer.  The tree can be collapsed and expanded to hide or expose inner nodes in the tree.  The look of the tree is subject to Swing's pluggable look and feel.  The sample program in this lesson uses a Motif look and feel. It can easily be changed by modifying only one statement.

A tree can be, but doesn't have to be, a very complex structure.  It is possible for the programmer to provide the data model that maintains the data for the tree in a Model-View-Controller sense.  However, a simple default model is readily available and it isn't necessary to provide your own model.  A series of subsequent lessons on using the JList class discuss these possibilities in detail.  (JList and JTree are very similar from a programming viewpoint.)

It is also possible, but not necessary, for the programmer to define how the individual nodes in the tree are to be rendered when it is necessary to redraw the tree.  This capability is also discussed in detail in subsequent lessons on both trees and lists.

The nodes in the sample program in this lesson are rendered using a default capability to label each node with a String object, specified as a parameter to the node constructor.

Several different types of events can be generated and handled when the user performs various actions on a Swing tree.  The sample program in this lesson uses this event capability to extract and display information about a leaf whenever the user selects the leaf.  When the user selects a node that is not a leaf, the event is ignored.

Overview

Swing implements a modified form of the Model-View-Controller (MVC) paradigm.  As of October 1998, most of the current literature on Swing trees presents them in this paradigm.  While in some cases, the MVC paradigm probably leads to better program design, it also leads to additional programming complexity and requires a much higher level of skill and understanding on the part of the programmer.

It is often possible to use Swing components in your programs without getting explicitly involved in the MVC paradigm.  This lesson shows you how to create and use Swing trees in an effective manner without any consideration of MVC.

Swing also makes it possible for the programmer to provide rendering specifications for the individual nodes of the tree involving, for example, mixtures of images and text.  This also leads to considerable programming complexity.  However, if you are satisfied with using simple text strings to render the nodes in accordance with a specified Programmable Look and Feel, you can avoid this complexity as well.  In fact, reduced to this level, creating and using a tree isn't much more difficult than placing a bunch of JButton objects in a container, giving them labels, and responding to action events on the buttons.  At this level, creating and using a tree is possibly simpler than creating and using a typical Swing menu structure.

In addition to the use of trees and selection listener objects, this lesson also illustrates the use of scroll panes.

Sample Program

This program illustrates the creation and use of a very simple but also very useful JTree structure.

The values of the nodes, including the leaves, are hard coded by passing a String as a parameter when the nodes are instantiated.  The parent-child relationships among the nodes are also hard coded when the tree is instantiated.

A hashtable is used to associate the values of the leaves with information about the leaves.  The values of the leaves are the keys in the hashtable.  The items of information about the leaves are the values in the hashtable.  Although it isn't illustrated by the program, it would be very easy to modify the values in the hashtable at runtime and therefore modify the information about the leaves at runtime.

The hashtable is where you would put the information that your  program would use to invoke some particular action when  the user selects a leaf.  In this sample program, the information is simply displayed.  However, the action can be just about anything that you want it to be as long as the information extracted from the hashtable is sufficient to support that action.

The program was tested using JDK 1.1.6 and Swing 1.0.3 under Win95.

Interesting Code Fragments

The program is written as a single class, containing several inner classes and several instance variables.  The first code fragments shows some of the instance variables so that you will recognize them when you see them being used later.  The comments pretty well explain the purpose of each of the instance variables so I won't elaborate on that here.
 

public class Tree07 extends JFrame{
//public class Tree07{

  //The following hashtable is used to associate tree leaf
  // values with information about the leaf.  
  Hashtable theHashtable = new Hashtable();

  //Absolute coordinate info in pixels.
  int frameWidth = 350;
  int frameHeight = 350;
  
  //The number of rows in the tree when it is fully
  // expanded
  int numberRows = 19;

  //Reference to the JTree object
  JTree tree;
   
  //The following is a reference to a panel which contains
  // the tree.
  JPanel treePanel;
  
  //The following is a reference to a class which is used
  // to cause the look & feel of the program to emulate
  // Motif.
  String plafClassName 
       = "com.sun.java.swing.plaf.motif.MotifLookAndFeel";
    
  //For future reference, three available plaf 
  // implementations are:
  //com.sun.java.swing.plaf.metal.MetalLookAndFeel
  //com.sun.java.swing.plaf.motif.MotifLookAndFeel
  //com.sun.java.swing.plaf.windows.WindowsLookAndFeel  
  
  JLabel display;//selections are displayed here

The following fragment is a simple main() method that can be used to test this program on a stand-alone basis.
 

  public static void main(String args[]) {
    new Tree07();
  }//end main

The following fragment shows the beginning of the constructor.  It also shows two of the thirteen statements that are used to populate the hashtable with leaf values and information about the leaves.  You can view the entire set of statements used to populate the hashtable in the complete listing of the program that follows later.
 

  public Tree07 () {//constructor
    theHashtable.put("Cabbage","Good in slaw");
    theHashtable.put("Squash","The yellow variety");
    //Remaining hashtable population statements omitted
    // for brevity

A little later, I will instantiate a node object for every node in the tree.  I need a set of reference variables to refer to those nodes.  The following fragment instantiates an array of reference variables of type DefaultMutableTreeNode, which is the class from which tree nodes are instantiated.  The elements in this array will be used to refer to the nodes in the tree.
 

    DefaultMutableTreeNode[] theTreeNodes = 
                    new DefaultMutableTreeNode[numberRows];

The DefaultMutableTreeNode class provides three constructors.  A description of the constructor that I elected to use is shown below.
 

DefaultMutableTreeNode(java.lang.Object userObject) 

Creates a tree node with no parent and  no children, but which allows children, and initializes it with the specified user object.

As you can see, this constructor allows the programmer to specify any object to be used as the value for the node.  I elected to use simple String objects for the values of the nodes.  You might want to experiment with other types of objects as parameters to this constructor just to see what you get.

The following fragment shows three of nineteen similar statements used to instantiate the nodes for the tree.  Each of these statements instantiates a new node object, gives it a String for a value, and assigns it to one if the reference variables in the array that was instantiated earlier.  You can view the omitted statements in the complete listing of the program later.
 

    theTreeNodes[0] = 
                 new DefaultMutableTreeNode("Sample Tree");
    theTreeNodes[1] = 
                  new DefaultMutableTreeNode("Vegetables");
    theTreeNodes[2] = 
                     new DefaultMutableTreeNode("Cabbage");
    //Repetitive statements omitted for brevity

The next fragment is quite possibly the most complicated part of the entire program.  These statements define the parent-child relationship among the nodes.  The add() method is used to cause one node to become a child of another node.  I didn't omit any of the statements from this group.  I suggest that you use a pencil and paper and see if you can sketch out the structure of the tree based on this information.  That will help you to understand how to do it yourself later.
 

    theTreeNodes[0].add(theTreeNodes[1]);
    theTreeNodes[1].add(theTreeNodes[2]);
    theTreeNodes[1].add(theTreeNodes[3]);
    theTreeNodes[1].add(theTreeNodes[4]);
    theTreeNodes[0].add(theTreeNodes[5]);
    theTreeNodes[5].add(theTreeNodes[6]);
    theTreeNodes[6].add(theTreeNodes[7]);
    theTreeNodes[6].add(theTreeNodes[8]);
    theTreeNodes[6].add(theTreeNodes[9]);
    theTreeNodes[5].add(theTreeNodes[10]);
    theTreeNodes[10].add(theTreeNodes[11]);
    theTreeNodes[10].add(theTreeNodes[12]);
    theTreeNodes[10].add(theTreeNodes[13]);
    theTreeNodes[0].add(theTreeNodes[14]);
    theTreeNodes[14].add(theTreeNodes[15]);
    theTreeNodes[14].add(theTreeNodes[16]);
    theTreeNodes[14].add(theTreeNodes[17]);
    theTreeNodes[14].add(theTreeNodes[18]);

A little later, I will discuss the root node of the tree.  If you sketched this tree properly, you will have seen that the node referenced by theTreeNodes[0] is the root of this tree.  All other nodes are descendants of this node.

As you will see shortly when I discuss the TreePanel class, this program instantiates a JTree object which constitutes the tree, places that object in a JScrollPane object to provide scrolling capability, and places the scroll pane in a JPanel object.  This results in an encapsulated tree object of type TreePanel that can be placed in any container that will accommodate a JPanel  In this case, the TreePanel is simply placed in the outer JFrame.

The next fragment shows the instantiation of the TreePanel object.  Note that the root node of the tree is passed as a parameter to the constructor.  You will see why later when I discuss the constructor for TreePanel.

    treePanel = new TreePanel(theTreeNodes[0]);

A tree wouldn't be worth much if you couldn't cause some action to happen by selecting one of its nodes.  The following fragment instantiates a TreeSelectionListener object and registers it on the tree. 

    tree.addTreeSelectionListener(new MyTreeListener());

This is standard Delegation Event Model material.  The listener defines the valueChanged() method of the TreeSelectionListener interface which is invoked whenever a different node is selected using the mouse or an arrow key.  We will see the definition of the listener class a little later.

I'm going to skip some code that:

The next fragment contains the definition of the listener class used earlier to instantiate a TreeSelectionListener object and register it on the tree.

The code uses methods of the TreeSelectionEvent object to identify the node that was selected.

If the node is not a leaf, the selection is ignored.  If it is a leaf, the value of the node is obtained and used as the key to extract a data value from the hashtable.  This value is then displayed in a JLabel object at the bottom of the frame (that we skipped over earlier).

As I mentioned, this is all pretty standard event handling material.  All that is required is to search the documentation until you find the methods that you need to accomplish your objectives.
 

  class MyTreeListener implements TreeSelectionListener{
    public void valueChanged(TreeSelectionEvent e) {
      DefaultMutableTreeNode theNode =
                    (DefaultMutableTreeNode)
                      (e.getPath().getLastPathComponent());

      if (theNode.isLeaf()){
        display.setText((String)theHashtable.get(
                                 theNode.getUserObject()));
      }//end if
    }//end valueChanged() method
  }//end inner class MyTreeListener

That brings us to the most important fragment of all:  the one that is used to instantiate the tree.  When used in this simple manner, it turns out to be surprisingly easy.  Earlier I instantiated an array of references to the nodes of the tree.  Then I used the add() method to add children to their parent and establish the structure of the tree.  One of the nodes must be the root, which is an ancestor of all other nodes.

All the JTree constructor needs to construct a tree is a reference to that root node.  Once you instantiate a tree based on the root node, the behavior of the tree that involves the ability to collapse and expand the tree or sub-trees within the tree becomes available with no additional programming effort on your part.  All you have to do at that point is to package the tree in a way that is suitable for your user.

The constructor for this class receives the root node as a parameter.  This root node is passed to a JTree constructor to construct a JTree object that is the actual tree.  The tree is placed in a JScrollPane object to provide vertical and horizontal scrolling capability.  The scroll pane is added to a JPanel object that can then be placed in any container that will accommodate a JPanel as a component.

For example, if you had some reason to do so, you could place the TreePanel object in a JButton. I've tried it, and it really works.

The Pluggable Look and Feel variable initialized earlier is used to set the look and feel of the tree to Motif.  I also provided two other possibilities earlier in the code that you can use to experiment with the look and feel by editing one statement in the code.

Finally, the code makes certain that the tree is fully expanded before terminating the constructor.
 

  class TreePanel extends JPanel{
    public TreePanel(TreeNode root){//constructor
      setLayout(new BorderLayout());

      //Create tree
      tree = new JTree(root);

      // Put the tree in a scrollable pane and add the
      // scrollable pane to the JPanel container.
      JScrollPane sp = new JScrollPane(tree);
      add(sp, BorderLayout.CENTER);
      
      //Set the look and feel to emulate something
      try{
        UIManager.setLookAndFeel(plafClassName);
      }catch(Exception ex){System.out.println(ex);}
      SwingUtilities.updateComponentTreeUI(this);

      //Expand the tree
      for(int cnt = 0; cnt < numberRows; cnt++){
        tree.expandRow(cnt);
      }//end for loop
    }//end constructor
  }//end inner class TreePanel
  //=====================================================//
}//end class Tree07

That's really all there is to creating and using simple trees in Swing. 

Program Listing

This section contains a complete listing of the program and you can view any code in that listing that was omitted from the above fragments.

/*File Tree07.java
Revised 10/08/98

This program illustrates the creation of a very simple but
also very useful JTree structure.

The values of the nodes, including the leaves are hard
coded when the nodes are instantiated.  The parent-child
relationships among the nodes are also hard coded when
the tree is instantiated.

A hashtable is used to associate the values of the leaves
with information about the leaves.  The values of the
leaves are the keys in the hashtable.  The items of 
information about the leaves are the values in the 
hashtable.  Although the program doesn't illustrate it,
it would be very practical to modify the values in the
hashtable at runtime and therefore modify the information
about the leaves at runtime.  

This is where you would put the information that your 
program would use to invoke some particular action when 
the user selects a leaf.  In this sample program, the
information is simply displayed.  However, the action
could be just about anything that you wanted as long as
the information in the value object in the hashtable
were sufficient to support that action.

Tested using JDK 1.1.6 and Swing 1.0.3 under Win95.
**********************************************************/

import java.io.*;
import java.util.*;
import java.awt.*;
import java.awt.event.*;
import com.sun.java.swing.*;
import com.sun.java.swing.tree.*;
import com.sun.java.swing.event.*;

//=======================================================//
public class Tree07 extends JFrame{
//public class Tree07{

  //The following hashtable is used to associate tree leaf
  // values with information about the leaf.  
  Hashtable theHashtable = new Hashtable();

  //Absolute coordinate info in pixels.
  int frameWidth = 350;
  int frameHeight = 350;
  
  //The number of rows in the tree when it is fully
  // expanded
  int numberRows = 19;

  //Reference to the JTree object
  JTree tree;
  
 
  //The following is a reference to a panel which contains
  // the tree.
  //TreePanel treePanel;
  JPanel treePanel;
  
  //The following is a reference to a class which is used
  // to cause the look & feel of the program to emulate
  // Motif.
  String plafClassName 
       = "com.sun.java.swing.plaf.motif.MotifLookAndFeel";
    
  /*
  For future reference, the three plaf implementations are:
  
  com.sun.java.swing.plaf.metal.MetalLookAndFeel
  com.sun.java.swing.plaf.motif.MotifLookAndFeel
  com.sun.java.swing.plaf.windows.WindowsLookAndFeel  
  */
  
  JLabel display;//selections are displayed here
  //-----------------------------------------------------//

  //The following main method can be used to test this 
  // class on a stand-alone basis.
  public static void main(String args[]) {
    //Instantiate the top-level JFrame object.
    new Tree07();
  }//end main
  //-----------------------------------------------------//

  public Tree07 () {//constructor
    
    //Association of leaf values with leaf information
    theHashtable.put("Cabbage","Good in slaw");
    theHashtable.put("Squash","The yellow variety");
    theHashtable.put("Onion","Not too hot");
    theHashtable.put("Squirrel","Likes to store nuts");
    theHashtable.put("Rabbit","Runs fast");
    theHashtable.put("Fox","Crazy like a ...");
    theHashtable.put("Horse","Fun to ride");
    theHashtable.put("Pig","Lives in mud");
    theHashtable.put("Cow","Gives us milk");
    theHashtable.put("Peach","From Georgia");
    theHashtable.put("Grape","Saueeze into wine");
    theHashtable.put("Apple","Red delicious");
    theHashtable.put("Orange","Very juicy");


    //Create an array of references to tree node
    // references.
    DefaultMutableTreeNode[] theTreeNodes = 
                    new DefaultMutableTreeNode[numberRows];

    //The following code instantiates the actual nodes,
    // gives them a String value, and assigns them to
    // the reference variables in the array of node
    // references.  
    theTreeNodes[0] = 
                 new DefaultMutableTreeNode("Sample Tree");
    theTreeNodes[1] = 
                  new DefaultMutableTreeNode("Vegetables");
    theTreeNodes[2] = 
                     new DefaultMutableTreeNode("Cabbage");
    theTreeNodes[3] = new DefaultMutableTreeNode("Squash");
    theTreeNodes[4] = new DefaultMutableTreeNode("Onion");
    theTreeNodes[5] = 
                     new DefaultMutableTreeNode("Animals");
    theTreeNodes[6] = 
                     new DefaultMutableTreeNode("Forrest");
    theTreeNodes[7] = 
                    new DefaultMutableTreeNode("Squirrel");
    theTreeNodes[8] = new DefaultMutableTreeNode("Rabbit");
    theTreeNodes[9] = new DefaultMutableTreeNode("Fox");
    theTreeNodes[10] = new DefaultMutableTreeNode("Farm");
    theTreeNodes[11] = new DefaultMutableTreeNode("Horse");
    theTreeNodes[12] = new DefaultMutableTreeNode("Pig");
    theTreeNodes[13] = new DefaultMutableTreeNode("Cow");
    theTreeNodes[14] = new DefaultMutableTreeNode("Fruit");
    theTreeNodes[15] = new DefaultMutableTreeNode("Peach");
    theTreeNodes[16] = new DefaultMutableTreeNode("Grape");
    theTreeNodes[17] = new DefaultMutableTreeNode("Apple");
    theTreeNodes[18] = 
                      new DefaultMutableTreeNode("Orange");

    //Construct the tree using the nodes created above.
    // These statements establish ParentChild relationships
    // among the nodes.  Children are added to their
    // parents.
    theTreeNodes[0].add(theTreeNodes[1]);
    theTreeNodes[1].add(theTreeNodes[2]);
    theTreeNodes[1].add(theTreeNodes[3]);
    theTreeNodes[1].add(theTreeNodes[4]);
    theTreeNodes[0].add(theTreeNodes[5]);
    theTreeNodes[5].add(theTreeNodes[6]);
    theTreeNodes[6].add(theTreeNodes[7]);
    theTreeNodes[6].add(theTreeNodes[8]);
    theTreeNodes[6].add(theTreeNodes[9]);
    theTreeNodes[5].add(theTreeNodes[10]);
    theTreeNodes[10].add(theTreeNodes[11]);
    theTreeNodes[10].add(theTreeNodes[12]);
    theTreeNodes[10].add(theTreeNodes[13]);
    theTreeNodes[0].add(theTreeNodes[14]);
    theTreeNodes[14].add(theTreeNodes[15]);
    theTreeNodes[14].add(theTreeNodes[16]);
    theTreeNodes[14].add(theTreeNodes[17]);
    theTreeNodes[14].add(theTreeNodes[18]);

    treePanel = new TreePanel(theTreeNodes[0]);

    
    //Instantiate a TreeSelectionListener object and 
    // register it to listen for events caused by the user
    // selecting a node in the tree with the mouse or the 
    // arrow keys.  The listener implements the 
    // valueChanged() method, and an event is processed 
    // each time the user selects a different node.
    tree.addTreeSelectionListener(new MyTreeListener());
  
    
    //Display the TreePanel object in the top-level frame.
    Container content = getContentPane();
    content.add(treePanel,BorderLayout.CENTER);

    //Create a JLabel object for display and put in at
    // the bottom of the frame.
    display = new JLabel("Display Selection Here");
    content.add(display,BorderLayout.SOUTH);
    
    //Set the size of the frame and make it visible.
    setSize(frameWidth, frameHeight);
    setTitle("Copyright 1998, R.G.Baldwin");
    setVisible(true);

    //An anonymous inner class to terminate the program
    // when the user clicks the close button on the frame. 
    this.addWindowListener(new WindowAdapter() {
      public void windowClosing(WindowEvent e) {
        System.exit(0);}//terminate the program
    });
    
  }//end constructor
  //=====================================================//

  //This inner class is used to instantiate a 
  // TreeSelectionListener object for registration on the
  // JTree object.  The method in this object is invoked
  // whenever the user selects a different node in the 
  // tree.

  class MyTreeListener implements TreeSelectionListener{
    public void valueChanged(TreeSelectionEvent e) {
      //Get the node that was selected.
      DefaultMutableTreeNode theNode =
                    (DefaultMutableTreeNode)
                      (e.getPath().getLastPathComponent());
    
      //If the node is a leaf, 
      if (theNode.isLeaf()) {
        
        //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%//
        //This is the place to insert any code that should
        // be executed when the user selects a node.  In
        // this sample program, the action is to simply to
        // display information about the node in a JLabel
        // at the bottom of the frame.

        display.setText((String)theHashtable.get(
                                 theNode.getUserObject()));

        //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%//
      }//end if

    }//end valueChanged() method
  }//end inner class MyTreeListener
  //=====================================================//
  
  //This inner class is used to instantiate the actual
  // JTree object and place it in a scrollable pane in a
  // JPanel container.  The JPanel can then be placed in 
  // any other container.

  class TreePanel extends JPanel {
    public TreePanel(TreeNode root){
      setLayout(new BorderLayout());

      //Create tree
      tree = new JTree(root);

      // Put the tree in a scrollable pane and add the
      // scrollable pane to the JPanel container.
      JScrollPane sp = new JScrollPane(tree);
      add(sp, BorderLayout.CENTER);
      
      //Set the look and feel to emulate something
      try{
        UIManager.setLookAndFeel(plafClassName);
      }catch(Exception ex){System.out.println(ex);}
      SwingUtilities.updateComponentTreeUI(this);

      //Expand the tree
      for(int cnt = 0; cnt < numberRows; cnt++){
        tree.expandRow(cnt);
      }//end for loop
    }//end constructor
  }//end inner class TreePanel
  //=====================================================//
}//end class Tree07

-end-