HomeDigital EditionSys-Con RadioSearch Java Cd
Advanced Java AWT Book Reviews/Excerpts Client Server Corba Editorials Embedded Java Enterprise Java IDE's Industry Watch Integration Interviews Java Applet Java & Databases Java & Web Services Java Fundamentals Java Native Interface Java Servlets Java Beans J2ME Libraries .NET Object Orientation Observations/IMHO Product Reviews Scalability & Performance Security Server Side Source Code Straight Talking Swing Threads Using Java with others Wireless XML
 

Since the introduction of the Java Foundation Classes (JFC), Java applications have been able to be implemented using a rich set of window components. These components - called Swing - along with customizable "look and feel," allow applications to be implemented without relying on a native windowing system. With the release of Java 2 (a.k.a. JDK 1.2), the JFC has found a permanent home as part of the JDK rather than being distributed separately. Swing includes two very powerful but complex components called JTable and JTree. This article focuses solely on the JTree and explores various aspects of the JTree by using two examples that show how business objects can be visually represented within the JTree component.

Using the Model-View-Controller (MVC) pattern, originally from Smalltalk, the majority of the Swing classes are implemented by using a variation of the MVC that collapses the view and controller into a single class called the delegate. (For more details on this topic, you can visit an online Swing tutorial, "What is Swing? - 2"). A working understanding of the model/delegate relationship will help you understand the classes and interfaces that accompany each of the Swing components.

As stated by Sun, "With the JTree class, you can display hierarchical data. JTree doesn't actually contain your data; it's simply a view of the data." The challenge comes from associating business objects with a corresponding Swing object. So how do you represent your current business objects in a JTree without altering your class definition of the business objects?

This article covers two alternatives. In both examples the objectives are to minimize (1) the coupling between the business objects and the JTree, and (2) the amount of effort and code to accomplish the tasks. In both examples I use a class called Vehicle that represents any business object and that can be displayed in hierarchical fashion.

Since the JTree is designed for displaying data with hierarchical properties, the only requirement is that there be methods within the business object's class definition to implement navigation within a hierarchical structure. In the examples, the Vehicle class has subtypes that provide a hierarchical structure. For example, one instance of the Vehicle class might be called "Motor Vehicle." The class could contain subtypes of Car, Truck and Van. These vehicles, while separate, share the commonality of being a motor vehicle.

Throughout this article I use terminology common to the JTree API and its associated classes and interfaces. The terminology is defined for you in the JTree Terminology table.

One aspect of the JTree that makes it more complex than other Swing classes is that the associated model in the model-delegate pattern for the JTree isn't where data is maintained. Take the JTextField class, for example. In JTextFields, the view (JTextArea) offers a setText(String) method, and its associated model (called a document) offers an insertString(int, String, AttributeSet). Both methods allow manipulation of the underlying data. In the case of the JTree, neither the JTree class nor its associated model interface - the javax.swing.tree.TreeModel - offers a means of manipulating the underlying data.

Another aspect of the JTree is the add(Component) method, which comes from being a subclass of the Container class. This method, however, is used for performing additions in the sense of containment (i.e., JPanels are commonly used to contain several other components by adding them to the JPanel), not for adding data to the JTree.

In the first example - AddData_ExampleA.java (see Listing 1) - associating a business object's data to the JTree is done using the default helper classes: javax.swing.tree.DefaultTreeModel and javax.swing.tree.DefaultMutableTreeNode. The default model class consists of the methods insertNodeInto(MutableTree-Node, MutableTreeNode, int) and removeNodeFromParent(MutableTree-Node).

These methods allow the addition and removal of nodes from the JTree. Since my business object, the Vehicle class, doesn't implement the MutableTreeNode interface, it can't be directly added to the JTree. Therefore, to make it a valid MutableTreeNode without altering the class definition, I "wrap" the Vehicle class using the DefaultMutableTreeNode.

JTree Terminology

Node: Any position within the JTree where data associated with the business object is being represented.
Path: A collection of a contiguous set of nodes. A path can contain one or many nodes. A null path indicates a zero node path or an empty path. The collection of nodes will consist of a strict ancestry line. (If you think of a traditional organizational chart as a tree, then an example of a path would be the line drawn from you to the president or CEO.)
Leaf: A special kind of node. As its name implies, this is the node at the end of a path. There are no more nodes connected to the leaf node. (Using the organizational chart example again, the leaf is the person that has no personnel reporting to him or her.)
Root: A special kind of node. In comparison to a leaf, a root's parent information is never examined. It's the highest point within the hierarchy. A root's parent relationship either does not exist or doesn't need to be displayed.
Parent: Represents a node's relationship with another node. In a parent/child relationship, the parent is analogous to a super class within the realms of object-oriented concepts.
Child: Represents a node's relationship with another node. In a parent/child relationship, the child is analogous to a subclass of its parent. It inherits all the properties associated with its parent. (Note: As of JDK 1.2/Swing 1.1, a node can have only one parent.)

User Object: Refers to the business object associated with a node. While not required, all user objects will usually be of the same class type. (In the examples provided, the Vehicle class is used to represent the business object.)
Editor: This is a component (usually an extension of a JComponent) that has the unique role of allowing the user to change the data of a specific node.
Renderer: This is a component (usually an extension of a JComponent) that has the unique role of deciding how a node's data is to be displayed within the context of the JTree when a user isn't editing the data. (Note: Using an AWT component as an editor or renderer may generate unwanted results.)
See http://java.sun.com/products/jfc/tsc/index.html
TreeModelEvents: Swing provides the following three types of tree events:
1. Expansion event - an event generated when a node is collapsed or expanded.
2. Model events - there are four types of model events:
a. node changed - generated after a node is changed. This is the only event the TreeModel interface supports with the method valueForPathChanged(TreePath path, Object newValue). While this method could be implemented to represent any of the four types of model events, typically this represents the node changed event, and the DefaultTreeModel class implements it as such.
b. node inserted - generated when a node is inserted into the JTree.
c. node removed - generated when a node is removed from the JTree.
d. structure changed - a "catchall" event used when something drastic has happened to the structure of the JTree. It's the most expensive event as it may result in a repaint of the entire JTree.
3. Selection event - an event generated when the selection of a node takes place.

Here are the steps performed within the code in AddData_ExampleA.java:

  1. Obtain a reference to a user object. An instance of the user object is created. In this example, the Vehicle class is the user object:
    Vehicle vObj = new Vehicle("Transportation Vehicles");

  2. Create an instance of TreeNode. An instance of the DefaultMutableTreeNode class that implements the MutableTree-Node interface (a subinterface of Tree-Node) is created using the instance of the user object created in step 1. (The second argument indicates whether the node will allow children to be added to it. In this example, I want to allow children so I pass in the value true.)
    DefaultMutableTreeNode tRoot = new DefaultMutableTreeNode(vObj, true);

  3. Create an instance of TreeModel. The DefaultTreeModel class implements the TreeModel interface and can be created using the TreeNode object that was created in step 2 as its constructor's argument:
    i_model = new DefaultTreeModel( tRoot );

  4. Create an instance of the JTree. The JTree is created using the TreeModel object that was created in step 3:
    i_tree = new JTree( i_model );

  5. Create a child TreeNode. When the user clicks the add button, another instance of the DefaultMutableTreeNode is created using another instance of the Vehicle class with the name of "Car":
    i_car = new Vehicle( "Car" ); i_carNode = new DefaultMutableTreeNode( i_car );

  6. Add child node to the JTree. The method insertNodeInto(MutableTreeNode, MutableTreeNode, int) from the DefaultTreeModel class is invoked on the TreeModel that was created in step 3. There are three arguments. The first argument consists of using the instance of the DefaultMutableTreeNode that was created in step 5. The second argument calls for the parent of the object being inserted, which in this case is the root. To obtain the root, the TreeModel interface provides a getRoot() method. (Note: the return type of getRoot() is Object, which requires casting the returned object to the MutableTreeNode class.) The third argument requires an int to indicate where within the children (assuming more than one child) the new node should be graphically positioned. Since there are no other children, the value used is 0:
    i_model.insertNodeInto( i_carNode, (MutableTreeNode)i_model.getRoot(), 0 );

To see this example run, compile and execute the AddData_ExampleA.java (see Listing 1) source file. Upon executing the application, the JTree is displayed showing a single node - the root (see Figure 1).

Figure 1
Figure 1:

At this point steps 1 and 2 have been completed. Once the user invokes some action that indicates adding a node to the JTree (in this example, clicking the button labeled Add Node: "Car" button) a new node is added to the tree (see Figure 2).

Figure 2
Figure 2:

To remove a node from the JTree, the DefaultTreeModel method - removeNodeFromParent(MutableTreeNode) - is called using the object created back in step 3 of the add process:

i_model.removeNodeFromParent( i_carNode );

To see this happen, click on the button labeled Remove Node: 'Car' button in the AddData_ExampleA.java program. By repeating steps 5 and 6, the program will construct the entire contents of a JTree.

While this add process is simple and easy to program, there are some shortcomings with this approach. First, it demands that the application constructing the JTree take full responsibility for constructing and maintaining all the hierarchical relationships between each node. The code to handle this can easily become too large and difficult to debug or maintain.

A second shortcoming is that the responsibility of keeping concurrent data accurate falls back on the application containing the JTree. Running the AddData_ExampleA class explains this. After you create and add the child node "Car," if the button labeled "Change Name to 'Van'" is clicked, the node that previously displayed "Car" will now display "Van." However, for the refresh to occur immediately, the following code is required:

i_model.valueForPathChanged( pathToRoot, i_car );

Another method, called valueForPathChanged
(TreePath, Object), is provided as part of the TreeModel interface (see Figure 3). However, it again requires knowing which node has changed and the path in which it resides. The reason the update isn't "free" is because Swing is still not aware of attribute changes made to the node's user object.

Figure 3
Figure 3:

A third shortcoming is with the use of the default classes that are provided by Swing. While convenient to use, it should be noted that certain limitations and costs exist. In my example, the DefaultMutableTreeNode is not a thread-safe class. Other issues relating to performance may need to be addressed when using the "default" classes in Swing.

It should also be noted that since the methods insertNodeInto() and removeNode() aren't part of the TreeModel interface, calls made to the getModel() method will require casting prior to invoking these methods. This defeats the advantage of using interfaces because if these methods were part of the TreeModel interface, then the cast to DefaultTreeModel after getModel() wouldn't be necessary.

The second example, displaying a business object's data in a JTree, is done by creating a tailored MutableTreeNode class. Writing my own MutableTreeNode class gives me a "bridge" between the user object class being displayed and the Swing MutableTreeNode interface. I use the term bridge to imply that there will be a translation between API calls invoked by one class and the appropriate methods invoked in a corresponding target class (see Figure 4).

Figure 4
Figure 4:

This allows the target class (the user object class) to be free from knowing the functionality of the calling class, and vice versa. Therefore, the Vehicle class definition (see Listing 2) isn't influenced by how Swing is implemented.

Note: It's good practice to implement the toString() method in your objects to provide a meaningful String representation of the class. The JTree uses the toString() method to determine what text to display in the TreeNodes.

Accomplishing this requires completion of the two steps listed below:
1. Create a MutableTreeNode (VehicleTreeNode) class (see Listing 3) for the Vehicle class. This class will implement the MutableTreeNode interface by invoking methods defined in the Vehicle class.
2. Update the JTree to reflect changes made to the user object.

When changes are made to the underlying object the JTree won't update its view to reflect the new value until it's prompted to do so. As discussed earlier, the method valueForPathChanged(TreePath, Object) on the TreeModel will update the JTree view. However, to invoke the method requires a reference to the TreeModel that can't be obtained from a TreeNode. Therefore, I chose to implement an event mechanism as a logical means of communicating updates made by the MutableTree-Nodes to their associated user objects. This required creating two interfaces (UpdateEventSource, UpdateEventListener) (see Listings 4 and 5) and one class (UpdateEvent) (see Listing 6) for the event.

Now to bring it all together! Following is the sequence of steps that occurs when executing the AddData_ExampleB (see Listing 7) application class:

  1. Create an instance of the root Vehicle class:
    Vehicle wrkVehicle = new Vehicle( "Vehicle" );
    wrkVehicle.setType( "Motor" );
    wrkVehicle.setDescription( "Classification for motor vehicles" );

  2. Create an instance of the VehicleTree-Node class. The argument used in its constructor is the Vehicle class that was created in step 1. The second argument is a Boolean that indicates if the node being created will allow children. In this example, the nodes will have children so the value of true is used:
    i_root = new VehicleTreeNode( wrkVehicle, true );

  3. Create an instance of a TreeModel class. By using the DefaultTreeModel class, the constructor is passed in the root node created in step 1 and true is passed in to indicate that children are allowed:
    i_model = new DefaultTreeModel(i_root, true);

  4. Create an instance of the JTree. The JTree is constructed using an instance of the DefaultTreeModel class, which is constructed using the VehicleTreeNode object that was created in step 3:
    i_tree = new JTree( i_model );

At this point the work is finished and the magic of the JTree begins (see Figure 5).

Figure 5
Figure 5:

Here's how this works: subsequent calls are made by the JTree to the TreeNode (in my example, it's the VehicleTreeNode), asking if it allows children (allowsChildren). If so, it obtains a child count (getChildCount), iterates through the list of children and sets the current node as the parent (setParent) on the child node. This will repeat for each node until the leaf node is reached (getChildCount returns 0). Actually, the default behavior is to perform these steps in a lazy fashion. Rather than take a performance hit by obtaining the entire structure of the JTree right away, nodes are displayed in a collapsed state and wait until they're expanded before completing construction of the JTree.

Adding nodes to the JTree is accomplished by invoking methods similar to those in the first example. By invoking the DefaultTreeModel's method, insertNodeInto(MutableTreeNode, MutableTreeNode, int) with a VehicleTreeNode as the required MutableTreeNode, any subtypes associated with the Vehicle will also immediately appear on the JTree. This differs from the first example in that subsequent calls to the insertNodeInto() method would be required to add the subtypes of the Vehicle to the JTree. This can be seen by running the AddData_ExampleB.java application. The Truck/Vehicle is added to the root Vehicle prior to being added to the JTree. So when the root Vehicle is added to the JTree, the child node Truck is also added to the JTree without requiring the additional call to insertNodeInto().

It's worth noting that while the MutableTreeNode interface offers methods like insert(), remove() and removeFromParent(), invoking these methods directly to alter the parent /child relationships circumvents the TreeModel. Since the TreeModel maintains the view, changes made directly to the MutableTreeNodes won't be reflected until a forced repaint occurs (resizing the window, etc.).

Summary
The first example demonstrated how data could be added to a JTree simply and easily. In the example, the handling of the details of the JTree was delegated to the Swing default classes. It's a good solution if the JTree is going to be used to display predominantly static or read-only data. However, if the JTree is to be used heavily, such as in an administration application, then the second alternative - creating a specific MutableTreeNode class to handle the translation between the graphical and data classes - may be more appropriate. It minimizes the resources necessary to perform the construction of the tree, as well as the code that needs to be written.

I hope this article assists developers who are new to Java, or to the JFC and Swing, to quickly become acclimated to the power of using a JTree to graphically represent and administer their data objects.

Resources
1. http://java.sun.com/docs/books/tutorial/uiswing/components/tree.html
2. http://java.sun.com/docs/books/tutorial/uiswing/overview/swingFeatures.html#model

About the Author
A graduate of Taylor University with a degree in computer science/artificial intelligence, Mark Steenbarger is a senior software engineer at Tivoli Systems, Inc. He develops Enterprise Service Management software as a member of the applications team and has been developing ESM software since 1996. Mark can be reached at [email protected]

	

Listing 1. adddata_examplea.java
 
import javax.swing.*; 
import javax.swing.tree.*; 
import java.awt.*; 
import java.awt.event.*; 
import java.util.*; 

public class AddData_ExampleA 
{ 
  private DefaultMutableTreeNode  i_carNode; 
  private DefaultTreeModel        i_model; 
  private JTree                   i_tree; 
  private JButton                 i_add; 
  private JButton                 i_remove; 
  private JButton                 i_change; 
  private Vehicle                 i_car; 
  
  public AddData_ExampleA( JFrame frm ) 
  { 
    //Create the root 
    Vehicle vObj = new Vehicle( 
                       "Transportation Vehicles"); 
    DefaultMutableTreeNode tRoot = 
      new DefaultMutableTreeNode(vObj, true); 
    i_model = new DefaultTreeModel( tRoot ); 
    //Create the tree using the root. 
    i_tree = new JTree( i_model ); 

    //Used to give desireable look to the JTree 
    i_tree.putClientProperty("JTree.lineStyle", "Angled"); 

    i_tree.setShowsRootHandles( true ); 
    frm.getContentPane().add(i_tree, 
      BorderLayout.CENTER ); 

    i_add = new JButton("Add Node: 'Car'"); 
    AddDataActionListener al = 
      new AddDataActionListener(); 
    i_add.addActionListener( al ); 

    i_remove = new JButton( "Remove Node: 'Car'"); 
    i_remove.addActionListener( al ); 
    i_change = new JButton("Change Name to 'Van'"); 
    i_change.addActionListener( al ); 

    JPanel buttonPanel = 
      new JPanel( new FlowLayout() ); 
    buttonPanel.add( i_add ); 
    buttonPanel.add( i_remove ); 
    buttonPanel.add( i_change ); 
    frm.getContentPane().add( 
      buttonPanel, BorderLayout.SOUTH ); 
  } 

  public class AddDataActionListener 
    implements ActionListener 
  { 
    public void actionPerformed( ActionEvent aeV ) 
    { 
     Object source = aeV.getSource(); 
     if( source == i_add ) { 
        if( i_carNode == null ) { 
          i_car = new Vehicle( "Car" ); 
          i_carNode = new DefaultMutableTreeNode( 
            i_car ); 

          //Using the DefaultTreeModel's 
          //insertNodeInto() method. 
          i_model.insertNodeInto( i_carNode, 
            (MutableTreeNode)i_model.getRoot(),0); 

          //method is used to expose the new node 
          //that was just added. 
          i_tree.makeVisible( new TreePath( 
            i_model.getPathToRoot( i_carNode )) ); 
        } 
      } 
      else if( (source == i_remove ) 
        && (i_carNode != null) ) { 
        i_model.removeNodeFromParent( i_carNode ); 
        i_carNode = null; 
      } 
      else if ( (source == i_change) 
        && (i_carNode != null) ) { 
        i_car.setName("Van"); 
        TreeNode[] nodesToRoot = 
          i_model.getPathToRoot( i_carNode ); 
        TreePath pathToRoot = 
          new TreePath( nodesToRoot ); 
        i_model.valueForPathChanged( 
          pathToRoot, i_car ); 
      } 
    } 
  } 

  public static void main(String[] args) { 
    JFrame f = new JFrame( 
      "Add data to the JTree - Example A"); 
    f.getContentPane().setLayout( 
      new BorderLayout() ); 
    f.addWindowListener(new WindowAdapter() { 
      public void windowClosing( WindowEvent we ) 
      { 
       System.exit(0); 
      } 
    }); 
    AddData_ExampleA example = 
      new AddData_ExampleA( f ); 
    f.pack(); 
    f.setSize( 
      new Dimension( f.getSize().width, 400 ) ); 
    f.setLocation( 200, 100 ); 
    f.setVisible(true); 
  } 
} 

Listing 2. vehicle.java
 
import java.util.*; 

public class Vehicle implements UpdateEventSource 
{ 
  String    i_name; 
  String    i_type; 
  String    i_description; 
  Vector    i_subTypes; 
  Vector    i_listeners; 

  public Vehicle(String name) 
  { 
    i_subTypes = new Vector(); 
    i_listeners = new Vector(); 
    i_name = name; 
  } 

  public void setName( String name ) 
  { 
    i_name = name; 
    fireUpdateEvent( new UpdateEvent(this,this) ); 
  } 

  public String getName() 
  { 
    return( i_name ); 
  } 

  public void setType( String type ) 
  { 
    i_type = type; 
  } 

  public String getType() 
  { 
    return( i_type ); 
  } 

  public void setDescription( String desc ) 
  { 
    i_description = desc; 
  } 

  public String getDescription() 
  { 
    return( i_description ); 
  } 

  //Using a vector for this example, could be any 
  //means of data retrieval like SQL statements, 
  //CORBA calls, etc 
  public Vector getSubTypes() 
  { 
    return( i_subTypes ); 
  } 
  
  public void addSubType( Vehicle type ) 
  { 
    i_subTypes.addElement( type ); 
  } 

  public void removeSubType( Vehicle type ) 
  { 
    i_subTypes.removeElement( type ); 
  } 

  /*********************************************** 
  * NOTE: It is good practice to override this   * 
  * method from Object. The JTree uses this to   * 
  * determine how to display text in a TreeNode  * 
  * cell.                                        * 
  *                                              * 
  * @param void                                  * 
  *                                              * 
  * @return String Contains the components       * 
  *                                              * 
  ***********************************************/ 
  public String toString() 
  { 
    return( getName() ); 
  } 

  public void fireUpdateEvent(UpdateEvent update) 
  { 
    Enumeration enumListeners = 
      i_listeners.elements(); 
    while( enumListeners.hasMoreElements() ) 
    { 
      Object nextListener = 
        enumListeners.nextElement(); 
      if( nextListener instanceof 
        UpdateEventListener ) 
      { 
        ((UpdateEventListener)nextListener). 
          valueUpdated( update ); 
      } 
    } 
  } 

  public void addUpdateEventListener( 
    UpdateEventListener listener ) 
  { 
    if( !(i_listeners.contains( listener )) ) 
    { 
      i_listeners.addElement( listener ); 
    } 
  } 

  public void removeUpdateEventListener( 
    UpdateEventListener listener ) 
  { 
    i_listeners.removeElement( listener ); 
  } 
} 

Listing 3. vehicletreenode.java
 
import javax.swing.*; 
import javax.swing.tree.*; 
import java.awt.*; 
import java.awt.event.AdjustmentEvent; 
import java.util.*; 

public class VehicleTreeNode 
  implements MutableTreeNode, UpdateEventListener, 
    UpdateEventSource 
{ 
  private Vector          i_listeners; 
  private Vector          i_myChildren; 
  private boolean         i_allowChildren; 
  private Vehicle         i_vehicle; 
  private MutableTreeNode i_parent; 

  public VehicleTreeNode( Vehicle userObj, boolean allowChildren ) 
  { 
    i_myChildren = new Vector(); 
    i_allowChildren = allowChildren; 
    i_listeners = new Vector(); 
    if( userObj instanceof Vehicle ) 
    { 
      i_vehicle = (Vehicle)userObj; 
      i_vehicle.addUpdateEventListener( this ); 
      Enumeration enumSubTypes = 
        i_vehicle.getSubTypes().elements(); 
      while( enumSubTypes.hasMoreElements() ){ 
        Vehicle nextVehicle = 
          (Vehicle)enumSubTypes.nextElement(); 
        i_myChildren.addElement( 
          new VehicleTreeNode( nextVehicle, 
            allowChildren ) ); 
      } 
    } 
  } 

  public void valueUpdated( UpdateEvent update ) 
  { 
   if( update.getSource() instanceof Vehicle ) 
    { 
      fireUpdateEvent( update ); 
    } 
  } 

  public void fireUpdateEvent(UpdateEvent update) 
  { 
    UpdateEvent event = new UpdateEvent( 
      this, update.getUpdateValue() ); 
    Enumeration enumListeners = 
      i_listeners.elements(); 
   while( enumListeners.hasMoreElements() ) 
    { 
      Object nextListener = 
        enumListeners.nextElement(); 
      if( nextListener instanceof 
        UpdateEventListener ) 
      { 
        ((UpdateEventListener)nextListener). 
          valueUpdated( event ); 
      } 
    } 
  } 

  public void addUpdateEventListener( 
    UpdateEventListener listener ) 
  { 
   if( !(i_listeners.contains( listener )) ) 
    { 
      i_listeners.addElement( listener ); 
    } 
  } 

  public void removeUpdateEventListener( 
    UpdateEventListener listener ) 
  { 
    i_listeners.removeElement( listener ); 
  } 

  //Methods to satisfy the TreeNode interface 

  //Returns an enumeration of the children 
  public Enumeration children() 
  { 
    return( i_myChildren.elements() ); 
  } 

  //Returns true if the receiver allows children. 
  public boolean getAllowsChildren() 
  { 
    return( i_allowChildren ); 
  } 

  //Returns the child TreeNode at index. 
  public TreeNode getChildAt( int index ) 
  { 
    MutableTreeNode node = (VehicleTreeNode) 
            i_myChildren.elementAt( index ); 
    node.setParent( this ); 
    return( node ); 
  } 

  //Returns the number of children. 
  public int getChildCount() 
  { 
    return( i_myChildren.size() ); 
  } 

  //Returns the index of node. 
  public int getIndex(TreeNode node) 
  { 
    return( i_myChildren.indexOf( node ) ); 
  } 

  //Returns the parent TreeNode of the receiver. 
  public TreeNode getParent() 
  { 
    return( i_parent ); 
  } 

  public boolean isLeaf() 
  { 
    return( i_myChildren.size() == 0 ); 
  } 

  //Methods to satisfy MutableTreeNode interface 

  //Adds child to the receiver at index. 
  public void insert( 
    MutableTreeNode node, int index) 
  { 
    if( i_allowChildren ) { 
      node.setParent( this ); 
      i_myChildren.insertElementAt( node, index); 
    } 
  } 

  //Removes the child at index from the receiver. 
  public void remove(int index) 
  { 
    i_myChildren.removeElementAt(index); 
  } 

  //Removes node from the receiver. 
  public void remove(MutableTreeNode node) 
  { 
    i_myChildren.removeElement( node ); 
  } 

  //Removes the receiver from its parent. 
  public void removeFromParent() 
  { 
    if( i_parent != null ) { 
      i_parent.remove( this ); 
      i_parent = null; 
    } 
  } 

  //Sets the parent of the receiver to newParent. 
  public void setParent(MutableTreeNode node) 
  { 
    i_parent = node; 
  } 

  public Vehicle getUserObject() 
  { 
    return( i_vehicle ); 
  } 

  //Resets the user object of the receiver to object 
  public void setUserObject(Object userObject) 
  { 
   if( userObject instanceof Vehicle ) 
      i_vehicle = (Vehicle)userObject; 
  } 

  public String toString() 
  { 
    return( i_vehicle.toString() ); 
  } 
} 

Listing 4. updateeventsource.java
 
/*********************************************** 
*                                              * 
* Interface defining the methods to be         * 
* implemented.                                 * 
*                                              * 
***********************************************/ 
public interface UpdateEventSource 
{ 
    public void addUpdateEventListener( 
      UpdateEventListener listener ); 

    public void removeUpdateEventListener( 
      UpdateEventListener listener ); 
} 
  
  

Listing 5. updateeventlistener.java
 
public interface UpdateEventListener 
{ 
    public void valueUpdated( UpdateEvent update); 
} 

Listing 6. updateevent.java
 
import java.util.*; 

/*********************************************** 
*                                              * 
* Event class used to communicate between the  * 
* application and the TreeNode.                * 
*                                              * 
***********************************************/ 
public class UpdateEvent extends EventObject 
{ 
  private Object i_value; 

  public UpdateEvent( 
    Object sourceObj, Object newValue ) 
  { 
    super( sourceObj ); 
    i_value = newValue; 
  } 

  public Object getUpdateValue() 
  { 
    return( i_value ); 
  } 
} 

Listing 7. adddata_exampleb.java
 
import javax.swing.*; 
import javax.swing.tree.*; 
import javax.swing.event.*; 
import java.awt.*; 
import java.awt.event.*; 
import java.util.*; 

public class AddData_ExampleB 
{ 
  private DefaultTreeModel      i_model; 
  private JTree                 i_tree; 
  private JButton               i_cmdAdd; 
  private JButton               i_cmdRemove; 
  private JButton               i_cmdUpdate; 

  private VehicleTreeNode       i_root; 
  private VehicleTreeNode       i_currentTreeNode; 
  private JTextField            i_txtName; 
  private JTextField            i_txtType; 
  private JTextArea             i_txtDescription; 
  private JLabel                i_lblName; 
  private JLabel                i_lblType; 
  private JLabel                i_lblDesc; 
  private AddDataActionListener i_al; 
  
  public AddData_ExampleB( JFrame frm ) 
  { 
    GridBagLayout gb = new GridBagLayout(); 
    GridBagConstraints panConstr = 
      new GridBagConstraints(); 
    panConstr.anchor = 
      GridBagConstraints.NORTHWEST; 
    panConstr.insets = new Insets( 2, 2, 2, 2 ); 
    frm.getContentPane().setLayout( gb ); 

    //Create the root 
    i_al = new AddDataActionListener(); 

    Vehicle wrkVehicle = new Vehicle( "Vehicle" ); 
    wrkVehicle.setType( "Motor" ); 
    wrkVehicle.setDescription( 
      "Classification for motor vehicles" ); 

    Vehicle truck = new Vehicle( "Truck" ); 
    truck.setType( "Work" ); 
    truck.setDescription("For hauling goods"); 

 Vehicle pickup = new Vehicle( "Pickup " ); 
    pickup.setType( "Work" ); 
    pickup.setDescription( "For hauling hay" ); 
    truck.addSubType( pickup ); 
    wrkVehicle.addSubType( truck ); 

    i_root = new VehicleTreeNode(wrkVehicle,true); 
    i_model = new DefaultTreeModel(i_root, true); 
    //Create the tree using the root. 
    i_tree = new JTree( i_model ); 

    //Used to give desireable look to the JTree 
    i_tree.putClientProperty("JTree.lineStyle", "Angled"); 

    i_tree.setShowsRootHandles( true ); 
    i_tree.addTreeSelectionListener( i_al ); 

    panConstr.fill = GridBagConstraints.BOTH; 
    panConstr.weightx = 40; 
    panConstr.weighty = 100; 
    panConstr.gridx = 0; 
    panConstr.gridy = 0; 
    JScrollPane scrPane = new JScrollPane(i_tree); 
    gb.setConstraints( scrPane, panConstr ); 
    frm.getContentPane().add( scrPane ); 

    JPanel panel = createEditPanel(); 
    panConstr.weightx = 60; 
    panConstr.weighty = 100; 
    panConstr.gridx = 1; 
    panConstr.gridy = 0; 
    gb.setConstraints( panel, panConstr ); 
    frm.getContentPane().add( panel ); 

    JPanel buttonPanel = new JPanel( 
      new GridLayout(1, 2) ); 
    i_cmdAdd = new JButton("Add Nodes"); 
    i_cmdAdd.addActionListener( i_al ); 
    buttonPanel.add( i_cmdAdd ); 
    i_cmdRemove = new JButton( 
      "Remove Selected Node"); 
    i_cmdRemove.addActionListener( i_al ); 
    buttonPanel.add( i_cmdRemove ); 
    panConstr.fill = GridBagConstraints.NONE; 
    panConstr.weightx = 0; 
    panConstr.weighty = 0; 
    panConstr.gridx = 0; 
    panConstr.gridy = 1; 
    panConstr.gridwidth = 2; 
    gb.setConstraints(buttonPanel, panConstr); 
    frm.getContentPane().add( buttonPanel ); 
  } 

  public class AddDataActionListener 
    implements ActionListener, 
      TreeSelectionListener, UpdateEventListener 
  { 

  /*********************************************** 
  * This method is required by implementing the  * 
  * ActiontListener interface. The method        * 
  * is called when a user clicks the Update, Add * 
  * or Remove JButtons. If the user clicks add   * 
  * the class creates 2 more Vehicle classes and * 
  * inserts them into the model. NOTE: Do not    * 
  * insert into the MutableTreeNode as the view  * 
  * will not be updated until a repaint() occurs * 
  *                                              * 
  * @param update The ActionEvent that is        * 
  *               generated by the JButtons      * 
  * @return void                                 * 
  *                                              * 
  ***********************************************/ 
    public void actionPerformed( ActionEvent aeV ) 
    { 
      Object source = aeV.getSource(); 
      /// 
      // Add 2 new objects to the JTree 
      // 
      if( (source == i_cmdAdd) && 
        (i_root.getChildCount() <= 1) ) 
      { 
        Vehicle car = new Vehicle("Car"); 
        car.setType( "Personal" ); 
        car.setDescription( 
          "Used to drive to and from work"); 
        VehicleTreeNode carNode = 
          new VehicleTreeNode(car,true); 
        Vehicle van = new Vehicle("Van"); 
        van.setType( "Groups" ); 
        van.setDescription( 
          "For transporting groups of people"); 
        VehicleTreeNode vanNode = 
          new VehicleTreeNode(van,true); 
        i_model.insertNodeInto( 
          carNode, i_root, 1 ); 
        i_model.insertNodeInto( 
          vanNode, i_root, 1 ); 
      } 
      /// 
      // Remove the selected TreeNode from JTree. 
      // 
      else if( (source == i_cmdRemove) ) 
      { 
        if( i_tree.getMinSelectionRow() > 1 ) 
        { 
          if( i_currentTreeNode != null ) { 
            i_currentTreeNode. 
              removeUpdateEventListener(this); 
          } 
          i_model.removeNodeFromParent( 
            i_currentTreeNode); 
        } 
        else if( i_tree.getMinSelectionRow() == 1) 
            JOptionPane.showMessageDialog( 
                    null, 
                    "Intentially not allowing " + 
                    "this node to be removed"); 

      } 
      /// 
      // Update a TreeNode with changed values 
      // 
      else if( source == i_cmdUpdate ) { 
        if( i_currentTreeNode != null ) 
        { 
          i_currentTreeNode.getUserObject(). 
            setName( i_txtName.getText() ); 
          i_currentTreeNode.getUserObject(). 
            setType(i_txtType.getText()); 
          i_currentTreeNode.getUserObject(). 
            setDescription( 
              i_txtDescription.getText()); 
        } 
      } 
    } 

  /*********************************************** 
  * This method is required by implementing the  * 
  * UpdateEventListener interface.The interface  * 
  * allows the MutableTreeNode/VehicleTreeNode   * 
  * to communicate with the JTree.               * 
  *                                              * 
  * @param update The UpdateEvent that is        * 
  *               generated by the               * 
  *               MutableTreeNode                * 
  *                           (VehicleTreeNode)  * 
  * @return void                                 * 
  *                                              * 
  ***********************************************/ 
  public void valueUpdated( UpdateEvent update ) 
  { 
    Object source = update.getSource(); 
    if( source instanceof TreeNode ) 
    { 
      TreeNode[] nodes = 
        i_model.getPathToRoot((TreeNode)source); 
      TreePath path = new TreePath( nodes ); 
      /// 
      // Inform the model that something has 
      // changed, which in turn updates the view 
      // 
      i_model.valueForPathChanged( path, update. 
        getUpdateValue() ); 
    } 
  } 

  /*********************************************** 
  * This method is required by implementing the  * 
  * TreeSelectionListener interface. This call   * 
  * indicates a change has occurred and the      * 
  * view needs to be updated. Rather than        * 
  * registring with each of the nodes to listen  * 
  * for changes, this class only registers to    * 
  * listen to the node currently selected.       * 
  *                                              * 
  * @param treeEvent The selection event that    * 
  *                  contains information re:    * 
  *                  which node is selected      * 
  *                                              * 
  * @return void                                 * 
  *                                              * 
  ***********************************************/ 
  public void valueChanged( TreeSelectionEvent 
      treeEvent ) 
    { 
      if( i_currentTreeNode != null ) { 
        i_currentTreeNode. 
          removeUpdateEventListener( this ); 
      } 
      Object source = treeEvent.getSource(); 
      if( source instanceof JTree ) 
      { 
        if( ((JTree)source).getSelectionPath() 
          != null ) 
        { 
          Object selNode = ((JTree)source). 
            getSelectionPath(). 
              getLastPathComponent(); 
          if( selNode instanceof VehicleTreeNode ) 
          { 
            /// 
            // Add as listener for changes to the 
            // TreeNode 
            // 
            i_currentTreeNode = 
              (VehicleTreeNode)selNode; 
            i_currentTreeNode. 
              addUpdateEventListener( this ); 
            Vehicle curVehicle = 
              i_currentTreeNode.getUserObject(); 
            /// 
            // Update the view (JTextFields, etc) 
            // 
            if( curVehicle != null ) 
            { 
              i_txtName.setText( 
                curVehicle.getName() ); 
              i_txtType.setText( 
                curVehicle.getType() ); 
              i_txtDescription.setText( 
                curVehicle.getDescription() ); 
            } 
          } 
        } 
      } 
    } 
  } 
  

  /*********************************************** 
  * This method creates the view containing the  * 
  * JTree and text components.                   * 
  *                                              * 
  * @param none                                  * 
  *                                              * 
  * @return JPanel Contains the components       * 
  *                                              * 
  ***********************************************/ 
  public JPanel createEditPanel() 
  { 
    JPanel panel = new JPanel(); 
    GridBagLayout layout = new GridBagLayout(); 
    GridBagConstraints gbc = 
      new GridBagConstraints(); 
    panel.setLayout( layout ); 
    i_lblName = new JLabel( "Name" ); 
    gbc.anchor = GridBagConstraints.NORTHWEST; 
    gbc.insets = new Insets( 2, 2, 2, 2 ); 
    gbc.fill = GridBagConstraints.NONE; 
    gbc.weightx = 0; 
    gbc.weighty = 0; 
    gbc.gridx = 0; 
    gbc.gridy = 0; 
    layout.setConstraints(i_lblName, gbc); 
    panel.add( i_lblName ); 
  
    i_txtName = new JTextField(); 
    gbc.fill = GridBagConstraints.HORIZONTAL; 
    gbc.fill = GridBagConstraints.BOTH; 
    gbc.weightx = 100; 
    gbc.gridx = 1; 
    layout.setConstraints( i_txtName, gbc ); 
    panel.add( i_txtName ); 
  
    i_lblType = new JLabel( "Type" ); 
    gbc.fill = GridBagConstraints.NONE; 
    gbc.weightx = 0; 
    gbc.gridx = 0; 
    gbc.gridy = 1; 
    layout.setConstraints( i_lblType, gbc ); 
    panel.add( i_lblType ); 
  
    i_txtType = new JTextField(); 
    gbc.fill = GridBagConstraints.HORIZONTAL; 
    gbc.weightx = 100; 
    gbc.gridx = 1; 
    layout.setConstraints( i_txtType, gbc ); 
    panel.add( i_txtType ); 
  
    i_lblDesc = new JLabel( "Description " ); 
    gbc.fill = GridBagConstraints.NONE; 
    gbc.weightx = 0; 
    gbc.gridx = 0; 
    gbc.gridy = 2; 
    layout.setConstraints( i_lblDesc, gbc ); 
    panel.add( i_lblDesc ); 
  
    i_txtDescription = new JTextArea(); 
    i_txtDescription.setLineWrap( true ); 
    i_txtDescription.setWrapStyleWord( true ); 

    JScrollPane scrPane = 
      new JScrollPane( i_txtDescription ); 
    gbc.fill = GridBagConstraints.BOTH; 
    gbc.weighty = 100; 
    gbc.gridy = 3; 
    gbc.gridwidth = 2; 
    layout.setConstraints( scrPane, gbc ); 
    panel.add( scrPane ); 
  
    i_cmdUpdate = new JButton( "Update " ); 
    i_cmdUpdate.addActionListener( i_al ); 
    gbc.fill = GridBagConstraints.NONE; 
    gbc.weighty = 0; 
    gbc.gridy = 4; 
    gbc.gridwidth = 1; 
    layout.setConstraints( i_cmdUpdate, gbc ); 
    panel.add( i_cmdUpdate ); 
  
    JPanel filler = new JPanel(); 
    gbc.fill = GridBagConstraints.BOTH; 
    gbc.weightx = 100; 
    gbc.weighty = 100; 
    gbc.gridy = 5; 
    gbc.gridwidth = 2; 
    layout.setConstraints( filler, gbc ); 
    panel.add( filler ); 
    gbc.gridx = 1; 
    gbc.weightx = 100; 
    gbc.weighty = 0; 
  
    return( panel ); 
  } 
  
  /*********************************************** 
  * Main method for launching the application    * 
  *                                              * 
  * @param args Required signature for main()    * 
  *                                              * 
  * @return void                                 * 
  *                                              * 
  ***********************************************/ 
  public static void main(String[] args) { 
    JFrame f = new JFrame( 
      "Add data to the JTree - Example B"); 
    f.addWindowListener(new WindowAdapter() { 
      public void windowClosing( WindowEvent we ) 
      { 
       System.exit(0); 
      } 
    }); 
    AddData_ExampleB example = 
                      new AddData_ExampleB( f ); 
    f.pack(); 
    f.setSize( 
      new Dimension( f.getSize().width, 400 ) ); 
    f.setLocation( 200, 100 ); 
    f.setVisible(true); 
  } 
} 


  

 

All Rights Reserved
Copyright ©  2004 SYS-CON Media, Inc.
  E-mail: [email protected]

Java and Java-based marks are trademarks or registered trademarks of Sun Microsystems, Inc. in the United States and other countries. SYS-CON Publications, Inc. is independent of Sun Microsystems, Inc.