Looking back over the previous two steps (steps 4 and 5), what we did could be summed up as “making an editable list cell”. The renderer gave us the image of a checkbox but it took some effort to coax life into that image. Earlier it was pointed out that JTrees, JTables and JComboBoxes use custom renderers just as JList does. What wasn’t mentioned was that all of them except for the JList are editable and offer a mechanism for customizing editors that is similar to the mechanism for customizing renderers, and that greatly simplifies editing their underlying data models. This chapter’s main program doesn’t require an editable JTable, JTree or JComboBox, so in this brief interlude, we take a quick detour to look at the process for customizing JTree and JTable editors.
JTrees use TreeCellEditors. Table 14-9 shows JTree’s editor-related methods. JTables and TableColumns use TablecellEditors. Table 14-10 shows JTable’s and tableColumn’s editor-related methods.
Method Name |
---|
public void setCellEditor(TreeCellEditor editor) |
public TreeCellEditor getCellEditor() |
Defining Class | Method |
---|---|
JTable | public void setDefaultEditor(Class columnClass, TableCellEditor editor) |
JTable | public TableCellRenderer getDefaultEditor(Class columnClass) |
JTable | public TableCellRenderer getCellEditor(int row, int column) |
TableColumn | public void setHeaderEditor(TableCellEditor headerEditor) |
TableColumn | public TableCellEditor getHeaderEditor() |
TableColumn | public void setCellEditor(TableCellEditor cellEditor) |
TableColumn | public TableCellRenderer getCellEditor() |
Both the TreeCellEditor and TableCellEditor interfaces extend from the CellEditor interface as shown in the class hierarchy in figure 14-9. Table 14-11 shows the methods that the CellEditor interface de nes.
javax.swing.CellEditor javax.swing.tree.TreeCellEditor javax.swing.table.TableCellEditor
Method Name and Purpose |
---|
public Object getCellEditorValue() Returns the value that the editor component contains. |
public boolean isCellEditable(EventObject anEvent) Returns true if the cell should be edited. |
public boolean shouldSelectCell(EventObject anEvent) Returns true if the cell should be selected. |
public boolean stopCellEditing() Call this to stop the editing process and accept the value contained in the editor component. This method returns false ifthe value could not be accepted. |
public void cancelCellEditing() Call this to stop the editing process and discard the value contained in the editor component. |
public void addCellEditorListener(CellEditorListener l) Called automatically when this CellEditor becomes a component’s editor. |
public void removeCellEditorListener(CellEditorListener l) Called automatically when this CellEditor is no longer a component’s editor. |
The TableCellEditor interface extends CellEditor and defines one additional method:
public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column);
The TreeCellEditor interface extends CellEditor and defines the additional method:
public Component getTreeCellEditorComponent(JTree tree, Object value, boolean isSelected, boolean expanded, boolean leaf, int row);
Both these methods must return a configured component that will handle the editing of the value parameter. In both cases, the first parameter is a reference to the faux-composite component itself. The second is the object (obtained from the data model) that is represented by the implicated cell. The remaining parameters identify the state and location of the implicated cell.
As an aid to writing a CellEditor, the javax.swing.AbstractCellEditor class implements CellEditor to provide default implementations of all the methods except for getCellEditorValue. Whether you need an editor for a JTable or a JTree, my recommendation is to extend AbstractCellEditor and implement either the TreeCellEditor or TableCellEditor interface as required. You will have to implement two methods: getCellEditorValue() and the one method declared by the additional interface. You may choose to override other methods if their default behavior is not appropriate. In particular, you might want to override the two methods isCellEditable() and shouldSelectCell() to return different values depending on which element of the data model is implicated by the EventObject. Determining which element is implicated by the EventObject takes a bit of work, though, as the getValueFromEvent() method of DemoTreeOrTableHandler class illustrates (lines 90 - 108 of example 14.12).
Example 14.12: chap14.interlude.DemoTreeOrTableCellHandler.java
1 package chap14.interlude; 2 3 import java.awt.Color; 4 import java.awt.Component; 5 import java.awt.Point; 6 import java.awt.event.ActionEvent; 7 import java.awt.event.ActionListener; 8 import java.awt.event.MouseEvent; 9 import java.util.EventObject; 10 11 import javax.swing.AbstractCellEditor; 12 import javax.swing.JTable; 13 import javax.swing.JTextField; 14 import javax.swing.JTree; 15 import javax.swing.table.TableCellEditor; 16 import javax.swing.table.TableCellRenderer; 17 import javax.swing.tree.TreeCellEditor; 18 import javax.swing.tree.TreeCellRenderer; 19 import javax.swing.tree.TreePath; 20 21 public class DemoTreeOrTableCellHandler 22 extends AbstractCellEditor 23 implements TreeCellEditor, TableCellEditor, TreeCellRenderer, TableCellRenderer { 24 25 private JTextField rendererField; 26 private JTextField editorField; 27 28 public DemoTreeOrTableCellHandler() { 29 rendererField = new JTextField(); 30 rendererField.setBorder(null); 31 rendererField.setForeground(Color.blue); 32 33 editorField = new JTextField(); 34 editorField.setBorder(null); 35 editorField.setForeground(Color.red); 36 37 editorField.addActionListener(new ActionListener() { 38 public void actionPerformed(ActionEvent e) { 39 stopCellEditing(); 40 } 41 }); 42 } 43 public Object getCellEditorValue() { 44 return editorField.getText(); 45 } 46 public Component getTreeCellEditorComponent( 47 JTree tree, 48 Object value, 49 boolean isSelected, 50 boolean expanded, 51 boolean leaf, 52 int row) { 53 54 editorField.setText(String.valueOf(value)); 55 return editorField; 56 } 57 public Component getTableCellEditorComponent( 58 JTable table, 59 Object value, 60 boolean isSelected, 61 int row, 62 int column) { 63 64 editorField.setText(String.valueOf(value)); 65 return editorField; 66 } 67 public Component getTreeCellRendererComponent( 68 JTree tree, 69 Object value, 70 boolean selected, 71 boolean expanded, 72 boolean leaf, 73 int row, 74 boolean hasFocus) { 75 76 rendererField.setText(String.valueOf(value)); 77 return rendererField; 78 } 79 public Component getTableCellRendererComponent( 80 JTable table, 81 Object value, 82 boolean isSelected, 83 boolean hasFocus, 84 int row, 85 int column) { 86 87 rendererField.setText(String.valueOf(value)); 88 return rendererField; 89 } 90 private Object getValueFromEvent(EventObject e) { 91 Object value = null; 92 if (e instanceof MouseEvent) { 93 MouseEvent event = (MouseEvent)e; 94 Point p = event.getPoint(); 95 Object src = e.getSource(); 96 if (src instanceof JTree) { 97 JTree tree = (JTree)src; 98 TreePath path = tree.getClosestPathForLocation(p.x, p.y); 99 value = path.getLastPathComponent(); 100 } else if (src instanceof JTable) { 101 JTable table = (JTable)src; 102 int row = table.rowAtPoint(p); 103 int column = table.columnAtPoint(p); 104 value = table.getValueAt(row, column); 105 } 106 } 107 return value; 108 } 109 }
Following is a demonstration application that creates a JTree and a JTable and a minimal renderer and editor for each. It is comprised of the two classes DemoTreeOrTableCellHandler and DemoFrame.
The DemoTreeOrTableCellHandler class extends AbstractCellEditor. In an attempt to “be all things to all people” it implements TreeCellEditor, TableCellEditor, TreeCellRenderer and TableCellRenderer simultaneously, albeit in a minimal way. This is perhaps putting too much functionality into one class but it is useful for pointing out the overlaps between the various interfaces.
The DemoFrame class illustrates the basics of creating a TableModel and TreeModel as well as demonstrating the use of a custom renderer and editor in trees and tables. Notice that it creates two instances of DemoTreeOrTableHandler – one for the JTree and another for the JTable. Editors should not be shared between components lest the components’ painting processes become confused. For some mischievous fun, rewrite DemoFrame so that the JTree and JTable share the same instance of DemoTreeOrTableHandler. Then run the program, clicking and editing various cells, going back and forth between the JTree and the JTable, to see why sharing an editor isn’t a good idea.
Example 14.13: chap14.interlude.DemoFrame.java
1 package chap14.interlude; 2 3 import java.awt.BorderLayout; 4 import java.awt.GridLayout; 5 6 import javax.swing.BorderFactory; 7 import javax.swing.JFrame; 8 import javax.swing.JPanel; 9 import javax.swing.JScrollPane; 10 import javax.swing.JTable; 11 import javax.swing.JTree; 12 import javax.swing.table.DefaultTableModel; 13 import javax.swing.tree.DefaultMutableTreeNode; 14 import javax.swing.tree.DefaultTreeModel; 15 16 public class DemoFrame extends JFrame { 17 18 public DemoFrame() { 19 super("Tree and Table Editor Demo"); 20 21 DemoTreeOrTableCellHandler handler1 = new DemoTreeOrTableCellHandler(); 22 23 JTable table = createTable(); 24 table.setDefaultRenderer(Object.class, handler1); 25 table.setDefaultEditor(Object.class, handler1); 26 27 DemoTreeOrTableCellHandler handler2 = new DemoTreeOrTableCellHandler(); 28 29 JTree tree = createTree(); 30 tree.setCellRenderer(handler2); 31 tree.setCellEditor(handler2); 32 33 //uncomment to see what happens. 34 //tree.setCellRenderer(handler1); 35 //tree.setCellEditor(handler1); 36 37 JPanel contentPane = new JPanel(); 38 setContentPane(contentPane); 39 contentPane.setLayout(new GridLayout(1, 2)); 40 JPanel treePanel = new JPanel(new BorderLayout()); 41 JPanel tablePanel = new JPanel(new BorderLayout()); 42 tablePanel.add(BorderLayout.CENTER, new JScrollPane(table)); 43 tablePanel.setBorder(BorderFactory.createTitledBorder("Table Demo")); 44 treePanel.add(BorderLayout.CENTER, new JScrollPane(tree)); 45 treePanel.setBorder(BorderFactory.createTitledBorder("Tree Demo")); 46 contentPane.add(treePanel); 47 contentPane.add(tablePanel); 48 49 setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 50 pack(); 51 setVisible(true); 52 53 } 54 private JTable createTable() { 55 Object[][] data = { { "Mickey", "NMI", "Mouse", "mousem" }, { 56 "Donald", "NMI", "Duck", "duckd" }, { 57 "Felix", "the", "Cat", "catf" }, { 58 "Winnie", "ther", "Pooh", "poohw" } 59 }; 60 Object[] columns = { "First", "Middle", "Last", "User Name" }; 61 62 DefaultTableModel model = new DefaultTableModel(data, columns); 63 JTable table = new JTable(model); 64 65 return table; 66 } 67 private JTree createTree() { 68 DefaultMutableTreeNode food = new DefaultMutableTreeNode("Food"); 69 DefaultMutableTreeNode fruits = new DefaultMutableTreeNode("Fruits"); 70 DefaultMutableTreeNode vegetables = 71 new DefaultMutableTreeNode("Vegatables"); 72 DefaultMutableTreeNode apples = new DefaultMutableTreeNode("Apples"); 73 DefaultMutableTreeNode pears = new DefaultMutableTreeNode("Pears"); 74 DefaultMutableTreeNode cucumbers = new DefaultMutableTreeNode("Cucumbers"); 75 DefaultMutableTreeNode tomatoes = new DefaultMutableTreeNode("Tomatoes"); 76 food.add(fruits); 77 food.add(vegetables); 78 fruits.add(apples); 79 fruits.add(pears); 80 vegetables.add(cucumbers); 81 vegetables.add(tomatoes); 82 83 DefaultTreeModel model = new DefaultTreeModel(food); 84 JTree tree = new JTree(model); 85 tree.setEditable(true); 86 87 return tree; 88 } 89 public static void main(String[] arg) { 90 new DemoFrame().setVisible(true); 91 } 92 }
Use the following commands to compile and execute the example. From the directory containing the src folder:
javac –d classes -sourcepath src src/chap14/interlude/DemoFrame.java java –cp classes chap14/interlude/DemoFrame
JTables, JTrees and JComboBoxes are able to edit their underlying data models, and they provide methods for plugging in custom editors through a mechanism that is similar to plugging in custom renderers. To create a custom editor for a JTree or JTable, it is easiest to extend AbstractCellEditor and implement either the TreeCellEditor or the TableCellEditor interface as required.