[an error occurred while processing this directive]

Page One
Page Two
The Databank
What Is Swing?
Special Report
IDE Roundup
Swing and the Web
Swing Text
Tech Topics
Friends
Tips and Tricks
The PLAF Papers
Call 911
The Archive
JFC Home
Download Swing
Swing API Docs
Download JDK
JDK Docs
Java Tutorial
 
The Swing Connection Tech Topics

Creating TreeTables in Swing
Just Use a JTree to Render JTable Cells

By Philip Milne

A TreeTable is a combination of a Tree and a Table -- a component capable of both expanding and contracting rows, as well as showing multiple columns of data. The Swing package does not contain a JTreeTable component, but it is fairly easy to create one by installing a JTree as a renderer for the cells in a JTable.

This article explains how to use this technique to create a TreeTable. It concludes with a example application, named TreeTableExample0, which displays a working TreeTable browser that you can use to browse a local file system (see illustration).

In Swing, the JTree, JTable, JList, and JComboBox components use a single delegate object called a cell renderer to draw their contents.  A cell renderer is a component whose paint() method is used to draw each item in a list, each node in a tree, or each cell in a table.  A cell renderer component can be viewed as a "rubber stamp": it's moved into each cell location using setBounds(), and is then drawn with the component's paint() method.

By using a component to render cells, you can achieve the effect of displaying a large number of components for the cost of creating just one.  By default, the Swing components that employ cell renderers simply use a JLabel, which supports the drawing of simple combinations of text and an icon. To use any Swing component as a cell renderer, all you have to do is create a subclass that implements the appropriate cell renderer interface: TableCellRenderer for JTable, ListCellRenderer for JList, and so on.


Rendering in Swing

Here's an example of how you can extend a JCheckBox to act as a renderer in a JTable:

    public class CheckBoxRenderer extends JCheckBox          
                 implements TableCellRenderer          { 
        public Component getTableCellRendererComponent(JTable table, 
               Object value, boolean isSelected, 
               boolean hasFocus, int row, int column) { 
                       setSelected(((Boolean)value).booleanValue())); 
                       return this; 
                 } 
       }

How the example program works

The code snippet shown above -- part of a sample program presented in full later in this article -- shows how to use a JTree as a renderer inside a JTable. This is a slightly unusual case because it uses the JTree to paint a single node in each cell of the table rather than painting a complete copy of the tree in each of the cells.
 
We start in the usual way: expanding the JTree into a cell render by extending it to implement the TableCellRenderer interface. To implement the required behavior or a cell renderer, we must arrange for our renderer to paint just the node of the tree that is visible in a particular cell. One simple way to achieve this is to override the setBounds() and paint() methods, as follows:


 public class TreeTableCellRenderer extends JTree 
             implements TableCellRenderer { 
    protected int visibleRow; 
    public void setBounds(int x, int y, int w, int h) { 
              super.setBounds(x, 0, w, table.getHeight());          
          } 
    public void paint(Graphics g) { 
              g.translate(0, -visibleRow * getRowHeight());          
              super.paint(g); 
          } 
    public Component getTableCellRendererComponent(JTable table,          
              object value, 
              boolean isSelected, 
              boolean hasFocus, 
              int row, int column) { 
                  visibleRow = row; 
                  return this; 
              } 
 } 

As each cell is painted, the JTable goes through the usual process of getting the renderer, setting its bounds, and asking it to paint. In this case, though, we record the row number of the cell being painted in an instance variable named visibleRow. We also override setBounds(), so that the JTree remains the same height as the JTable, despite the JTable's attempts to set its bounds to fit the dimensions of the cell being painted.

To complete this technique we override paint(), making use of the stored variable visibleRow, an operation that effectively moves the clipping rectangle over the appropriate part of the tree. The result is that the JTree draws just one of its nodes each time the table requests it to paint.

In addition to installing the JTree as a renderer for the cells in the first column, we install the JTree as the editor for these cells also. The effect of this strategy is the JTable then passes all mouse and keyboard events to this "editor" -- thus allowing the tree to expand and contract its nodes as a result of user input.


Example: A file-system browser

The example program presented with this article creates and implements a browser for a file system. Each directory can be expanded and collapsed. Other columns in the table display important properties of files and directories, such as file sizes and dates.


Note: Correct Swing Version Required

To compile and run the example program provided with this article, you must use Swing 1.1 Beta 2 or a compatible Swing release.


Here is the full list of classes used in the example program, along with a brief description of what each class does (you can download or view each file by clicking its link):

  • TreeTableModel.java: A new interface, extending the TreeModel interface, which describes the kind of data that can be drawn by a TreeTable.

  • AbstractTreeTableModel.java: A base class for TreeTableModels. This class handles the list of listeners.

  • TreeTableModelAdapter.java: A wrapper class that implements the TableModel interface, given both TreeTableModel and a JTree.

  • AbstractCellEditor.java: A base class for CellEditors. This class handlers the list of listeners.

  • JTreeTable.java: A subclass of JTable, this class can render data from a TreeTableModel.

  • MergeSort.java: An implementation of merge sort.

  • FileSystemModel.java: A model of the local file system, implemented as a concrete subclass of AbstractTreeTableModel. This class implements the TreeTableModel interface.

  • TreeTableExample0.java: A demonstration program showing the TreeTable in action.

  • sources.zip: A zipped file containing of the above.
When you execute the TreeTableExample0 program, it displays a TreeTable showing the files and directories in a file system, as shown in the diagram at the beginning of this article. When you click on a branch element in the first column of the table, the item expands or collapses, just as it would in any other tree.

Caveats

For clarity, we have kept the TreeTableExample0 program short. To be complete, a full-featured JTreeTable component would have to deal with a number of other issues. Here are some features that are missing from this chapter's sample program:
  • The TreeTable does not update itself in response to tree events.

  • The view on the file system never updates. If a directory is removed while one is viewing it, the display does not update itself accordingly.

  • The selected TreePaths, maintained in the TreeSelectionModel, may not match what is visually selected.

  • When the selection is changed by user input to the JTree, the table should autoscroll to make sure the new lead selection index is visible.
[an error occurred while processing this directive]