Search code examples
javaswingjtable

How do I prevent a JTable from taking a certain input in the specified column?


I am trying to get a JTable to only accept input in a certain format for each column. For example, in a table with 5 columns the ones at indexes 3 and 4 should only accept numeric values.

I know about RowFilter and RowFilter.regexFilter(...) because of this post, but that hides the entire row instead of just discarding the change and even prevents yet empty rows from showing up right after adding them. This is how I've tried to use it (aka an example):

public static void main(String[] args) {
    // Create empty table with 5 columns
    JTable table = new JTable(new DefaultTableModel(new String[][] {}, new String[] { "String", "String", "String", "Num", "Num" }));

    // Create a new table row sorter
    TableRowSorter<TableModel> sorter = new TableRowSorter<>(table.getModel());

    // Filter columns 4 and 5 to only contain numbers
    sorter.setRowFilter(RowFilter.regexFilter("[0-9]+", 3, 4));

    // Apply the sorter to the table
    table.setRowSorter(sorter);

    // Boilerplate JFrame setup in case you want to run this
    JFrame frame = new JFrame("MRE");
    JPanel contentPane = new JPanel();
    JButton addRow = new JButton("Add Row");
    addRow.addActionListener((event) -> ((DefaultTableModel) table.getModel()).addRow(new String[] {}));
    contentPane.add(new JScrollPane(table));
    contentPane.add(addRow);
    frame.setSize(500, 500);
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.setContentPane(contentPane);
    frame.setVisible(true);
}

TL;DR: I want cells to restore their previous value if the one entered isn't allowed or to not even accept bad input in the typing phase.


Solution

  • I managed to do it, by overriding the editingStopped(ChangeEvent e) method of JTable. You're probably supposed to do that with a TableCellEditor, but I found this to be easier:

    public class MyTable extends JTable {
    
        // Contains the check for each column
        private final Map<Integer, Function<String, Boolean>> checks = new HashMap<>();
    
        public STable(TableModel model) {
            super(model);
    
            // Fill the map
            checks.put(0, (str) -> ...);
            checks.put(3, (str) -> ...);
            checks.put(4, (str) -> ...);
        }
    
        @Override
        public void editingStopped(ChangeEvent e) {
            String value = getCellEditor().getCellEditorValue().toString();
    
            final Function<String, Boolean> checker = checks.get(getSelectedColumn());
            if (checker != null && !checker.apply(value))
                getCellEditor().cancelCellEditing();
            else
                super.editingStopped(e);
        }
    
    }
    

    Also, I didn't try it, but camickr's suggestion of overriding getColumnClass(int column) sounds like it could work well. I didn't try it because I needed more complexity than that.