3

java - I want a JTable cell/column that allows me to enter only single digit number(like 1 to 9). How can I do that?

I have tried this but the number i pressed is coming twice in the cell.

table.addKeyListener(new KeyAdapter() {         
        public void keyPressed(KeyEvent e) {
            System.out.println("pressed..."+e.getKeyChar());
            char key = e.getKeyChar();
            int selectedColumn = table.getSelectedColumn();
            int selectedRow = table.getSelectedRow();
            if(table.getValueAt(selectedRow, selectedColumn) == null)
            table.setValueAt(key, selectedRow, selectedColumn);
        }
   });
Paul Samsotha
  • 205,037
  • 37
  • 486
  • 720
user3463568
  • 103
  • 1
  • 6
  • 1
    See [How to prevent typing characters except numbers in JTable cells?](http://stackoverflow.com/questions/21165469/how-to-prevent-typing-characters-except-numbers-in-jtable-cells/21165906#21165906) – dic19 Mar 26 '14 at 10:49

2 Answers2

5

Don't use a KeyListener. What you can do instead is use a JTextField for the TableCellEditor and just add a DocumentFilter to the JTextField that allows only numbers.

Here's a running example

import javax.swing.DefaultCellEditor;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import javax.swing.table.TableCellEditor;
import javax.swing.text.AbstractDocument;
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.DocumentFilter;
import javax.swing.text.DocumentFilter.FilterBypass;

public class JTableNumberColumn {

    public JTableNumberColumn() {
        JFrame frame = new JFrame();
        JTextField field = createTextField();
        frame.add(new JScrollPane(createTable(field)));
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }

    private JTextField createTextField() {
        JTextField field = new JTextField();
        ((AbstractDocument) field.getDocument()).setDocumentFilter(new DocumentFilter() {
            @Override
            public void insertString(FilterBypass fb, int off, String str, AttributeSet attr)
                    throws BadLocationException {
                fb.insertString(off, str.replaceAll("\\D++", ""), attr);  // remove non-digits
            }

            @Override
            public void replace(FilterBypass fb, int off, int len, String str, AttributeSet attr)
                    throws BadLocationException {
                fb.replace(off, len, str.replaceAll("\\D++", ""), attr);  // remove non-digits
            }
        });
        return field;
    }

    private JTable createTable(final JTextField field) {
        String[] cols = {"Only Numbers", "Col 2", "Col 3"};
        String[][] data = {{null, null, null}, {null, null, null}, {null, null, null}};
        final TableCellEditor editor = new DefaultCellEditor(field);
        JTable table = new JTable(data, cols) {
            @Override
            public TableCellEditor getCellEditor(int row, int column) {
                int modelColumn = convertColumnIndexToModel(column);

                if (modelColumn == 0) {
                    return editor;
                } else {
                    return super.getCellEditor(row, column);
                }
            }
        };
        return table;
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                new JTableNumberColumn();
            }
        });
    }
}

EDIT 1

I may have misread your question. If you want to allow only one number. then you can use a JFormattedTextField with a MaskFormatter as the TableCellEditor. Here's an example

import javax.swing.DefaultCellEditor;
import javax.swing.JFormattedTextField;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.SwingUtilities;
import javax.swing.table.TableCellEditor;
import javax.swing.text.MaskFormatter;

public class JTableNumberColumn {

    public JTableNumberColumn() {
        JFrame frame = new JFrame();
        JFormattedTextField field = createFormattedTextField();
        frame.add(new JScrollPane(createTable(field)));
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }


    private JFormattedTextField createFormattedTextField() {
        JFormattedTextField field = new JFormattedTextField(createFormatter("#"));
        return field;
    }

    protected MaskFormatter createFormatter(String s) {
        MaskFormatter formatter = null;
        try {
            formatter = new MaskFormatter(s);
        } catch (java.text.ParseException exc) {
            System.err.println("formatter is bad: " + exc.getMessage());
            System.exit(-1);
        }
        return formatter;
    }

    private JTable createTable(final JFormattedTextField field) {
        String[] cols = {"Only Numbers", "Col 2", "Col 3"};
        String[][] data = {{null, null, null}, {null, null, null}, {null, null, null}};
        final TableCellEditor editor = new DefaultCellEditor(field);
        JTable table = new JTable(data, cols) {
            @Override
            public TableCellEditor getCellEditor(int row, int column) {
                int modelColumn = convertColumnIndexToModel(column);

                if (modelColumn == 0) {
                    return editor;
                } else {
                    return super.getCellEditor(row, column);
                }
            }
        };
        return table;
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                new JTableNumberColumn();
            }
        });
    }
}

EDIT 2

It has been brought to my attention I also overlooked the fact that you only want 1-9 and not 0-9. In that case I would just stick with the first option of the JTextField with DocumentFilter, but in the filter, check the length of the input and change the regex to allow only 1-9.

Here's the example

import javax.swing.DefaultCellEditor;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import javax.swing.table.TableCellEditor;
import javax.swing.text.AbstractDocument;
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.DocumentFilter;
import javax.swing.text.DocumentFilter.FilterBypass;

public class JTableNumberColumn {

    public JTableNumberColumn() {
        JFrame frame = new JFrame();
        JTextField field1 = createTextField();
        frame.add(new JScrollPane(createTable(field1)));
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }

    private JTextField createTextField() {
        final JTextField field = new JTextField();
        ((AbstractDocument) field.getDocument()).setDocumentFilter(new DocumentFilter() {
            @Override
            public void insertString(FilterBypass fb, int off, String str, AttributeSet attr)
                    throws BadLocationException {
                int length = field.getDocument().getLength();
                if (length + str.length() <= 1) {
                    fb.insertString(off, str.replaceAll("[^1-9]", ""), attr);  // remove non-digits
                }
            }

            @Override
            public void replace(FilterBypass fb, int off, int len, String str, AttributeSet attr)
                    throws BadLocationException {
                int length = field.getDocument().getLength();
                if (length + str.length() <= 1) {
                    fb.replace(off, len, str.replaceAll("[^1-9]", ""), attr);  // remove non-digits
                }
            }
        });
        return field;
    }


    private JTable createTable(final JTextField field) {
        String[] cols = {"Only Numbers", "Col 2", "Col 3"};
        String[][] data = {{null, null, null}, {null, null, null}, {null, null, null}};
        final TableCellEditor editor = new DefaultCellEditor(field);
        JTable table = new JTable(data, cols) {
            @Override
            public TableCellEditor getCellEditor(int row, int column) {
                int modelColumn = convertColumnIndexToModel(column);

                if (modelColumn == 0) {
                    return editor;
                } else {
                    return super.getCellEditor(row, column);
                }
            }
        };
        return table;
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                new JTableNumberColumn();
            }
        });
    }
}
Paul Samsotha
  • 205,037
  • 37
  • 486
  • 720
  • 1
    +1 for the example :) Using `JFormattedTextField` instead of document filters could be another option. – dic19 Mar 26 '14 at 10:53
  • I want to up-vote this answer again :) Note if you use [NumberFormatter](http://docs.oracle.com/javase/7/docs/api/javax/swing/text/NumberFormatter.html) instead of `MaskFormatter` you can call these methods: `setMinimum(1)` and `setMaximum(9)` (OP wants number from 1 to 9). These are inherited from [InternationalFormatter](http://docs.oracle.com/javase/7/docs/api/javax/swing/text/InternationalFormatter.html) – dic19 Mar 26 '14 at 11:11
  • @dic19 I prefer the idea of real time validation so I stuck with the `DocumentFilter` and just changed it to check the length and also changed the regex a little. See my EDIT 2 – Paul Samsotha Mar 26 '14 at 11:28
  • Sorry but I don't get the "real time validation" part (I saw the same expression in other Q&A's too but don't get the idea). – dic19 Mar 26 '14 at 11:36
  • @dic19 basically real time validation in this context means that each character input is validated and allowed to be entered only if it meets some constraints. With a `JFormattedTextField` and `NumberFormatter` you can still enter other characters and it won't get validated until the text field loses focus. The `MaskFormatter` _does_ provide that real time validation in this situation – Paul Samsotha Mar 26 '14 at 11:42
  • Ahh I thought that but wasn't sure. Yes you can do that with `JFormattedTextField` too: `NumberFormatter formatter = new NumberFormatter(NumberFormat.getIntegerInstance()); formatter.setAllowsInvalid(false); formatter.setOverwriteMode(true); formatter.setMinimum(1); formatter.setMaximum(9);` It will complain if you try to input a non-digit character or `0`. Give it a try :D – dic19 Mar 26 '14 at 11:48
  • @dic19 I gave it a whirl but after I type a number, I'm stuck with that number. I can't change it (backspace). Any guesses? – Paul Samsotha Mar 26 '14 at 11:58
  • Yes, I've seen that before in `JSpinner` too. If you go to the first position and type a number, does it override the previous one? Make sure you call `setOverwriteMode(true)`. – dic19 Mar 26 '14 at 12:04
  • I am using the first answer. It is working well for me. But, I need to enter decimal number. It doesn't allows dot. Help me. – YASEER ARAFATH Aug 14 '14 at 09:56
0

One method is to implement your own TableModel (I would do this anyway) and implement the setValueAt(Object, int, int) method. Verify within that method and just don't set the value if it is bad (including putting up a JDialog or whatever is appropriate). Below is some code:

public class MyTableModel extends AbstractTableModel {

...

@Override
public void setValueAt(Object o, int i, int j) {
    if (o instanceof String) {
        char c = ((String) o).charAt(0);

        if (((String) o).length() == 1 && c >= '0' && c <= '9') {
            data[i][j] = Integer.valueOf(((String) o).substring(0, 1));
            return;
        }
    }
    JOptionPane.showMessageDialog(null, "Must input a number from 0-9");
}

I took so long to hit post that somebody else answered! Anyway, because my approach is different, I thought I'd include it. I'm putting in a working program.

import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.table.AbstractTableModel;

public class TestTable {

    public static void main(String[] args) {
        TestTable tt = new TestTable();
        tt.init();
    }

    private void init() {
        // Make model
        MyModel model = new MyModel ();
        JTable table = new JTable(model);

        // Add to a frame
        JScrollPane pane = new JScrollPane(table);
        JFrame frame = new JFrame("Test");
        frame.add(pane);
        frame.pack();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setVisible(true);
    }

}

class MyModel extends AbstractTableModel {

    // Just some dummy data
    Integer[][] data = new Integer[10][6];

    // Init the dummy data
    public MyModel() {
        super();
        for (int i = 0; i < data.length; i++) {
            Integer[] is = data[i];
            for (int j = 0; j < is.length; j++) {
                data[i][j] = 20 * i + j;
            }
        }
    }

    @Override
    public int getRowCount() {
        return data.length;
    }

    @Override
    public int getColumnCount() {
        return data[0].length;
    }

    @Override
    public Object getValueAt(int i, int j) {
        return data[i][j];
    }

    // Makes cells editable
    @Override
    public boolean isCellEditable(int i, int j) {
        return true;
    }

    @Override
    public void setValueAt(Object o, int i, int j) {
        if (o instanceof String) {
            char c = ((String) o).charAt(0);

            if (((String) o).length() == 1 && c >= '0' && c <= '9') {
                data[i][j] = Integer.valueOf(((String) o).substring(0, 1));
                return;
            }
        }
        JOptionPane.showMessageDialog(null, "Must input a number from 0-9");
    }
}
timbo
  • 1,533
  • 1
  • 15
  • 26