Professional Documents
Culture Documents
With the JTable class you can display tables of data, optionally allowing the user to edit the data. JTable does
not contain or cache data; it is simply a view of your data. Here is a picture of a typical table displayed within a
scroll pane:
The rest of this section shows you how to accomplish some common table-related tasks. Here are the topics this
section covers:
Try this:
1. Click the Launch button to run SimpleTableDemo using Java™ Web Start (download JDK 7 or later).
Or, to compile and run the example yourself, consult the example index.
Object[][] data = {
{"Kathy", "Smith",
"Snowboarding", new Integer(5), new Boolean(false)},
{"John", "Doe",
"Rowing", new Integer(3), new Boolean(true)},
{"Sue", "Black",
"Knitting", new Integer(2), new Boolean(false)},
{"Jane", "White",
"Speed reading", new Integer(20), new Boolean(true)},
{"Joe", "Brown",
"Pool", new Integer(10), new Boolean(false)}
};
There are two JTable constructors that directly accept data (SimpleTableDemo uses the first):
The advantage of these constructors is that they are easy to use. However, these constructors also have
disadvantages:
2
They require that you put all of the table's data in an array or vector, which may not be appropriate for
some data. For example, if you are instantiating a set of objects from a database, you might want to
query the objects directly for their values, rather than copying all their values into an array or vector.
If you want to get around these restrictions, you need to implement your own table model, as described in
Creating a Table Model.
The JScrollPane constructor is invoked with an argument that refers to the table object. This creates a
scroll pane as a container for the table; the table is automatically added to the container.
JTable.setFillsViewportHeight is invoked to set the fillsViewportHeight property. When this
property is true the table uses the entire height of the container, even if the table doesn't have enough
rows to use the whole vertical space. This makes it easier to use the table as a drag-and-drop target.
The scroll pane automatically places the table header at the top of the viewport. The column names remain
visible at the top of the viewing area when the table data is scrolled.
If you are using a table without a scroll pane, then you must get the table header component and place it
yourself. For example:
container.setLayout(new BorderLayout());
container.add(table.getTableHeader(), BorderLayout.PAGE_START);
container.add(table, BorderLayout.CENTER);
When the user resizes a column by dragging its right border, then either other columns must change size, or the
table's size must change. By default, the table's size remains the same, and all columns to the right of the drag
point resize to accommodate space added to or removed from the column to the left of the drag point.
To customize initial column widths, you can invoke setPreferredWidth on each of your table's columns. This
sets both the preferred widths of the columns and their approximate relative widths. For example, adding the
following code to SimpleTableDemo makes its third column bigger than the other columns:
3
column.setPreferredWidth(100); //third column is bigger
} else {
column.setPreferredWidth(50);
}
}
As the preceding code shows, each column in a table is represented by a TableColumn object. TableColumn
supplies getter and setter methods for the minimum, preferred, and maximum widths of a column, as well as a
method for getting the current width. For an example of setting cell widths based on an approximation of the
space needed to draw the cells' contents, see the initColumnSizes method in TableRenderDemo.java.
When the user explicitly resizes columns, the columns' preferred widths are set such that the user-specified
sizes become the columns' new current widths. However, when table itself is resized — typically because the
window has resized —; the columns' preferred widths do not change. Instead, the existing preferred widths are
used to calculate new column widths to fill the available space.
User Selections
In its default configuration, a table supports a selection that consists of one or more rows. The user can select a
contiguous range of rows or an arbitrary set of rows. The last cell that the user indicated gets a special
indication; in the Metal look and feel, the cell is outlined. This cell is known as the lead selection; it is
sometimes called "the cell with the focus" or "the current cell".
The user uses the mouse and/or keyboard to make selections, as described in the following table:
To see how selections work, click the Launch button to run TableSelectionDemo using Java™ Web Start
(download JDK 7 or later). Or, to compile and run the example yourself, consult the example index.
This example program presents the familiar table, and allows the user to manipulate certain JTable options.
There is also a text pane that logs selection events.
In the screenshot below, a user has run the program, clicked in the first row, then control-clicked in the third
row. Notice the outline around the last cell clicked; this is how the Metal look and feel highlights the lead
selection.
4
Under "Selection Mode" there are a set of radio buttons. Click the one labelled "Single Selection". Now you can
only select one row at a time. If you click on the "Single Interval Selection" radio button, you can select a set of
rows that must be contiguous.
All of the radio buttons under "Selection Mode" invoke JTable.setSelectionMode. This method takes a
single argument, which must be one of the following constants defined in javax.swing.ListSelectionModel:
MULTIPLE_INTERVAL_SELECTION, SINGLE_INTERVAL_SELECTION, and SINGLE_SELECTION.
Returning to TableSelectionDemo, notice the three option checkboxes under "Selection Options." Each of
checkbox controls the state of a boolean bound variable defined by JTable:
NOTE: JTable uses a very simple concept of selection, managed as an intersection of rows and columns. It
was not designed to handle fully independent cell selections.
5
If you clear all three check boxes (setting all three bound properties to false), there is no selection; only the
lead selection is shown.
You may notice that the "Cell Selection" checkbox is disabled in multiple interval selection mode. This is
because cell selection is not supported in this mode in the demo. You can specify selection by cell in multiple
interval selection mode, but the result is a table that does not produce useful selections.
You may also notice that changing any of the three selection options can affect the others. This is because
allowing both row selection and column selection is exactly the same as enabling cell selection. JTable
automatically updates the three bound variables as necessary to keep them consistent.
NOTE: Setting cellSelectionEnabled to a value has the side effect of also setting both
rowSelectionEnabled and columnSelectionEnabled to that value. Setting both rowSelectionEnabled and
columnSelectionEnabled to a value has the side effect of also setting cellSelectionEnabled to that value.
Setting rowSelectionEnabled and columnSelectionEnabled to different values has the side effect of also
setting cellSelectionEnabled to false.
To retrieve the current selection, use JTable.getSelectedRows which returns an array of row indexes, and
JTable.getSelectedColumns which returns an array of column indexes. To retrieve the coordinates of the
lead selection, refer to the selection models for the table itself and for the table's column model. The following
code formats a string containing the row and column of the lead selection:
User selections generate a number of events. For information on these, refer to How to Write a List Selection
Listener in the Writing Event Listeners lesson.
NOTE: Selection data actually describes selected cells in the "view" (table data as it appears after any sorting
or filtering) rather than in the table model. This distinction does not matter unless your viewed data has been
rearranged by sorting, filtering, or user manipulation of columns. In that case, you must convert selection
coordinates using the conversion methods described in Sorting and Filtering.
6
The JTable constructor used by SimpleTableDemo creates its table model with code like this:
new AbstractTableModel() {
public String getColumnName(int col) {
return columnNames[col].toString();
}
public int getRowCount() { return rowData.length; }
public int getColumnCount() { return columnNames.length; }
public Object getValueAt(int row, int col) {
return rowData[row][col];
}
public boolean isCellEditable(int row, int col)
{ return true; }
public void setValueAt(Object value, int row, int col) {
rowData[row][col] = value;
fireTableCellUpdated(row, col);
}
}
As the preceding code shows, implementing a table model can be simple. Generally, you implement your table
model in a subclass of the AbstractTableModel class.
Your model might hold its data in an array, vector, or hash map, or it might get the data from an outside source
such as a database. It might even generate the data at execution time.
This table is different from the SimpleTableDemo table in the following ways:
TableDemo's custom table model, even though it is simple, can easily determine the data's type, helping
the JTable display the data in the best format. SimpleTableDemo's automatically created table model,
on the other hand, does not know that the # of Years column contains numbers (which should generally
be right aligned and have a particular format). It also does not know that the Vegetarian column
contains boolean values, which can be represented by check boxes.
The custom table model implemented in TableDemo does not let you edit the name columns; it does,
however, let you edit the other columns. In SimpleTableDemo, all cells are editable.
See below the code taken from TableDemo.java that is different from the SimpleTableDemo.java. Bold font
indicates the code that makes this table's model different from the table model defined automatically for
SimpleTableDemo.
public TableDemo() {
...
JTable table = new JTable(new MyTableModel());
...
}
7
class MyTableModel extends AbstractTableModel {
private String[] columnNames = ...//same as before...
private Object[][] data = ...//same as before...
/*
* Don't need to implement this method unless your table's
* editable.
*/
public boolean isCellEditable(int row, int col) {
//Note that the data/cell address is constant,
//no matter where the cell appears onscreen.
if (col < 2) {
return false;
} else {
return true;
}
}
/*
* Don't need to implement this method unless your table's
* data can change.
*/
public void setValueAt(Object value, int row, int col) {
data[row][col] = value;
fireTableCellUpdated(row, col);
}
...
}
import javax.swing.event.*;
import javax.swing.table.TableModel;
8
public class SimpleTableDemo ... implements TableModelListener {
...
public SimpleTableDemo() {
...
table.getModel().addTableModelListener(this);
...
}
If DefaultTableModel is not a suitable base class for your custom table model class, consider subclassing
AbstractTableModel. This class implements a simple framework for constructing TableModelEvent objects.
Your custom class simply needs to invoke one the following AbstractTableModel methods each time table
data is changed by an external source.
Method Change
fireTableCellUpdated Update of specified cell.
fireTableRowsUpdated Update of specified rows
fireTableDataChanged Update of entire table (data only).
fireTableRowsInserted New rows inserted.
fireTableRowsDeleted Existing rows Deleted
fireTableStructureChanged Invalidate entire table, both data and structure.
Instead, a single cell renderer is generally used to draw all of the cells that contain the same type of data. You
can think of the renderer as a configurable ink stamp that the table uses to stamp appropriately formatted data
9
onto each cell. When the user starts to edit a cell's data, a cell editor takes over the cell, controlling the cell's
editing behavior.
For example, each cell in the # of Years column in TableDemo contains Number data — specifically, an
Integer object. By default, the cell renderer for a Number-containing column uses a single JLabel instance to
draw the appropriate numbers, right-aligned, on the column's cells. If the user begins editing one of the cells,
the default cell editor uses a right-aligned JTextField to control the cell editing.
To choose the renderer that displays the cells in a column, a table first determines whether you specified a
renderer for that particular column. If you did not, then the table invokes the table model's getColumnClass
method, which gets the data type of the column's cells. Next, the table compares the column's data type with a
list of data types for which cell renderers are registered. This list is initialized by the table, but you can add to it
or change it. Currently, tables put the following types of data in the list:
Remember that if you let a table create its own model, it uses Object as the type of every column. To specify
more precise column types, the table model must define the getColumnClass method appropriately, as
demonstrated by TableDemo.java.
Keep in mind that although renderers determine how each cell or column header looks and can specify its tool
tip text, a renderer does not handle events. If you need to pick up the events that take place inside a table, the
technique you use varies by the sort of event you are interested in:
The next few sections tell you how to customize display and editing by specifying renderers and editors. You
can specify cell renderers and editors either by column or by data type.
It is easy to customize the text or image rendered by the default renderer, DefaultTableCellRenderer. You
just create a subclass and implement the setValue method so that it invokes setText or setIcon with the
appropriate string or image. For example, here is how the default date renderer is implemented:
If extending DefaultTableCellRenderer is insufficient, you can build a renderer using another superclass.
The easiest way is to create a subclass of an existing component, making your subclass implement the
TableCellRenderer interface. TableCellRenderer requires just one method:
getTableCellRendererComponent. Your implementation of this method should set up the rendering
component to reflect the passed-in state, and then return the component.
In the snapshot of TableDialogEditDemo, the renderer used for Favorite Color cells is a subclass of JLabel
called ColorRenderer. Here are excerpts from ColorRenderer.java that show how it is implemented.
11
}
Here is the code from TableDialogEditDemo.java that registers a ColorRenderer instance as the default
renderer for all Color data:
To specify a cell-specific renderer, you need to define a JTable subclass that overrides the getCellRenderer
method. For example, the following code makes the first cell in the first column of the table use a custom
renderer:
To add a tool tip to a cell using its renderer, you first need to get or create the cell renderer. Then, after making
sure the rendering component is a JComponent, invoke the setToolTipText method on it.
An example of setting tool tips for cells is in TableRenderDemo. Click the Launch button to run it using Java™
Web Start (download JDK 7 or later). Or, to compile and run the example yourself, consult the example index.
The source code is in TableRenderDemo.java. It adds tool tips to the cells of the Sport column with the
following code:
Although the tool tip text in the previous example is static, you can also implement tool tips whose text changes
depending on the state of the cell or program. Here are a couple ways to do so:
12
Add a bit of code to the renderer's implementation of the getTableCellRendererComponent method.
Override the JTable method getToolTipText(MouseEvent).
An example of adding code to a cell renderer is in TableDialogEditDemo. Click the Launch button to run it
using Java™ Web Start (download JDK 7 or later). Or, to compile and run the example yourself, consult the
example index.
TableDialogEditDemo uses a renderer for colors, implemented in ColorRenderer.java, that sets the tool tip
text using the boldface code in the following snippet:
You can specify tool tip text by overriding JTable's getToolTipText(MouseEvent) method. The program
TableToolTipsDemo shows how. Click the Launch button to run it using Java™ Web Start (download JDK 7 or
later). Or, to compile and run the example yourself, consult the example index.
The cells with tool tips are in the Sport and Vegetarian columns. Here is a picture of its tool tip:
13
Here is the code from TableToolTipsDemo.java that implements tool tips for cells in the Sport and
Vegetarian columns:
The code is fairly straightforward, except perhaps for the call to convertColumnIndexToModel. That call is
necessary because if the user moves the columns around, the view's index for the column will not match the
model's index for the column. For example, the user might drag the Vegetarian column (which the model
considers to be at index 4) so it is displayed as the first column — at view index 0. Since prepareRenderer
provides the view index, you need to translate the view index to a model index so you can be sure the intended
column has been selected.
14
An example of using the same tool tip text for all column headers is in TableSorterDemo.java. Here is how it
sets the tool tip text:
table.getTableHeader().setToolTipText(
"Click to sort; Shift-Click to sort in reverse order");
TableToolTipsDemo.java has an example of implementing column header tool tips that vary by column. If
you run TableToolTipsDemo (click the Launch button) using Java™ Web Start (download JDK 7 or later). Or,
to compile and run the example yourself, consult the example index.
You will see the tool tips when you mouse over any column header except for the first two. No tool tips were
suppled for the name columns since they seemed self-explanatory. Here is a picture of one of the column header
tool tips:
The following code implements the tool tips. Basically, it creates a subclass of JTableHeader that overrides the
getToolTipText(MouseEvent) method so that it returns the text for the current column. To associate the
revised table header with the table, the JTable method createDefaultTableHeader is overridden so that it
returns an instance of the JTableHeader subclass.
15
Sorting and Filtering
Table sorting and filtering is managed by a sorter object. The easiest way to provide a sorter object is to set
autoCreateRowSorter bound property to true:
This action defines a row sorter that is an instance of javax.swing.table.TableRowSorter. This provides a
table that does a simple locale-specific sort when the user clicks on a column header. This is demonstrated in
TableSortDemo.java, as seen in this screen shot:
To have more control over sorting, you can construct an instance of TableRowSorter and specify that it is the
sorter object for your table.
TableRowSorter<TableModel> sorter
= new TableRowSorter<TableModel>(table.getModel());
table.setRowSorter(sorter);
TableRowSorter uses java.util.Comparator objects to sort its rows. A class that implements this interface
must provide a method called compare that defines how any two objects are compared for the purpose of
sorting. For example, the following code creates a Comparator that sorts a set of strings by the last word in each
string:
To determine which Comparator to use for a column, TableRowSorter attempts to apply each of the following
rules in turn. Rules are followed in the order listed below; the first rule that provides the sorter with a
Comparator is used, and the remainining rules ignored.
16
2. If the table model reports that the column data consists of strings (TableModel.getColumnClass
returns String.class for that column), use a comparator that sorts the strings based on the current
locale.
3. If the column class returned by TableModel.getColumnClass implements Comparable, use a
comparator that sorts the strings based on the values returned by Comparable.compareTo.
4. If a string convertor has been specified for the table by invoking setStringConverter, use a
comparator that sorts the resulting string representations based on the current locale.
5. If none of the previous rules apply, use a comparator that invokes toString on the column data and
sorts the resulting strings based on the current locale.
For more sophisticated kinds of sorting, subclass TableRowSorter or its parent class
javax.swing.DefaultRowSorter.
To specify the sort order and sort precedence for columns, invoke setSortKeys. Here is an example that sorts
the table used in the examples by the first two columns. The precedence of the columns in the sort is indicated
by the order of the sort keys in the sort key list. In this case, the second column has the first sort key, so they
rows are sorted by first name, then last name.
In addition to reordering the results, a table sorter can also specify which rows will be displayed. This is known
as filtering. TableRowSorter implements filtering using javax.swing.RowFilter objects. RowFilter
implements several factory methods that create common kinds of filters. For example, regexFilter returns a
RowFilter that filters based on a regular expression.
In the following example code, you explicitly create a sorter object so you can later use it to specify a filter:
In a subsequent example, newFilter() is invoked every time the text field changes. When the user enters
complicated regular expressions, the try...catch prevents the syntax exception from interfering with input.
17
When a table uses a sorter, the data the users sees may be in a different order than that specified by the data
model, and may not include all rows specified by the data model. The data the user actually sees is known as the
view, and has its own set of coordinates. JTable provides methods that convert from model coordinates to view
coordinates — convertColumnIndexToView and convertRowIndexToView — and that convert from view
coordinates to model coordinates — convertColumnIndexToModel and convertRowIndexToModel.
The following example brings together the ideas discussed in this section. TableFilterDemo.java adds a small
number of changes to TableDemo. These include the code snippets earlier in this section, which provide a sorter
for the main table, and use a text field to supply the filtering regular expression. The following screen shot
shows TableFilterDemo before any sorting or filtering has been done. Notice that row 3 in the model is still
the same as row 3 in the view:
If the user clicks twice on the second column, the fourth row becomes the first row — but only in the view:
As previously noted, the text the user enters in the "Filter Text" text field defines a filter that determines which
rows are shown. As with sorting, filtering can cause view coordinates to diverge from model coordinates:
18
Here is the code that updates the status field to reflect the current selection:
table.getSelectionModel().addListSelectionListener(
new ListSelectionListener() {
public void valueChanged(ListSelectionEvent event) {
int viewRow = table.getSelectedRow();
if (viewRow < 0) {
//Selection got filtered away.
statusText.setText("");
} else {
int modelRow =
table.convertRowIndexToModel(viewRow);
statusText.setText(
String.format("Selected Row in view: %d. " +
"Selected Row in model: %d.",
viewRow, modelRow));
}
}
}
);
19
The preceding code is from TableRenderDemo.java. You can run TableRenderDemo (click the Launch button)
using Java™ Web Start (download JDK 7 or later). Or, to compile and run the example yourself, consult the
example index.
What if you want to specify an editor other than a text field, check box, or combo box? As DefaultCellEditor
does not support other types of components, you must do a little more work. You need to create a class that
implements the TableCellEditor interface. The AbstractCellEditor class is a good superclass to use. It
implements TableCellEditor's superinterface, CellEditor, saving you the trouble of implementing the event
firing code necessary for cell editors.
Your cell editor class needs to define at least two methods — getCellEditorValue and
getTableCellEditorComponent. The getCellEditorValue method, required by CellEditor, returns the
cell's current value. The getTableCellEditorComponent method, required by TableCellEditor, should
configure and return the component that you want to use as the editor.
Here is a picture of a table with a dialog that serves, indirectly, as a cell editor. When the user begins editing a
cell in the Favorite Color column, a button (the true cell editor) appears and brings up the dialog, with which
the user can choose a different color.
20
You can run TableDialogEditDemo (click the Launch button) using Java™ Web Start (download JDK 7 or
later). Or, to compile and run the example yourself, consult the example index.
Here is the code, taken from ColorEditor.java, that implements the cell editor.
public ColorEditor() {
button = new JButton();
button.setActionCommand(EDIT);
button.addActionListener(this);
button.setBorderPainted(false);
21
}
As you can see, the code is pretty simple. The only part that is a bit tricky is the call to fireEditingStopped at
the end of the editor button's action handler. Without this call, the editor would remain active, even though the
modal dialog is no longer visible. The call to fireEditingStopped lets the table know that it can deactivate the
editor, letting the cell be handled by the renderer again.
The automatic checking of user-entered strings occurs when the default editor attempts to create a new instance
of the class associated with the cell's column. The default editor creates this instance using a constructor that
takes a String as an argument. For example, in a column whose cells have type Integer, when the user types
in "123" the default editor creates the corresponding Integer using code equivalent to new Integer("123").
If the constructor throws an exception, the cell's outline turns red and refuses to let focus move out of the cell. If
you implement a class used as a column data type, you can use the default editor if your class supplies a
constructor that takes a single argument of type String.
If you like having a text field as the editor for a cell, but want to customize it — perhaps to check user-entered
text more strictly or to react differently when the text is invalid — you can change the cell editor to use a
22
formatted text field. The formatted text field can check the value either continuously while the user is typing or
after the user has indicated the end of typing (such as by pressing Enter).
The following code, taken from a demo named TableFTFEditDemo.java, sets up a formatted text field as an
editor that limits all integer values to be between 0 and 100. You can run TableFTFEditDemo (click the Launch
button) using Java™ Web Start (download JDK 7 or later). Or, to compile and run the example yourself, consult
the example index.
The following code makes the formatted text field the editor for all columns that contain data of type Integer.
table.setDefaultEditor(Integer.class,
new IntegerEditor(0, 100));
The override of getTableCellEditorComponent sets the formatted text field's value property (and not just the
text property it inherits from JTextField) before the editor is shown. The override of getCellEditorValue
keeps the cell value as an Integer, rather than, say, the Long value that the formatted text field's parser tends to
return. Finally, overriding stopCellEditing lets you check whether the text is valid, possibly stopping the
editor from being dismissed. If the text isn't valid, your implementation of stopCellEditing puts up a dialog
that gives the user the option of continuing to edit or reverting to the last good value. The source code is a bit
too long to include here, but you can find it in IntegerEditor.java.
Printing
JTable provides a simple API for printing tables. The easiest way to print out a table is to invoke
JTable.print with no arguments:
try {
if (! table.print()) {
System.err.println("User cancelled printing");
}
} catch (java.awt.print.PrinterException e) {
System.err.format("Cannot print %s%n", e.getMessage());
}
Invoking print on a normal Swing application brings up a standard printing dialog box. (On a headless
application, the table is simply printed.) The return value indicates whether the user went ahead with the print
job or cancelled it. JTable.print can throw java.awt.print.PrinterException, which is a checked
exception; that's why the above example uses a try ... catch.
JTable provides several overloads of print with various options. The following code from
TablePrintDemo.java shows how to define a page header:
23
MessageFormat header = new MessageFormat("Page {0,number,integer}");
try {
table.print(JTable.PrintMode.FIT_WIDTH, header, null);
} catch (java.awt.print.PrinterException e) {
System.err.format("Cannot print %s%n", e.getMessage());
}
For more sophisticated printing applications, use JTable.getPrintable to obtain a Printable object for the
table. For more on Printable, refer to the Printing lesson in the 2D Graphics trail.
TableDemo
Creating a Table
A basic table with a custom model.
Model
Using an Editor to
TableFTFEditDemo
Modifies TableDemo to use a custom editor (a formatted text
Validate User-
field variant) for all Integer data.
Entered Text
Modifies TableDemo to use a custom editor (a combo box) for
TableRenderDemo
Using a Combo Box
all data in the Sport column. Also intelligently picks column
as an Editor
sizes. Uses renderers to display tool tips for the sport cells.
Modifies TableDemo to have a cell renderer and editor that
TableDialogEditDemo Using Other Editors display a color and let you choose a new one, using a color
chooser dialog.
Specifying Tool
Tips for Cells,
TableToolTipsDemo
Demonstrates how to use several techniques to set tool tip text
Specifying Tool
for cells and column headers.
Tips for Column
Headers,
TableSortDemo
Demonstrates the default sorter, which allows the user to sort
Sorting and Filtering
columns by clicking on their headers.
TableFilterDemo
Demonstrates sorting and filtering, and how this can cause the
Sorting and Filtering
view coordinates to diverge from the model coordinates.
TablePrintDemo Printing Demonstrates table printing.
ListSelectionDemo
How to Write a List Shows how to use all list selection modes, using a list selection
Selection Listener listener that's shared between a table and list.
24
Builds on ListSelectionDemo making the data model be
SharedModelDemo Nowhere shared between the table and list. If you edit an item in the first
column of the table, the new value is reflected in the list.
25