Search code examples
javaswingjtablerowfilter

Filter JTable with Numbers without the grouping character (thousands-separator)


I'm trying to filter Rows in a JTable which contains Columns with numbers.

The filtering is working so far, but it filters over the numbers including the thousands-separators. For example, if there is a row with the number 25689 in one row and I try to filter for this row, i have to use "25.689". So it seems there is a formatting that is performed before the filtering.

I've tried to set an own default renderer and the numbers are shown without the separators but the filtering is the same.

Edit

I've added a full example re-creating my problem:

public class GroupingTest {

    JFrame frame= null;
    Container pane= null;
    JTextField tf=null;
    JXTable table=null;

    public void searchTable() {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {                       
                    final String searchEx = "(?i)"
                                          + Pattern.quote(tf.getText());

                    final RowFilter<TableModel, Object> filter;
                    filter = RowFilter.regexFilter(searchEx);    
                    table.setRowFilter(filter);
                    //packAll in edt
                    Utility.packTableView(table);                       
                } catch (final Exception e) {
                    return;
                }
            }
        });
    }

    public void createTable() {
        frame = new JFrame();
        pane=frame.getContentPane();

        tf = new JTextField();
        tf.setPreferredSize(new Dimension(200,25));

        tf.getDocument().addDocumentListener(new DocumentListener() {

            @Override
            public void removeUpdate(final DocumentEvent e) {
                searchTable();
            }

            @Override
            public void insertUpdate(final DocumentEvent e) {
                searchTable();
            }

            @Override
            public void changedUpdate(final DocumentEvent e) {
                searchTable();
            }
        });

        String[] columnHeaders = {"long","strings"};

        DefaultTableModel $model = new DefaultTableModel(columnHeaders, 0) {
            @Override
            public Class<?> getColumnClass(final int $col) {
                if($col == 0) {
                    return Long.class;
                } else if($col == 1){
                    return String.class;
                } else {
                    return Object.class;
                }
            }
        };

        table = new JXTable($model);

        table.setDefaultRenderer(Long.class, new DefaultTableCellRenderer() {

            @Override
            public java.awt.Component getTableCellRendererComponent(final JTable $table,
                    final Object $value, final boolean $isSelected, final boolean $hasFocus, final int $row,
                    final int $column) {
                super.getTableCellRendererComponent($table, $value, $isSelected, $hasFocus, $row, $column);

                if ($value instanceof Long) {
                    this.setHorizontalAlignment(SwingConstants.RIGHT);
                }

                return this;
            }
        });

        Object[] line1 = {new Long(23345),"asdf"};
        $model.addRow(line1);
        Object[] line2 = {new Long(3),"dfw"};
        $model.addRow(line2);

        pane.add(tf,BorderLayout.NORTH);
        pane.add(new JScrollPane(table),BorderLayout.CENTER);

        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setPreferredSize(new Dimension(300,200));
        frame.pack();
        frame.setVisible(true);             
    }

    public static void main(String[] args) {        
        GroupingTest gt = new GroupingTest();
        gt.createTable();    
    }    
}

Solution

  • The Filtering is working so far, but it filters over the numbers including the thousands-separators.

    When the value's format interferes with the expected functioning of sorters and filters then it's time to check if getColumnClass(int columnIndex) in the table model is retrieving the appropriate class (in this case Double).

    By default AbstractTableModel implementation of such method returns Object.class which is rendered using the toString() method (that's why you see the thousands-separator) and probably filtered according to the string representation as well. Subclasses of AbstractTableModel (such as DefaultTableModel) inherit this implementation and thus this method should be overriden. For example let's say your table model is DefaultTableModel and the first column is a Double:

    DefaultTableModel model = new DefaultTableModel()  {
        @Override
        public Class<?> getColumnClass(int columnIndex) {
            return columnIndex == 0 ? Double.class 
                                    : super.getColumnClass(columnIndex);
        }
    };
    

    See Sorting and Filtering section of How to Use Tables tutorial for further details.

    Update

    Given your new MVCE it is clear now what are you trying to achieve. I'd start saying that I've mistakenly assumed your table model holds Double instead of Long which makes no difference about overriding getColumnClass() method (it should be done anyways) but it will make a slight difference in the final solution.

    Now, to state the requirements clear, you need to filter that column either:

    • Users input a number (Long) including grouping character.
    • Users input a number without grouping character.
    • The string representation of the value contains the substring typed by the users.

    To achieve this goal I'd use a custom RowFilter instead of using a regex filter like you do in your example. This is to have control about the string typed by the user and check the three conditions listed above. I've managed to modify your searchTable() to satisfy the requirements. Note: I've included the queried String as an argument in this method to keep tf text field out of the implementation. Please see the code below:

    private void searchTable(final String query) {
    
        RowFilter<TableModel, Integer> filter = null;
    
        if (query.length() > 0) {
            filter = new RowFilter<TableModel, Integer>() {
                @Override
                public boolean include(RowFilter.Entry<? extends TableModel, ? extends Integer> entry) {
    
                   for (int i = 0; i < entry.getValueCount(); i++) {
                       String stringValue = entry.getStringValue(i);
                       Object entryValue = entry.getValue(i);
                       String numberString = entryValue instanceof Long 
                                           ? String.valueOf(entryValue)
                                           : "";
    
                       if (stringValue.contains(query) || numberString.contains(query)) {
                           return true;
                       }
                   }
    
                   return false;
                }
    
            };
        }
    
        table.setRowFilter(filter);
    }
    

    The flow will be more or less as follows:

    1. If the query length is 0 just let the filter be null. This means the table won't be filtered and all rentries will be included.

    2. If not (1) then prepare a new filter which iterates over the whole row asking if the String representation of the entry or the String value of the entry contains the queried String. While those might look the same thing they are not because Entry#getStringValue(int index) might (and actually does) retrieve a different value than String#valueOf(entry#getValue(int index)). In this case the first one retrieves the Long including grouping separators (or formatted if you prefer) while the second one retrieves the Long with no formatting at all (it means, no grouping separators).

    3. Apply the filter to the table in either case.

    I hope the idea is clear enough. If you want to filter a Double then it has to be tweaked a little bit because String.valueOf(double) includes the decimal (not grouping) separator and you might want to remove it before checking if it contains the queried String.