Xoetrope
View

Using XTrees

Luan O'Carroll, XUI 3.1, 26th June 2007

XUI's XTree component can be used for a wide variety of hierarchical data. In addition to the use of hierarchical data the tree component can be used to provide easy navigation of the relational data.

Out of the box XUI's hierarchical data model maps directly into the tree model provided by Swing via some simle adapters. The tree component can therefore be bound to XML tree structures and HTML like tables.

Beyond these implicitly hierarchical model a XUI tree component can be bound directly to a table where the columns are sorted with the sort order corresponding to the fields, left to right.

Somethimes though, these structures are insufficient and it is necessary to add a custom model for use with the trees. In addition to adding a custom model it may be necessary to add a custom renderer to properly render the tree nodes.

This article describes this use of the tree component and how data stored in the tree nodes can be accessed.

Setting up the custom tree model

In this example a table configured at the USER_DATA node in the XUI data model. The table describes the parent-child relationship with the ID field. The method below then recursively scans this table and appends nodes for the records match the parent node's ID.

Extra attributes can be added to the model for each tree node if you need to store extra information. The XModel can flexibly store an arbitrary number of attributes. In the example below 2 extra attributes are added.

 
 
Creating the data model
 
 
private void fillUserModel()
{
  userTable = (DatabaseTableModel)rootModel.get( "USER_DATA" );
  numUsers = userTable.getNumChildren();
  XBaseModel userModel = (XBaseModel)rootModel.get( "users" );
  userModel.clear();

  appendUsers( userModel, “0” );
}

private void appendUsers( XBaseModel parent, String parentID )
{
  for ( int i = 0; i < numUsers; i++ ) {
    DatabaseRowModel drm = (DatabaseRowModel)userTable.get( i );
    Object b = ((XFieldModel)drm.get( "Manager" )).get();
    String boss = ( b == null ) ? null : b.toString().trim();
    if ( boss.equals( parentID ) {
      String name = drm.get( "Name" ).toString().trim();
      String userID = drm.get( "UserID" ).toString().trim();


       XBaseModel newNode = new XBaseModel( parent, name, drm );
       newNode.setNumAttributes( XBaseModel.NUM_FIXED_ATTRIBUTE + 3 );
       newNode.setAttribValue( XBaseModel.NUM_FIXED_ATTRIBUTE,
                               "Extra0",
                               administrator );
       newNode.setAttribValue( XBaseModel.NUM_FIXED_ATTRIBUTE + 1,
                               "Extra1",
                               level1UserId );
       newNode.setAttribValue( XBaseModel.NUM_FIXED_ATTRIBUTE + 2,
                               "Extra2",
                               level2UserId );
       userModels.add( newNode );

       appendUsers( newNode, userID );
    }
  }
}
 
 

Adding a renderer

Once the model is setup it can be bound to the tree component, but the tree may not be expecting the type of data provided by the data model, or you may not want to structure your data model on the basis of how it is rendered in a tree component and therefore a custom cell renderer that can extract the desired information from the model may be useful.

 
 
Implement the renderer
 
 
public class UserTreeCellRenderer extends DefaultTreeCellRenderer
{
  private DatabaseRowModel drm;

  public UserTreeCellRenderer()
  {
    setToolTipText( "Users" );
    
    closedIcon = null;
    openIcon = null;
    leafIcon = null;
  }
  
  public Component getTreeCellRendererComponent( JTree tree,
                               Object value,
                               boolean sel,
                               boolean expanded,
                               boolean leaf,
                               int row,
                               boolean hasFocus )
  {
    if (( value != null ) && ( value instanceof XTreeModelAdapter )) {
      Object obj = ((XTreeModelAdapter)value).getModel().get();
      if ( obj instanceof DatabaseRowModel )
        drm = (DatabaseRowModel)obj;
      else
        drm = null;
    }

    return super.getTreeCellRendererComponent( 
                               tree,
                               value,
                               sel,
                               expanded,
                               leaf,
                               row,
                               hasFocus );
  }

  /**
   * Overrides <code>DefaultTreeCellRenderer.getPreferredSize</code> to
   * return slightly wider preferred size value.
   */
  public Dimension getPreferredSize()
  {
    Dimension retDimension = super.getPreferredSize();

    if ( retDimension != null )
      retDimension = new Dimension( 
          Math.max( retDimension.width +
            2 * retDimension.height + 32, 100 ),
          retDimension.height );

	return retDimension;
  }

  public void setText( String str )
  {
    if ( str.length() == 0 ) 
      super.setText( str );
    else if ( drm != null ) {
      String text = drm.get( "Name" ).toString().trim();
      
      super.setText( text );
    }
    else 
      super.setText( str );
    }
  }
  
 
 

Then to use the listener, just add the renderer to the list once it has been instantiated:

 
 
Set the cell renderer
 
 
usersTree.setCellRenderer( new UserTreeCellRenderer());
 
 

Listening for tree events

Once the tree has been setup, most applications will probably want to respond to use selections and the easiest way to do this is using XUI's event bindings:

 
 
Add an event handler
 
 
  <Events>
    <Event method="userChanged" target="usersTree" type="TreeSelectionHandler"/>
 
 

Accessing the tree nodes

The event handler is invoked once a tree selection is made, and you can retrieve the selected node from the tree component.

In the above methods the DatabaseRowModel was added to the model node used for the tree, and therefore the database model can be accessed. While this is not necessary, it is often usefult to retain a reference to the original datasource.

In addition (or even alternatively) the extra attributes added to the node can be accessed.

 
 
Implement the event handler
 
 
public void userChanged()
{ Object obj = usersTree.getSelectedNode(); if ( obj instanceof XBaseModel ) { XBaseModel model = (XBaseModel)obj; obj = model.get(); if ( obj instanceof DatabaseRowModel ) { DatabaseRowModel drm = (DatabaseRowModel)obj; String name = drm.get( "Name" ).toString().trim(); reportsTo = drm.get( "Manager" ).toString().trim(); extra1 = (String)model.getAttribValue( XBaseModel.NUM_FIXED_ATTRIBUTE + 1 ); extra2 = (String)model.getAttribValue( BaseModel.NUM_FIXED_ATTRIBUTE + 2 ); userId = drm.get( "UserID" ).toString().trim(); ... } } }