Java Tip 102: Add multiple JTable cell editors per column
Extend and reuse JTable for property pages
By default JTable
does not offer the capability to have more than one editor per column. A Visual Basic-like property page is one place where you would need more than one editor in a column. Fortunately, the class design for JTable
lets you extend JTable
‘s functionality to include a per-row editor with minimal code.
What is a TableCellEditor?
TableCellEditor
objects define how data in JTable
cells are edited. The concept of a TableCellEditor
is captured in Java as an interface: javax.swing.table.TableCellEditor
. That interface defines a single method that returns a Component. The method is called by JTable
whenever JTable
determines that a particular cell is being edited. Once the method returns a Component, the Component is resized to fit into the table cell and then displayed onto the appropriate JTable
cell.
You can use the method JTable.setDefaultEditor(Class, TableCellEditor)
to setup multiple editors in JTable
based on the class of data items contained in it. However, internally, JTable
only considers the possibility that a column will hold only one class. That assumption is demonstrated by the interface javax.swing.table.AbstractTableModel
, where the method getColumnClass(int)
defines that a particular column has only one class.
Fortunately, JTable
uses the method getCellEditor(int, int)
to determine a TableCellEditor
for a particular cell. In this tip, I will override that method to extend the functionality and allow TableCellEditor
s to be based on the row index.
Where do you store the new editors for JTable?
I created a new class called RowEditorModel
that is basically a wrapper around the Hashtable that holds TableCellEditor
s. Each editor is associated with an Integer object that represents the index of the row for which the editor should be used.
The code for RowEditorModel
is listed below:
1 import javax.swing.table.*;
2 import java.util.*;
3 public class RowEditorModel
4 {
5 private Hashtable data;
6 public RowEditorModel()
7 {
8 data = new Hashtable();
9 }
10 public void addEditorForRow(int row, TableCellEditor e )
11 {
12 data.put(new Integer(row), e);
13 }
14 public void removeEditorForRow(int row)
15 {
16 data.remove(new Integer(row));
17 }
18 public TableCellEditor getEditor(int row)
19 {
20 return (TableCellEditor)data.get(new Integer(row));
21 }
22 }
Users register new editors with the
addEditorForRow()
method on line 10. The
RowEditorModel
also allows the user to delete an editor for a row. And finally on line 18 there is an accessor that returns an editor based on a row index. Notice that the
RowEditorModel
does not make reference to a
JTable
in any way. The other changes that must be made are to the
JTable
itself. Below is a code listing for the new version of
JTable
, called
JTableX
.
1 import javax.swing.*;
2 import javax.swing.table.*;
3 import java.util.Vector;
4
5 public class JTableX extends JTable
6 {
7 protected RowEditorModel rm;
8
9 public JTableX()
10 {
11 super();
12 rm = null;
13 }
14
15 public JTableX(TableModel tm)
16 {
17 super(tm);
18 rm = null;
19 }
20
21 public JTableX(TableModel tm, TableColumnModel cm)
22 {
23 super(tm,cm);
24 rm = null;
25 }
26
27 public JTableX(TableModel tm, TableColumnModel cm,
28 ListSelectionModel sm)
29 {
30 super(tm,cm,sm);
31 rm = null;
32 }
33
34 public JTableX(int rows, int cols)
35 {
36 super(rows,cols);
37 rm = null;
38 }
39
40 public JTableX(final Vector rowData, final Vector columnNames)
41 {
42 super(rowData, columnNames);
43 rm = null;
44 }
45
46 public JTableX(final Object[][] rowData, final Object[] colNames)
47 {
48 super(rowData, colNames);
49 rm = null;
50 }
51
52 // new constructor
53 public JTableX(TableModel tm, RowEditorModel rm)
54 {
55 super(tm,null,null);
56 this.rm = rm;
57 }
58
59 public void setRowEditorModel(RowEditorModel rm)
60 {
61 this.rm = rm;
62 }
63
64 public RowEditorModel getRowEditorModel()
65 {
66 return rm;
67 }
68
69 public TableCellEditor getCellEditor(int row, int col)
70 {
71 TableCellEditor tmpEditor = null;
72 if (rm!=null)
73 tmpEditor = rm.getEditor(row);
74 if (tmpEditor!=null)
75 return tmpEditor;
76 return super.getCellEditor(row,col);
77 }
78 }
Most of the code in the above listing consists of constructor calls. I have included all of the constructors that JTable
defines, plus an extra one that will let the user create a JTable
with an associated RowEditorModel
(lines 53-57). Optionally, you can add the RowEditorModel
after the JTable
is constructed. In general, you want to assign the RowEditorModel
, either by using the new constructor or the setRowEditorModel
method, before the JTable
is displayed.
Most of the action occurs in the overridden method getCellEditor
. When JTableX
determines that a TableCellEditor
for a cell is needed, the code will then check the RowEditorModel
(line 72 and 73) to determine first the correct TableCellEditor
. If no TableCellEditor
is returned from the RowEditorModel
, then the method defaults to the version of getCellEditor
in the base class, which is JTable
.
I have included a small example program that demonstrates how to use the new JTableX
. The property pages look like the following:
Here is the code:
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.table.*;
import javax.swing.border.*;
public class PropPageTest extends JPanel
{
private JComboBox b;
private JTableX table;
private DefaultTableModel model;
private String[] col_names = {"Name", "Value"};
private String[] anchor_values = { "CENTER", "NORTH", "NORTHEAST",
"EAST", "SOUTHEAST", "SOUTH",
"SOUTHWEST", "WEST", "NORTHWEST" };
private String[] fill_values = { "NONE", "HORIZONTAL", "VERTICAL",
"BOTH" };
private void createGUI()
{
setLayout(new BorderLayout());
setBorder(BorderFactory.createBevelBorder(BevelBorder.LOWERED));
b = new JComboBox();
model = new DefaultTableModel(col_names,12)
{
public String[] prop_names = { "Name", "Anchor", "Fill",
"GridHeight", "GridWidth",
"GridX", "GridY", "Insets",
"Ipadx", "Ipady",
"WeightX", "WeightY" };
public Object getValueAt(int row, int col)
{
if (col==0)
return prop_names[row];
return super.getValueAt(row,col);
}
public boolean isCellEditable(int row, int col)
{
if (col==0)
return false;
return true;
}
};
table = new JTableX(model);
table.setRowSelectionAllowed(false);
table.setColumnSelectionAllowed(false);
// create a RowEditorModel... this is used to hold the extra
// information that is needed to deal with row specific editors
RowEditorModel rm = new RowEditorModel();
// tell the JTableX which RowEditorModel we are using
table.setRowEditorModel(rm);
// create a new JComboBox and DefaultCellEditor to use in the
// JTableX column
JComboBox cb = new JComboBox(anchor_values);
DefaultCellEditor ed = new DefaultCellEditor(cb);
// tell the RowEditorModel to use ed for row 1
rm.addEditorForRow(1,ed);
// create a new JComboBox and editor for a different row
cb = new JComboBox(fill_values);
ed = new DefaultCellEditor(cb);
// inform the RowEditorMode of the situation
rm.addEditorForRow(2,ed);
add(b, BorderLayout.NORTH);
add(table, BorderLayout.CENTER);
}
public PropPageTest()
{
createGUI();
}
public static void main(String[] args)
{
JFrame f = new JFrame("test");
f.setSize(300,350);
f.getContentPane().add(new PropPageTest(), BorderLayout.CENTER);
f.addWindowListener(new WindowAdapter()
{
public void windowClosing(WindowEvent e)
{
System.exit(0);
}
});
f.setVisible(true);
}
}
Conclusion
JTable
is a flexible and well-written component but it does not, by default, support the use of multiple TableCellEditor
s per column. Because the Swing designers wrote JTable
with such flexibility, I was able to extend it with little code and create a new version of JTable
that does support multiple editors per column.