Swing’s component for displaying tables, much like a spreadsheet.
Debugging
The most common bugs in using JTables are:
- Failing to fire the appropriate change events, such as fireTableRowsInserted,
in your add and similar TableModel
methods to let Swing know what needs to be repainted.
- When you modify the TableModel with a Thread
other than the Swing Thread, you must do most of
your JTable and TableModel
calls with SwingUtilities, including firing changes
via invokeLater from other Threads.
You further need locks in the TableModel to deal
with the problem of Swing accessing the model at the same time the app is
changing it.
JTables are tricky. I strongly suggest buying a
textbook that covers them rather than trying to figure it out all on your own
from the Javadoc.
Classes to implement a JTable
You actually need a family of components:
Limitations
JTables are not as flexible as HTML tables. Cells
cannot span multiple rows or multiple columns. It is possible to change the
height of a row from the default 16 pixels with JTable.
setRowHeight or width of column with TableColumn.
setWidth. In addition you can arrange that the user
can change the column widths with dragging.
JTabless are tricky. It is not that anything is that
difficult, it is just that there are so many classes and so many methods to
master. JTables are more like the plans for a do-it-yourself
table than a table-creating appliance. I strongly suggest buying a textbook that
covers them rather than trying to figure it out all on your own from the Javadoc.
How JTables Work
The key thing to understand is the data live only in the DataModel.
The only rendered data are what is currently visible. There is no giant bit map
scrolling off screen. There is no corresponding giant grid of JComponents.
There is not even a single row of JComponents. A TableCellRenderer
renders all its cells recycling the same JComponent.
Whenever the user scrolls, new rows are fetched from the DataModel
and rendered. The bit map for the rendered rows still visible is slid down in a
rapid bit/blt move. This scheme allows rapid scrolling over a DataModel
that might not even be totally RAM-resident.
Wide JTables
If your JTables are too wide what can you do?
- Put them in a JScrollPane so that the whole table
scrolls.
- Let the user change the width of the columns to squeeze the data into the
available space, widening columns as needed.
- Give the users shortcuts to configure various column width constellations.
- It gets awkward if you try to use two or three rows per data item. You still
need a perfect grid. You can’t have cells that span more than one column
as you can in an HTML table or spreadsheet. Rendering is based on identical data
format/type in each cell of a column.
- To delete a row, you can have a icon on each row to click, or you can select a
row and right click to select delete off a menu. If the cell contents are
immutable, you might allow the delete key on any cell.
Useful Classes and Interfaces
| Classes and Interfaces Useful in Creating JTables |
| Class |
Use |
| AbstractTableModel |
implements the TableModel with your own
class to represent each row. |
| DefaultCellEditor |
Implements TableCellEditor for some common
data types. |
| DefaultTableCellRenderer |
Implements TableCellRenderer for some common
data types, including ImageIcon. |
| DefaultTableModel |
implements the TableModel with each row
represented by a Vector. |
| JTable |
Represents the entire table. |
| JTableHeader |
Represents all the header columns. |
| ListSelectionModel |
Methods to track which rows are selected. |
| TableCellEditor |
Methods to edit a single cell. |
| TableCellRenderer |
Methods to display a single cell, both data and headers. |
| TableColumn |
Represents one column, widths etc. Used to attach TableCellRenderers
for both data and headers. |
| TableColumnModel |
information about all columns. |
| TableModel |
Represents the data in the table grid. |
| TableModelEvent |
notify listeners that a table model has changed |
| TableRowSorter |
Keep rows sorted. JDK 1.6+ only. |
Strategy
JTables can be overwhelming. It is hard to plan everything out before you start.
So here is a muddle through approach. You add a little bit, then compile. If you
don’t understand everything just compose dummy methods you can come back
to later.
- Define your JTable and a ScrollPane
to contain it.
- Use the JTable constructor that takes only one
parameter, the TableModel.
- Define your custom TableModel class, perhaps
making it an inner class. Have it extend AbstractTableModel
or DefaultTableModel. Use AbstractTableModel
if your rows are represented by a proper class, and DefaultTableModel
if they are represented by a Vector of fields. Don’t
try to compose your model totally from scratch by implementing every method in TableModel.
- Use your IDE to provide skeletons for the methods you absolutely must override.
- Look over the methods in your base class to see which ones need overriding, and
write the code.
- Declare a RowOfCells class whose elements are the
data for one row. Pick a more descriptive name than that, please. Write getters
and setters for the fields.
- Declare an ArrayListRowOfCells
allRows to hold all the data for the table.
- Define a set of constants to name each of the columns defining the 0-based
column number.
- Write add, set, remove
etc. methods to let you change the rows in the model. Base them on the
corresponding ArrayList methods. Make sure you call fireTableRowsInserted,
fireTableRowsUpdated or fireTableRowsDeleted
to notify Swing of the changes you made to the underlying data.
- Write your Object getValueAt(
int rowIndex, int
columnIndex ). Don’t change the Object
to something else, or you won’t override the correct method. It will need
a switch to use a different getter depending on the
column.
- Write your setValueAt( Object
aValue, int rowIndex,
int columnIndex ). Don’t
change the Object to something else, or you won’t
override the correct method. You can leave this out or use a dummy if your table
is not editable.
- Test your data-model modifying methods by using them to put some dummy data into
the table, rather than trying to handle keyboard input just yet. It may be
easier to add one column at a time to your model and get that column completely
working with custom renderers and editors. They you can clone that working,
debugged code for the other columns, rather than your first cut buggy code.
- Hookup buttons or menu items to call the add and remove
row methods.
- Set your column width with JTable.getColumnModel
and setPreferredWidth.
- Write a TableCellRenderer to chose the fonts and
colours of your heading. Install with TableColumn.setHeaderRenderer.
- Add your persistence mechanism to load up allRows and
save on exit. Make sure suitable event changes get fired. You might save to disk,
to the registry via the Preferences mechanism or to
a server.
- Compose your custom TableCellRenderers for the data
in each column. Hook them up with TableColumn.setCellRenderer.
- Add sorts to your TableModel, remembering to fireTableDataChanged.
You can trigger them with TableHeader.addMouseListener.
In JDK 1.6+ JTables have built-in javax.swing.table.
TableRowSorters.
- Add your editing and custom TableCellEditors. It can
be as simple as:
tableColumn.setCellEditor( new DefaultCellEditor( comboBox ) );
where comboBox is a JComboBox
scratch component the user is allowed to edit. DefaultCellEditor
will handle popping up the scratch edit component, initialising it, and sending
the new value to the TableModel. To do more
elaborate edits, search for sample code on the web. If you apply colours and
fonts to your scratch components, it will make it clearer to the user which cell
he is editing. The crucial methods are:
- getTableCellEditorComponent which converts an Object
value taken from from the TableModel via getValueAt
to a displayable GUI object, e.g. a JTextField.
- getCellEditorValue which extracts a String
from the GUI component and converts it to an Object
suitable for storing in the TableModel via setValueAt.
You can just throw an Exception if you don’t
like the value, perhaps make a noise too. The value will revert to the previous
if the user does not fix the problem.
- Add calls to stopEdit just prior to any time you
sort, insert, delete or replace rows. JTable does
not do this by default. Without this code, a field being edited will be
improperly left where it was, ending up on the wrong line.
- Insert code to make sure a given row is visible in your scroll region like this:
- Add threads so that the computation is on its own thread with the Swing thread
free to keep the GUI up to date. Add synchronized
to TableModel methods as needed. Make sure the
model is not locked when you call the various fire
methods because those events will need to get at the model via getValueAt
to repaint the GUI. Pepper your code with Thread.yield()
after you call methods that could change the GUI. This will give the Swing
thread a bash at processing the generated GUI-changing events. Remember that all
the JTable methods including fireTableCellUpdated
and brethren must be invoked only from the Swing thread. This means the calls
must often be wrapped in SwingUtilities.invokeLater.
Normally you need to make your TableModel thread-safe
because JTable will call its methods on the Swing
thread to discover the data values and your app will call its methods on some
other thread to set the data values.
- Hook up Listeners to the ListSelectionModel if there
is anything you want to when the selection changes. ListSelectionEvent.
getFirstIndex and ListSelectionEvent.
getLastIndex do not give you the bounds of selected
rows. They give you the range of rows whose selection status may have changed.
- Review the methods of JTable, ListSelectionModel,
DefaultTableModel and DefaultTableCellRenderer
reading the documentation and looking for methods to tweak the implementation.
When you are looking for a method, scan all those classes. Often the method you
are looking for is not where you expect and is not called what you might expect.
Using a text search tool on the Javadoc can be a great help to find what you are
looking for as can examining the source code.
TableCellRenderer
Gotchas
- If you write a TableCellRenderer that extends DefaultTableCellRenderer,
the underlying getTableCellRendererComponent method
keeps returning the same recycled JLabel object. So
if you add an icon to it for example, you will effectively add that icon to all
the columns, unless you explicitly remove it for the columns that don’t
need it.
- You hook up a TableCellRenderer with either TableColumn
setCellRenderer to control rendering a specific
column or jTable.setDefaultRenderer
to control rendering of all objects of a given class. You use TableColumn.
setHeaderRenderer to control rendering of the
header for a specific column. If you hook up two cell renderers to the same
column, they cascade (the output of the first becomes the input to the second),
and are both applied. The second does not replace the first.
- If you write a TableCellRenderer for headings,
make sure you call jTable.setAutoCreateColumnsFromModel(
false ); otherwise your renderer will be
ignored, even though you called TableColumn. setHeaderRenderer.
- If in your TableCellRenderer you create your own JLabels
to return, rather than using the one from DefaultTableCellRenderer,
make sure you call JLabel.setOpaque(
true ) or else your JLabel.
setBackground will be ignored.
- You will probably also want to call JLabel.
setBorder( new EmptyBorder(1,
1, 1, 1) ) to keep the division lines between columns and JLabel.
setHorizontalAlignment( SwingConstants.
CENTER ) to ensure the labels are centred. Make
sure you always set the alignment explicitly if you ever set it or otherwise an
alignment setting for one rendering will spill over into another.
- If you call JTable.showGrid( false
), you must also call JTable.setIntercellSpacing(
new Dimension( 0, 0 ) );
to butt the cells right up against each other.
- An alternative approach is to override the prepareRenderer
method.
TableRowSorter
- TableRowSorter is only available in JDK 1.6+. Sun
provides source for TableSorter you can cannibalise
to sort JTables in earlier JDKs.
- TableRowSorter does not sort your model. It creates
an auxiliary sorted index into it.
- TableRowSorter by default flips the sort from
ascending to descending and back when the user clicks a column header. The logic
to do this is handled elsewhere, not TableRowSorter.
I have not yet found the code. However, it does not display any indicators as to
the current column or whether it is sorted ascending or descending. I have not
yet figured out a clean way to implement the indicators that can’t
possibly get out of sync.
TZ: a Simple Example
Here is a simple example using a JTable that uses a TableRowSorter
to let the user sort by any of the columns. It displays a list of all the
timezones that Java Supports. To see it in action see timezone.
VerCheck: a Real World Example
Here is a real world example of a signed Applet that uses a JTable.
This is the main class, an Applet to let you
maintain a list of applications to automate checking if there is a newer version.
To see it is action see VerCheck.
Enumeration of the various states an application can be in, and the associated
icon:
Renders the icon for a state:
Class to hold the table data about an Application:
Default settings for a group of common apps:
Lets you edit an ISO date in a table:
Lets you render an ISO date in a table:
Lets you render table data choosing the colours:
Makes sounds, mostly verbal error messages:
Renders the table headars:
Learning More
To see some simple sample code implementing a JTable,
see the source code for the TimeZones Applet.
Sun’s Javadoc on the
JTable class : available:
Sun’s Javadoc on the
TableModel class : available:
Sun’s Javadoc on the
AbstractTableModel class : available:
Sun’s Javadoc on the
DefaultTableModel class : available:
Sun’s Javadoc on the
TableModelEvent class : available:
Sun’s Javadoc on the
TableColumnModel class : available:
Sun’s Javadoc on the
TableColumn class : available:
Sun’s Javadoc on the
TableCellRenderer class : available:
Sun’s Javadoc on the
DefaultTableCellRenderer class : available:
Sun’s Javadoc on the
JTableHeader class : available:
Sun’s Javadoc on the
TableCellEditor class : available:
Sun’s Javadoc on the
DefaultCellEditor class : available:
Sun’s Javadoc on the
ListSelectionModel class : available:
Sun’s Javadoc on the
TableRowSorter class : available: