Application Techniques



Programming Swing Tree Controls

How to add nodes and create a multilevel hierarchy for an AgcJTree control.

About this technique

Details

Category

Java Client Techniques> Version 3 Swing-based controls

Description

You'll learn about:

You can run this technique code from:

NOTE   First make sure that database is running on your localhost SilverStream Server

Related reading

See the chapter on programming forms in the Programmer's Guide

The frmJTreeLoadingData example has three AgcJTree controls that each load data using different techniques:

Importing the Swing tree package   Top of page

To work with the classes of the tree data model, include this import statement in your form:

  import javax.swing.tree.*; 

treeLoaded control: populating nodes from static values during setup   Top of page

The treeLoaded control has a list of first level values created by specifying a static list in the Property Inspector. Then code called from the formLoaded event adds child values to each first-level node from a static array. The sample values are types of animals (the first-level values) and specific animals (the second-level values).

The sample code produces a tree control whose values look like this:

To enter the list:

  1. Select the control and open the Property Inspector.

  2. For the Load choices property, select Statically.

  3. Open the Choices dialog and type this list:

      Mammals 
      Birds 
      Reptiles 
      Amphibians 
      Insects 
    

The array of data for the child nodes is declares as an instance variable:

  // 2D array of data, the subarrays correspond to the animal types 
  // specified in Designer 
  String value[] [] =  
  { 
     { "cat", "dog", "agouti" }, // mammals 
     { "duck", "goose", "egret" }, // birds 
     { "iguana", "basilisk", "skink" }, // reptiles 
     { "treefrog", "toad", "peeper" }, // amphibians 
     { "mosquito", "greenhead fly", "moose fly" } // Insects 
  }; 

This code in the user-defined method setup_treeLoaded() called from the formLoaded event adds child values to each of the first-level values:

  int count; 
  DefaultMutableTreeNode root, parentNode;  
  DefaultTreeModel model; 
   
  model = (DefaultTreeModel) tree.getModel(); 
  root = (DefaultMutableTreeNode) model.getRoot(); 
   
  // For each 1st-level node, specify that it allows children; 
  // then add the children from the sTreeDetail array. 
  count = root.getChildCount(); 
  for (int i = 0; i < count; i++)  
  { 
     parentNode = (DefaultMutableTreeNode) root.getChildAt(i); 
     parentNode.setAllowsChildren(true); 
     for (int j = 0; j < sTreeDetail[i].length; j++)  
     { 
        parentNode.add(  
          new DefaultMutableTreeNode( sTreeDetail[i][j], true) ); 
     } 
  } 

Notes on the code

treeExpandable control: populating nodes on demand   Top of page

In the treeExpandable control, the same data values populate the tree, but the child nodes are not added until the user tries to expand one of the first-level nodes. This is accomplished by implementing a TreeWillExpandListener and writing code in the treeWillExpand event that adds child values when necessary.

The coding tasks are:

  1. Import javax.swing.tree.* (described in Importing the Swing tree package).

  2. Specify that the form implements a TreeWillExpandListener.

  3. Write code that allows children for the first-level values.

  4. Make the data for the child nodes available in an accessible form. The example uses a hashtable.

  5. Write code in the treeWillExpand event to add child nodes.

Adding a TreeWillExpandListener   Top of page

The treeWillExpand event occurs after a user requests that the node expand and before the expanded nodes are visible. It is not automatically included on the form. To add it, you implement the TreeWillExpandListener interface. SilverStream can generate stubs for interface methods when you use this procedure:

  1. In the Programming Editor, select File>Java Interfaces and add the interface javax.swing.event.TreeWillExpandListener. Select the check box Create stubs for interface methods.

  2. Add the form as a listener for the control's treeWillExpand event. In the example, this is in the setup_treeExpandable() method, called from formLoaded.

      // Add the TreeWillExpandListener to the form. 
      tree.addTreeWillExpandListener(this); 
    
  3. In the generated stub for the treeWillExpand event, write code to add child nodes (see Adding child nodes in the treeWillExpand event).

Allowing children for the first-level values   Top of page

The example doesn't add child nodes during the setup phase, but it does need to specify that each parent node allows children so that a treeWillExpand event will occur. This code in setup_treeExpandable() loops over the nodes added in the Designer and calls setAllowsChildren(true) for each.

  int count; 
  DefaultMutableTreeNode root, parentNode;  
  DefaultTreeModel model; 
   
  model = (DefaultTreeModel) tree.getModel(); 
  root = (DefaultMutableTreeNode) model.getRoot(); 
   
  // For each 1st-level node, specify that it allows children. 
  count = root.getChildCount(); 
  for (int i = 0; i < count; i++)  
  { 
     parentNode = (DefaultMutableTreeNode) root.getChildAt(i); 
     parentNode.setAllowsChildren(true); 
  } 

Notes on the code

Populating the hashtable with data for the child nodes   Top of page

The instance variable sTreeDetail is a 2D array that holds the data for the child nodes. For the treeExpandable control, when the user clicks on a parent node, the value of that node is the hashtable key.

This is the declaration of the 2D array of Strings (also used for the treeLoaded control):

  // Array of data for child nodes; 
  // order of subarrays matches order of 1st-level  
  // values specified in Designer. 
  String sTreeDetail[] [] = { 
     { "cat", "dog", "agouti" },  // mammals 
     { "duck", "goose", "egret" }, // birds 
     { "iguana", "basilisk", "skink" }, // reptiles 
     { "treefrog", "toad", "peeper" }, // amphibians 
     { "mosquito", "greenhead fly", "moose fly" } // insects 
  }; 

This code in setup_treeExpandable(), called from formLoaded, populates the hashtable:

  hshTreeDetail.put("Mammals", sTreeDetail[0] ); 
  hshTreeDetail.put("Birds", sTreeDetail[1] ); 
  hshTreeDetail.put("Reptiles", sTreeDetail[2] ); 
  hshTreeDetail.put("Amphibians", sTreeDetail[3] ); 
  hshTreeDetail.put("Insects", sTreeDetail[4] ); 

Adding child nodes in the treeWillExpand event    Top of page

The TreeExpansionEvent object provides information about the node being expanded in a TreePath object. This code analyzes the TreePath to determine whether the node needs child nodes, and if so, adds them:

  public void treeWillExpand(javax.swing.event.TreeExpansionEvent evt) 
     throws javax.swing.tree.ExpandVetoException 
  { 
     if (evt.getSource() != treeExpandable)  
        return; 
      
     TreePath tp = evt.getPath(); 
     // Only add child nodes to 1st-level nodes 
     if (tp.getPathCount() != 2) 
        return; 
   
     // Get the node the user clicked. 
     DefaultMutableTreeNode parentNode =  
        (DefaultMutableTreeNode) tp.getLastPathComponent(); 
     // if it already has children (has been expanded), return. 
     if (parentNode.getChildCount() > 0) 
        return; 
         
     // If this node has no children (never expanded), add children. 
     // Get array of child node strings from hashtable  
     // using parent node value as key. 
     String value[] = (String[]) hshTreeDetail.get( 
        parentNode.toString() ); 
     for (int j = 0; j < value.length; j++)  
     { 
        // add each child node so that it does not allow children 
        parentNode.add(new DefaultMutableTreeNode(value[j], false)); 
     } 
  } 

Notes on the code

treeRowCursor control: populating a tree from database tables   Top of page

You can populate the first level of a tree from any row cursor. Other controls, such as views, can share and display the same data as the tree control. Changes to the data are also shared between controls using the same row cursor.

This section explains:

Using loadFromRowCursor()   Top of page

In this example, the form includes an AgcData object called dataDepts, which is associated with a table called departments. It has two column expressions: DeptID and DeptName. The node's display value is the name (expression 1) and the storage value is the ID (expression 0). The following code is in user-defined method setup_treeRowCursor(), called from the formLoaded event:

  try { 
     tree.loadFromRowCursor((AgiRowCursor)dataDepts, 1, 0); 
  }  
  catch (Exception e) { 
     agDialog.showMessage("Failed to load tree from row cursor. "  
        + e.toString() ); 
  } 
  // Add a second level of nodes 
  addLevel(tree); 

Notes on the code

Customizing the root node   Top of page

The root node is visible after you call loadFromRowCursor(). You can give it a value with the setUserObject() method and call nodeChanged() to notify the model of the change.

  // Get the root node and specify meaningful text. 
  DefaultTreeModel model = (DefaultTreeModel) tree.getModel(); 
  DefaultMutableTreeNode root =  
        (DefaultMutableTreeNode) model.getRoot(); 
  root.setUserObject("Departments"); 
  model.nodeChanged(root); 

Notes on the code

Populating the second level from an AgcData control   Top of page

The treeRowCursor control calls two user-defined methods to populate the second level of data: addLevel() and populate(). After populating the first level from the row cursor, you can add additional levels from another AgcData control.

Looping over the first-level nodes

The addLevel() method is called in the formLoaded event after loadFromRowCursor(). addLevel() gets each parent node value (a department ID) and calls populate() to create the child nodes for the department.

  public void addLevel(AgcJTree tree) 
  { 
     DefaultTreeModel model; 
     DefaultMutableTreeNode root, parentNode; 
     Integer value; 
     int count; 
   
     // get model 
     model = (DefaultTreeModel) tree.getModel(); 
     // get root 
     root = (DefaultMutableTreeNode) model.getRoot(); 
     // get the number of categories at the first level 
     count = root.getChildCount(); 
   
     for (int i=0; i < count; i++) 
     { 
        // get a department node and extract the ID value 
        parentNode = (DefaultMutableTreeNode) root.getChildAt(i); 
        parentNode.setAllowsChildren(true); 
        value = (Integer) ((AgoDisplayValue)  
              parentNode.getUserObject()).getUserData(); 
        populate(parentNode, value); 
     } 
  } 

Notes on the code

Adding the child nodes

The AgcData control that retrieves the second-level data is called dataKeywords and has these column expressions: EmpID, EmpFirstName, EmpLastName, and DeptID. The DeptID value has a foreign key relationship with the Department data at the first level.

The user-defined method populateDept() is called from addLevel():

  public void populateDept(DefaultMutableTreeNode parentNode, Integer value) 
    { 
      DefaultMutableTreeNode childNode; 
       
      try  
       { 
        String where; 
        dataEmployees.query("employees.deptid=" + value); 
        // if no employees found for dept, end process 
        if (!dataEmployees.gotoFirst()) return; 
        // loop over keywords and add as child nodes of category 
        while (dataEmployees.gotoNext()) 
         { 
          childNode = new DefaultMutableTreeNode 
           ( 
            new AgoDisplayValue( 
              (String)dataEmployees.getProperty(1) + " " + 
                  dataEmployees.getProperty(2),  
                    dataEmployees.getProperty(0) 
            ) 
          ); 
          parentNode.add(childNode); 
         } 
       }  
      catch (Exception e)  
      { 
        System.out.println("populateDept: " + e);       
      } 
    } 

Notes on the code






Copyright © 2000, SilverStream Software, Inc. All rights reserved.