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() |
||
![]() |
![]() |



