- Making the Table Editable
- The Cell Renderer
- The Cell Editor
- Conclusion
The Cell Renderer
Now that the JTable is editable, I can move on to the more complex issue. When I am dealing with simple strings in my Widget object, I am not too concerned with the way they look in the tableboth when being displayed and when being edited. Both states are basically the same. In the case of money, time, or a plethora of other situations, however, the appearance of a given value can be quite different while being displayed and edited. In addition, when I am editing a field that contains money, I want to make sure that the user does not input something unacceptable. Finally, I may want to make a column so that the user can select a value only from a list of values instead of a free form field.
To begin, I create a simple test frame that will load my table model and place it into a JTable to be displayed:
public class MyFrame extends JFrame { MyTableModel mtm; public MyFrame() { JPanel p = new JPanel(new BorderLayout()); ArrayList arrayList = loadData(); mtm = new MyTableModel(arrayList); JTable temp = initializeTable(); p.add(new JScrollPane(temp), BorderLayout.CENTER); setContentPane(p); pack(); } public JTable initializeTable() { JTable _table = new JTable(mtm); return _table; } private ArrayList loadData() { ArrayList al = new ArrayList(); Widget w = new Widget(); w.setName("TestName"); w.setQuantity(1); w.setValue(1.01); w.setLocation("South Store"); al.add(w); w = new Widget(); w.setName("Test Widget 2"); w.setQuantity(10); w.setValue(2.00); w.setLocation("North Store"); al.add(w); return al; } }
Next, I will make my value column display the value as currency instead of a double. To do this, I create a TableCellRenderer to handle the actual display of this cell in each row. Here is the code:
class MyCurrencyRenderer extends DefaultTableCellRenderer { NumberFormat currencyFormat; public MyCurrencyRenderer(NumberFormat cf) { currencyFormat = cf; } public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int col) { String _formattedValue; Double _value = (Double)value; if (value == null) { _formattedValue = "Not Set"; } else { _formattedValue = currencyFormat.format(_value); } JLabel testLabel = new JLabel(_formattedValue, SwingConstants.RIGHT); if (isSelected) { testLabel.setBackground(table.getSelectionBackground()); testLabel.setOpaque(true); testLabel.setForeground(table.getSelectionForeground()); } if (hasFocus) { testLabel.setForeground(table.getSelectionBackground()); testLabel.setBackground(table.getSelectionForeground()); testLabel.setOpaque(true); } return testLabel; } }
First, I have extended DefaultTableCellRenderer to avoid having to reimplement any methods that I don't want to change from the default functionality. The only method I am interested in is getTableCellRendererComponent. As you can see, there is a great deal of information being passed into this method. For this demonstration, I will not be using all of it, but you can see how this renderer can be extended to handle more than one data type.
First, I will cast the generic object that is being passed in for clarity. Next, I check to make sure that the value is not null. If it is, I will display a text message (I could just as easily set this to 0 instead). Next, I construct the JLabel that will be used to do the actual rendering of the cell by passing in the formatted String and setting the justification to right. The next bit sets the proper colors for this cell. Because I am going with the default look, I ask the table for the proper colors. Setting the JLabel to opaque is required; otherwise, the background color will not show because a JLabel defaults to a fully transparent background.
The next step is to tell my JTable to use this renderer instead of the default. To do this I alter the initializeTable() method of my frame:
public JTable initializeTable() { JTable _table = new JTable(mtm); NumberFormat _format = NumberFormat.getCurrencyInstance(); MyCurrencyRenderer _renderer = new MyCurrencyRenderer(_format); _table.setDefaultRenderer(Double.class, _renderer); return _table; }
Note that a JTable determines which renderer to use based on the class of object contained within that column. In this situation, I can set the table to use MyCurrencyRenderer to the default for handling doubles because my widget has only one double column. If I have more than one double, I can also tell the table to use this renderer for only a single column:
public JTable initializeTable() { JTable _table = new JTable(mtm); NumberFormat _format = NumberFormat.getCurrencyInstance(); MyCurrencyRenderer _renderer = new MyCurrencyRenderer(_format); TableColumnModel _model = _table.getColumnModel(); TableColumn _column = _model.getColumn(1); _column.setCellRenderer(_renderer); return _table; }
The other option is to code the renderer so that it can handle more than one column. Because the row and column are passed into getTableCellRendererComponent method, this is simple enough to do.