Problem with JTable and DefaultCellEditor

M

michael.o'connor

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
 
V

Vova Reznik

michael.o'[email protected] said:
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} },

{"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

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);
}
}
 
V

Vova Reznik

bparanj said:
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;
 
B

bparanj

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
 
V

Vova Reznik

bparanj said:
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

It is your choice. May you will need another constructor or ...
I would add check if editor is JTextComponent.
 
B

bparanj

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
 
V

Vova Reznik

bparanj said:
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

Stay here and don't leave.
I'll write anything for you.
 
S

steve

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

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 shit 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;
}


}

}
 
B

bparanj

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
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,982
Messages
2,570,190
Members
46,736
Latest member
zacharyharris

Latest Threads

Top