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.
|