Java Forum / GUI / May 2006
Problem with JTable and DefaultCellEditor
michael.o'connor@bluescopesteel.com - 24 May 2006 23:01 GMT I have a JTable with editable columns, some of which should accept only numbers. To do this, I have added a DefaultCellEditor to the numeric columns, with a JTextField to accept user input, and a KeyListener to filter out non-numeric keystrokes from the JTextField.
The user can start editing by mouse-clicking in a cell and typing, or by navigating around the table using the arrow keys, then type in a cell, without using the mouse.
All this works well, except in the second case when the user types in a cell without first selecting it with the mouse. The problem here is that the KeyListener does not see the first keystroke, but does see subsequent ones. That means one non-numeric character can be entered into the cell. However, the KeyListener always works if the user first selects the cell with a mouse-click. Below is some code showing this behaviour. What am I missing? How can I capture that first keystroke? I am using Java 1.5.
import java.awt.*; import java.awt.event.*; import javax.swing.*; import javax.swing.table.*;
public class Test extends JFrame { public Test() { JTable table = new JTable(new Object[][] { {"a",1,2},{"b",3,4} }, new String[] {"Text", "Num 1", "Num 2"}); table.setRowSelectionAllowed(false); table.setSurrendersFocusOnKeystroke(true);
TableColumnModel cm = table.getColumnModel(); cm.getColumn(1).setCellEditor(new MyCellEditor()); cm.getColumn(2).setCellEditor(new MyCellEditor());
JScrollPane scrollPane = new JScrollPane(table); getContentPane().add(scrollPane); }
private class MyCellEditor extends DefaultCellEditor { private final JTextField tf;
public MyCellEditor() { super(new JTextField()); tf = (JTextField) getComponent(); tf.addKeyListener(new MyKeyListener ()); setClickCountToStart(1); }
public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) { tf.setText(value.toString()); return tf; }
public Object getCellEditorValue() { return tf.getText(); } }
private class MyKeyListener implements KeyListener { public void keyPressed(KeyEvent e) {} public void keyReleased(KeyEvent e) {} public void keyTyped(KeyEvent e) { if ( ! Character.isDigit(e.getKeyChar()) ) { Toolkit.getDefaultToolkit().beep(); e.consume(); } } }
public static void main(String[] args) { Test frame = new Test(); frame.setDefaultCloseOperation(EXIT_ON_CLOSE); frame.pack(); frame.setLocationRelativeTo(null); frame.setVisible(true); } }
Mike
Vova Reznik - 25 May 2006 15:04 GMT > I have a JTable with editable columns, some of which should accept only > numbers. To do this, I have added a DefaultCellEditor to the numeric [quoted text clipped - 22 lines] > public Test() { > JTable table = new JTable(new Object[][] { {"a",1,2},{"b",3,4} }, {"a",1,2},{"b",3,4} - ??
> new String[] {"Text", "Num 1", "Num 2"}); > table.setRowSelectionAllowed(false); [quoted text clipped - 50 lines] > > Mike You don't need custom editor and KeyListener (but if you want you'd better to use JFormattedTextField which you may pass to a constructor of DefaultCellEditor).
You may create your own TableModel where you need to override getColumnClass and your table will know what to do.
Your class back (different name) with custom table model.
import java.awt.Component; import java.awt.Toolkit; import java.awt.event.KeyEvent; import java.awt.event.KeyListener; import java.util.ArrayList; import java.util.List;
import javax.swing.DefaultCellEditor; import javax.swing.JFrame; import javax.swing.JScrollPane; import javax.swing.JTable; import javax.swing.JTextField; import javax.swing.table.AbstractTableModel; import javax.swing.table.TableColumnModel; import javax.swing.table.TableModel;
public class NumericColumnTester extends JFrame {
public NumericColumnTester() { // JTable table = new JTable(new Object[][] { // { "a", new Integer(1), "2" }, { "b", new Integer(3), "4" } }, // new String[] { "Text", "Num 1", "Num 2" }); String[]header = { "Text", "Num 1", "Num 2" }; List data = new ArrayList(); data.add(new Object[]{ "a", new Integer(1), "2" }); data.add(new Object[]{ "b", new Integer(3), "4" }); TableModel model = new TblModel(data, header); JTable table = new JTable(model); table.setRowSelectionAllowed(false); table.setSurrendersFocusOnKeystroke(true);
// TableColumnModel cm = table.getColumnModel(); // cm.getColumn(1).setCellEditor(new MyCellEditor()); // cm.getColumn(2).setCellEditor(new MyCellEditor());
JScrollPane scrollPane = new JScrollPane(table); getContentPane().add(scrollPane); }
private class MyCellEditor extends DefaultCellEditor {
private final JTextField tf;
public MyCellEditor() { super(new JTextField()); tf = (JTextField) getComponent(); tf.addKeyListener(new MyKeyListener()); setClickCountToStart(1); }
public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) { tf.setText(value.toString()); return tf; }
public Object getCellEditorValue() { return tf.getText(); } }
class TblModel extends AbstractTableModel {
private List data; private String[] header;
public TblModel(List data, String[] header) { this.data = data; this.header = header; }
public boolean isCellEditable(int rowIndex, int columnIndex) { return columnIndex == 1; }
public int getColumnCount() { return header == null ? 0 : header.length; }
public int getRowCount() { return data == null ? 0 : data.size(); }
public Object getValueAt(int rowIndex, int columnIndex) { Object[] row = (Object[]) data.get(rowIndex); return row[columnIndex]; }
public Class getColumnClass(int columnIndex) { if (data == null) { return Object.class; } Object[] row = (Object[]) data.get(0); return row[columnIndex] == null ? Object.class : row[columnIndex].getClass(); }
public String getColumnName(int column) { return header[column]; }
public void setValueAt(Object aValue, int rowIndex, int columnIndex) { Object[] row = (Object[]) data.get(rowIndex); row[columnIndex] = aValue; super.fireTableCellUpdated(rowIndex, columnIndex); }
}
private class MyKeyListener implements KeyListener {
public void keyPressed(KeyEvent e) { }
public void keyReleased(KeyEvent e) { }
public void keyTyped(KeyEvent e) { if (!Character.isDigit(e.getKeyChar())) { Toolkit.getDefaultToolkit().beep(); e.consume(); } } }
public static void main(String[] args) { JFrame frame = new NumericColumnTester(); frame.setDefaultCloseOperation(EXIT_ON_CLOSE); frame.pack(); frame.setLocationRelativeTo(null); frame.setVisible(true); } }
bparanj - 25 May 2006 16:07 GMT I have a similar problem. But in my case, when the user starts editing I want to be able to just clear out the old value and allow the user to provide the new value. Is this possible? Thanks in advance. BP. http://www.ProblemSolvingSkill.net
Vova Reznik - 25 May 2006 16:30 GMT > I have a similar problem. But in my case, when the user starts editing > I want to be able to just clear out the old value and allow the user to > provide the new value. Is this possible? Thanks in advance. > BP. > http://www.ProblemSolvingSkill.net override JTable# public Component prepareEditor(TableCellEditor, int, int)
Component, if you didn't do your own, is always JTextComponent.
JTextComponent comp = super.prepareEditor(TableCellEditor, int, int);
comp.setText(null);
return comp;
bparanj - 25 May 2006 16:50 GMT import java.awt.Component;
import javax.swing.JTable; import javax.swing.table.TableCellEditor; import javax.swing.table.TableModel; import javax.swing.text.JTextComponent;
public class MyTable extends JTable {
public MyTable(TableModel dm) { super(dm); }
public Component prepareEditor(TableCellEditor editor,int row,int col) { JTextComponent comp = (JTextComponent)super.prepareEditor(editor, row, col); comp.setText(null);
return comp; }
}
This code works. Do I need to provide other methods and delegate the calls to JTable? Thanks. BP http://www.ProblemSolvingSkill.net
Vova Reznik - 25 May 2006 17:11 GMT > import java.awt.Component; > [quoted text clipped - 24 lines] > BP > http://www.ProblemSolvingSkill.net It is your choice. May you will need another constructor or ... I would add check if editor is JTextComponent.
bparanj - 25 May 2006 17:25 GMT Thanks. Actually two of my cells in my table allow numeric or hyphen characters only. So I have been experimenting with your code. It seems like that since the default implementation of JTable does not provide the validation that checks if the entered value is numeric or hyphen, I have to write my own custom editor right?
So if I do that, I am back to square one again. It is showing the first character even if is not a legal character and then it starts beeping and prevents entering of illegal characters. How do I resolve this issue? Thanks for your time again!
BP http://www.ProblemSolvingSkill.net
Vova Reznik - 25 May 2006 17:37 GMT > Thanks. Actually two of my cells in my table allow numeric or hyphen > characters only. So I have been experimenting with your code. It seems [quoted text clipped - 9 lines] > BP > http://www.ProblemSolvingSkill.net Stay here and don't leave. I'll write anything for you.
steve - 25 May 2006 23:37 GMT > Thanks. Actually two of my cells in my table allow numeric or hyphen > characters only. So I have been experimenting with your code. It seems [quoted text clipped - 9 lines] > BP > http://www.ProblemSolvingSkill.net no the way to do it is to pass in a reg expression, that sets the formatting. here is an example i use for formatting part numbers.
where you see the regexpression , is where you set the formatting option. all you need to do is , change 1 line and that forces what the user may enter into the text area. just link the class into the table, and you will be forced to enter 1-15 characters. Then all you need to do is play about to get the actual characters you want, (just look at reg expression on google)
package utils.Validation;
import java.awt.Component;
import java.awt.event.ComponentListener; import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
import javax.swing.DefaultCellEditor; import javax.swing.JFormattedTextField; import javax.swing.JTable; import javax.swing.JTextField; import javax.swing.SwingUtilities; import javax.swing.text.DefaultFormatterFactory;
import utils.RegexFormatter;
//this class validates a model number when it is edited in a table //it is used both by the shipment shedule screen & the lc details screen!! public class validateModelInTable extends DefaultCellEditor { JFormattedTextField ftf; public validateModelInTable(){ super(new JFormattedTextField()); ftf = (JFormattedTextField)getComponent(); //ensure every model number is atleast 1 char & no more than 15 //TODO //we need to get this from the database in the calling class!!, instead of getting it from a const. //this should also be "Graph", not "Print" because we do not want to allow spaces RegexFormatter formatter =new RegexFormatter("\\p{Graph}{1,15}"); formatter.setOverwriteMode(false); //set it so sh.t inserts!! ftf.setFormatterFactory(new DefaultFormatterFactory(formatter));
}
//Override to invoke setValue on the formatted text field. public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) { JFormattedTextField ftf = (JFormattedTextField)super.getTableCellEditorComponent( table, value, isSelected, row, column); ftf.setValue(value); return ftf; } //Override to ensure that the value remains an Integer. public Object getCellEditorValue() { JFormattedTextField ftf = (JFormattedTextField)getComponent(); Object o = ftf.getValue();
return o; } public boolean stopCellEditing() { JFormattedTextField ftf = (JFormattedTextField)getComponent(); if (ftf.isEditValid()) { try {
ftf.commitEdit(); } catch (java.text.ParseException exc) { }
return super.stopCellEditing();
}else {return false; } } }
bparanj - 30 May 2006 21:21 GMT Thank you Steve. I actually got some ideas from the Sun Swing forum. The following code is based on that:
package swing.table;
import java.awt.Component;
import javax.swing.JTable; import javax.swing.table.TableCellEditor; import javax.swing.table.TableModel; import javax.swing.text.JTextComponent;
import client.EditorState;
public class MyTable extends JTable {
public MyTable(TableModel dm) { super(dm); }
public Component prepareEditor(TableCellEditor editor, int row, int col) { //If the cell is JComboBox then default behaviour is same as super class. if(col == 2) { return super.prepareEditor(editor, row, col); }
JTextComponent comp = (JTextComponent) super.prepareEditor(editor, row, col); //The setText method makes the old value disappear when a user starts typing in a // cell if(!EditorState.getInstance().isRestoreInProgress()) { comp.setText(null); }
return comp; } }
This table allows the selection of cells in a table. This works fine in most cases. The problem is that when I select a particular cell in a table and open another table to display the data, it copies the value of the old table's cell into the newly opened table cell.
How do I avoid this unwanted copy of old data? Thanks in advance.
BP http://www.ProblemSolvingSkill.net
Free MagazinesGet these publications absolutely FREE for up to 12 months. There are no hidden fees and no obligation. Simply choose a title, complete the application form and submit it. Read more ...
|
|
|