How to mark JTable cell input as invalid?
The private static class JTable.GenericEditor
uses introspection to catch exceptions raised by constructing specific Number
subclasses with invalid String
values. If you don't need such generic behavior, consider creating PositiveIntegerCellEditor
as a subclass of DefaultCellEditor
. Your stopCellEditing()
method would be correspondingly simpler.
Addendum: Updated to use RIGHT
alignment and common error code.
Addendum: See also Using an Editor to Validate User-Entered Text.
private static class PositiveIntegerCellEditor extends DefaultCellEditor {
private static final Border red = new LineBorder(Color.red);
private static final Border black = new LineBorder(Color.black);
private JTextField textField;
public PositiveIntegerCellEditor(JTextField textField) {
super(textField);
this.textField = textField;
this.textField.setHorizontalAlignment(JTextField.RIGHT);
}
@Override
public boolean stopCellEditing() {
try {
int v = Integer.valueOf(textField.getText());
if (v < 0) {
throw new NumberFormatException();
}
} catch (NumberFormatException e) {
textField.setBorder(red);
return false;
}
return super.stopCellEditing();
}
@Override
public Component getTableCellEditorComponent(JTable table,
Object value, boolean isSelected, int row, int column) {
textField.setBorder(black);
return super.getTableCellEditorComponent(
table, value, isSelected, row, column);
}
}
I figured it out. Override the DefaultCellEditor and return false
/ set the border to red if the number given is not positive.
Unfortunately, since JTable.GenericEditor is static
w/ default
scope, I'm unable to override the GenericEditor
to provide this functionality and have to re-implement it w/ a few tweaks, unless someone has a better way of doing this, which I'd like to hear.
@SuppressWarnings("serial")
class PositiveNumericCellEditor extends DefaultCellEditor {
Class[] argTypes = new Class[]{String.class};
java.lang.reflect.Constructor constructor;
Object value;
public PositiveNumericCellEditor() {
super(new JTextField());
getComponent().setName("Table.editor");
((JTextField)getComponent()).setHorizontalAlignment(JTextField.RIGHT);
}
public boolean stopCellEditing() {
String s = (String)super.getCellEditorValue();
if ("".equals(s)) {
if (constructor.getDeclaringClass() == String.class) {
value = s;
}
super.stopCellEditing();
}
try {
value = constructor.newInstance(new Object[]{s});
if (value instanceof Number && ((Number) value).doubleValue() > 0)
{
return super.stopCellEditing();
} else {
throw new RuntimeException("Input must be a positive number.");
}
}
catch (Exception e) {
((JComponent)getComponent()).setBorder(new LineBorder(Color.red));
return false;
}
}
public Component getTableCellEditorComponent(JTable table, Object value,
boolean isSelected,
int row, int column) {
this.value = null;
((JComponent)getComponent()).setBorder(new LineBorder(Color.black));
try {
Class type = table.getColumnClass(column);
if (type == Object.class) {
type = String.class;
}
constructor = type.getConstructor(argTypes);
}
catch (Exception e) {
return null;
}
return super.getTableCellEditorComponent(table, value, isSelected, row, column);
}
public Object getCellEditorValue() {
return value;
}
}
This code is a small improvement of the accepted answer. If the user does not enter any value, clicking on another cell should allow him to select another cell. The accepted solution does not allow this.
@Override
public boolean stopCellEditing() {
String text = field.getText();
if ("".equals(text)) {
return super.stopCellEditing();
}
try {
int v = Integer.valueOf(text);
if (v < 0) {
throw new NumberFormatException();
}
} catch (NumberFormatException e) {
field.setBorder(redBorder);
return false;
}
return super.stopCellEditing();
}
This solution checks for empty text. In case of an empty text, we call the stopCellEditing()
method.