How to mark JTable cell input as invalid?
Asked Answered
A

4

11

If I take a JTable and specify a column's classtype on it's model as follows:

   DefaultTableModel model = new DefaultTableModel(columnNames, 100) {
       @Override
        public Class<?> getColumnClass(int columnIndex) {
            return Integer.class;
        }};

Then whenever a user tries to enter a double value into the table, Swing automatically rejects the input and sets the cell's outline to red.

I want the same effect to occur when someone enters a 'negative or 0' input to the cell. I've got this:

    @Override
    public void setValueAt(Object val, int rowIndex, int columnIndex) {
       if (val instanceof Number && ((Number) val).doubleValue() > 0) {
              super.setValueAt(val, rowIndex, columnIndex);
            } 
       }
   }

This prevents the cell from accepting any non-positive values, but it doesn't set the color to red and leave the cell as editable.

I tried looking into how JTable's doing the rejection by default, but I can't seem to find it.

How can I make it reject the non-positive input the same way it rejects the non-Integer input?

Ayres answered 23/9, 2011 at 15:41 Comment(0)
U
15

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.

enter image description here

    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);
    }
}
Unlimber answered 24/9, 2011 at 13:23 Comment(4)
Thanks this is much cleaner. I did go back and update my code to be less generic, since I was only seeking to use it in a specific place where I could control its use.Ayres
@Unlimber +1 hey trashgod is it a good idea to use a documentFilter to alternative to don't let user types?Bareilly
@nachokk: Yes, depending on the context; otherwise a beeping red box can be a little annoying. :-)Unlimber
I was thinking too that if you have your own AbstractTableModel in setValueAt(row,column) you can validate there if it's a business logic, but then you loose beeping red box :PBareilly
A
2

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;
        }
    }
Ayres answered 23/9, 2011 at 16:59 Comment(1)
+1 for genericity. You could try passing a Class Literal as Runtime-Type Token to the constructor, but it's not especially simpler.Unlimber
F
2

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.

Fleta answered 10/3, 2014 at 15:28 Comment(0)
G
0

So first I created an analogy to make this topic easier to be understood.
We have a pen(editor). This pen will need some ink(The component that the editor use, an example of a component is JTextField,JComboBox and so on) to write.

Then this is a special pen when we want to write something using the pen, we speak(typing behavior in the GUI) to tell it to write something(write in the model). Before writing it out, the program in this pen will evaluate whether the word is valid(which being set in stopCellEditing() method), then it writes the words out on paper(model).

Would like to explain @trashgod's answer since I have spent 4 hours on the DefaultCellEditor Section.

//first, we create a new class which inherit DefaultCellEditor
private static class PositiveIntegerCellEditor extends DefaultCellEditor {
//create 2 constant to be used when input is invalid and valid
    private static final Border red = new LineBorder(Color.red);
    private static final Border black = new LineBorder(Color.black);
    private JTextField textField;

//construct a `PositiveIntegerCellEditor` object  
//which use JTextField when this constructor is called
    public PositiveIntegerCellEditor(JTextField textField) {
        super(textField);
        this.textField = textField;
        this.textField.setHorizontalAlignment(JTextField.RIGHT);
    }
//basically stopCellEditing() being called to stop the editing mode  
//but here we override it so it will evaluate the input before  
//stop the editing mode
    @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;
        }
//if no exception thrown,call the normal stopCellEditing()
        return super.stopCellEditing();
    }

//we override the getTableCellEditorComponent method so that
//at the back end when getTableCellEditorComponent method is  
//called to render the input, 
//set the color of the border of the JTextField back to black 
    @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);
    }
}  

Lastly, use this line of code in your class that initialise JTable to set your DefaultCellEditor

table.setDefaultEditor(Object.class,new PositiveIntegerCellEditor(new JTextField()));

The Object.class means which type of column class you wish to apply the editor (Which part of paper you want to use that pen. It can be Integer.class,Double.class and other class).
Then we pass new JTextField() in PositiveIntegerCellEditor() constructor(Decide which type of ink you wish to use).

If anything that I misunderstood please tell me. Hope this helps!

Geography answered 26/7, 2019 at 3:37 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.