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

Figure 1.
Figure 2.

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

Tony Colston has been programming
professionally since 1991, beginning with the development of ATMs
and debit cards. He now works for Tennessee-based Buckman Labs,
where he spends his days dreaming up new ways to distribute reports
in realtime over the Web. His hobbies include playing basketball
(badly), and playing Quake III and Diablo II. When he is not being
a nerd, he spends his time worshiping his wife Beth who, strangely,
thinks nerds are cool. You can check out his Webpage at

Source: www.infoworld.com