Setting RowSorter only once in Java Swing JTable - Stack Overflow

We have a JTable extension (actually, more than one).Being a JTable, it supports RowSorters. Typically

We have a JTable extension (actually, more than one).

Being a JTable, it supports RowSorters. Typically, it's a TableRowSorter.

First, the table is set up (e.g. columns are added), then it is filled (or re-filled).

The sorter is an exception, though. Even though, it seems like part of setup logic, it is reset on each refill. Before a new sorter instance is set, it is populated with Comparators stored in the table's collection.

At first, I didn't get it, so I tried to set the sorter only during the setup. However, I encountered one problem: when you add Comparators to a TableRowSorter, the latter checks with a TableModel (not TableColumnModel) if it has such a column index. If not, it throws.

Keep in mind the setup and fill cannot be mixed together: the table may be refreshed, GUI elements may be reconfigured — all of those would trigger a refill. In other words, the data is fluid, it cannot be set just once on setup.

I would prefer to have comparators set in the table's overridden addColumn(). It would check the column's type and set a matching comparator to the table's row sorter automatically (our columns are sadly not parameterized but have magic constants for types). I tried adding new columns to both column and data models (see javax.swing.table.DefaultTableModel#addColumn(java.lang.Object)), aiming to keep the two in sync. However, manipulating a TableModel that way results in erasing previously set comparators (see javax.swing.DefaultRowSorter#allChanged). The same happens if I simply update the data model's column count on each added column. I will include an MRE for that too, if necessary.

I also considered extending TableRowSorter and tweaking its default behavior a little bit. Unfortunately, it doesn't seem as straightforward as I hoped: my idea was to override javax.swing.DefaultRowSorter#getModelWrapper so that it checks with a TableColumnModel only to find the method is final.

Our app is highly customizable: it has user rights, app settings and whatnot — some of those determine the number of columns in a table (e.g. the user can add an extra column in GUI). The app may not know the total column count, let alone their identifiers before the user starts interaction. That is another reason why it does not seem feasible to fully initialize a TableModel beforehand, before its first fill (e.g. set its column count).

It appears there are only two options:

  1. Somehow break the connection between a RowSorter and TableModel.
  2. Accept the fact that I would need to reset comparators on each refill.

So my question is, is it the way you deal with a TableRowSorter, you reset it on each fill? Or is there a way to do it just once on setup, as I think it should be?

MRE:

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.RowSorter;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;
import javax.swing.table.TableModel;
import javax.swing.table.TableRowSorter;
import java.awt.BorderLayout;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;

public class SortedTableDemo {

    public static void main(String[] args) {
        JFrame frame = new JFrame("JTable Example");
        frame.setContentPane(createMainPanel());
        frame.pack();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }

    private static JPanel createMainPanel() {
        JPanel panel = new JPanel(new BorderLayout());
        panel.add(createTableScroller());
        return panel;
    }

    private static JScrollPane createTableScroller() {
        JTable table = createTable();
        return new JScrollPane(table);
    }

    private static JTable createTable() {
        JTable table = new JTable();
        table.setAutoCreateColumnsFromModel(false);
        
        TableColumnModel columnModel = table.getColumnModel();
        createColumns().forEach(columnModel::addColumn);
        
        RowSorter<? extends TableModel> rowSorter = createRowSorter(columnModel);
        table.setRowSorter(rowSorter);
        return table;
    }

    private static RowSorter<? extends TableModel> createRowSorter(TableColumnModel columnModel) {
        TableRowSorter<? extends TableModel> sorter = new TableRowSorter<>();
        sorter.setComparator(columnModel.getColumnIndex("Age"), ComparatorparingInt(Integer::intValue));
        return sorter;
    }

    private static List<TableColumn> createColumns() {
        List<TableColumn> columns = new ArrayList<>();

        TableColumn nameColumn = new TableColumn();
        nameColumn.setHeaderValue("Name");
        columns.add(nameColumn);

        TableColumn ageColumn = new TableColumn();
        ageColumn.setHeaderValue("Age");
        columns.add(ageColumn);

        TableColumn cityColumn = new TableColumn();
        cityColumn.setHeaderValue("City");
        columns.add(cityColumn);

        return columns;
    }
}
Exception in thread "main" java.lang.IndexOutOfBoundsException: column beyond range of TableModel
    at javax.swing.DefaultRowSorter.checkColumn(DefaultRowSorter.java:1219)
    at javax.swing.DefaultRowSorter.setComparator(DefaultRowSorter.java:761)
    at demos.table.SortedTableDemo.createRowSorter(SortedTableDemo.java:54)

We have a JTable extension (actually, more than one).

Being a JTable, it supports RowSorters. Typically, it's a TableRowSorter.

First, the table is set up (e.g. columns are added), then it is filled (or re-filled).

The sorter is an exception, though. Even though, it seems like part of setup logic, it is reset on each refill. Before a new sorter instance is set, it is populated with Comparators stored in the table's collection.

At first, I didn't get it, so I tried to set the sorter only during the setup. However, I encountered one problem: when you add Comparators to a TableRowSorter, the latter checks with a TableModel (not TableColumnModel) if it has such a column index. If not, it throws.

Keep in mind the setup and fill cannot be mixed together: the table may be refreshed, GUI elements may be reconfigured — all of those would trigger a refill. In other words, the data is fluid, it cannot be set just once on setup.

I would prefer to have comparators set in the table's overridden addColumn(). It would check the column's type and set a matching comparator to the table's row sorter automatically (our columns are sadly not parameterized but have magic constants for types). I tried adding new columns to both column and data models (see javax.swing.table.DefaultTableModel#addColumn(java.lang.Object)), aiming to keep the two in sync. However, manipulating a TableModel that way results in erasing previously set comparators (see javax.swing.DefaultRowSorter#allChanged). The same happens if I simply update the data model's column count on each added column. I will include an MRE for that too, if necessary.

I also considered extending TableRowSorter and tweaking its default behavior a little bit. Unfortunately, it doesn't seem as straightforward as I hoped: my idea was to override javax.swing.DefaultRowSorter#getModelWrapper so that it checks with a TableColumnModel only to find the method is final.

Our app is highly customizable: it has user rights, app settings and whatnot — some of those determine the number of columns in a table (e.g. the user can add an extra column in GUI). The app may not know the total column count, let alone their identifiers before the user starts interaction. That is another reason why it does not seem feasible to fully initialize a TableModel beforehand, before its first fill (e.g. set its column count).

It appears there are only two options:

  1. Somehow break the connection between a RowSorter and TableModel.
  2. Accept the fact that I would need to reset comparators on each refill.

So my question is, is it the way you deal with a TableRowSorter, you reset it on each fill? Or is there a way to do it just once on setup, as I think it should be?

MRE:

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.RowSorter;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;
import javax.swing.table.TableModel;
import javax.swing.table.TableRowSorter;
import java.awt.BorderLayout;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;

public class SortedTableDemo {

    public static void main(String[] args) {
        JFrame frame = new JFrame("JTable Example");
        frame.setContentPane(createMainPanel());
        frame.pack();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }

    private static JPanel createMainPanel() {
        JPanel panel = new JPanel(new BorderLayout());
        panel.add(createTableScroller());
        return panel;
    }

    private static JScrollPane createTableScroller() {
        JTable table = createTable();
        return new JScrollPane(table);
    }

    private static JTable createTable() {
        JTable table = new JTable();
        table.setAutoCreateColumnsFromModel(false);
        
        TableColumnModel columnModel = table.getColumnModel();
        createColumns().forEach(columnModel::addColumn);
        
        RowSorter<? extends TableModel> rowSorter = createRowSorter(columnModel);
        table.setRowSorter(rowSorter);
        return table;
    }

    private static RowSorter<? extends TableModel> createRowSorter(TableColumnModel columnModel) {
        TableRowSorter<? extends TableModel> sorter = new TableRowSorter<>();
        sorter.setComparator(columnModel.getColumnIndex("Age"), ComparatorparingInt(Integer::intValue));
        return sorter;
    }

    private static List<TableColumn> createColumns() {
        List<TableColumn> columns = new ArrayList<>();

        TableColumn nameColumn = new TableColumn();
        nameColumn.setHeaderValue("Name");
        columns.add(nameColumn);

        TableColumn ageColumn = new TableColumn();
        ageColumn.setHeaderValue("Age");
        columns.add(ageColumn);

        TableColumn cityColumn = new TableColumn();
        cityColumn.setHeaderValue("City");
        columns.add(cityColumn);

        return columns;
    }
}
Exception in thread "main" java.lang.IndexOutOfBoundsException: column beyond range of TableModel
    at javax.swing.DefaultRowSorter.checkColumn(DefaultRowSorter.java:1219)
    at javax.swing.DefaultRowSorter.setComparator(DefaultRowSorter.java:761)
    at demos.table.SortedTableDemo.createRowSorter(SortedTableDemo.java:54)
Share Improve this question edited Mar 3 at 11:43 Cagepi asked Mar 3 at 7:07 CagepiCagepi 3171 silver badge7 bronze badges 5
  • Bonus question: why on earth does it query a TableModel, not TableColumnModel – Cagepi Commented Mar 3 at 7:08
  • Just to be clear, when you talk about filling the table you are referring to setting the data in the TableModel, correct? – Abra Commented Mar 3 at 7:43
  • @Abra it is correct – Cagepi Commented Mar 3 at 7:44
  • In the code in your question, I don't see that you add any data to the JTable so if that code is meant to be a minimal reproducible example, I don't see how it is. What am I missing? – Abra Commented Mar 3 at 8:05
  • @Abra it doesn't matter. The exception is thrown during the table setup, it never comes to populating the table with data anyway. Keep in mind, the MRE is not supposed to reproduce a populated GUI table, it's supposed to reproduce the exception. Adding data would, on the contrary, make the example not minimal – Cagepi Commented Mar 3 at 8:24
Add a comment  | 

2 Answers 2

Reset to default 1

If the underlying model structure changes (the modelStructureChanged method is invoked) the following are reset to their default values: Comparators by column, current sort order, and whether each column is sortable. The default sort order is natural (the same as the model), and columns are sortable by default.

From https://docs.oracle/javase/8/docs/api/javax/swing/table/TableRowSorter.html

The documentation says that the state of the TableRowSorter would be reset if there is a change in the structure of the model. This means anytime you cause a change in the structure, you must set the comparators again. Things like changing the number of columns would cause a structure change, whereas things like just setting the value of a cell should not cause a structure change.

So the right way seems to be to set the comparators again whenever you cause a structure change. A possibly hackish way to do that would be to override modelStructureChanged() on TableRowSorter to set the comparators again.

Looking at source code:

private void checkColumn(int column) {
    if (column < 0 || column >= getModelWrapper().getColumnCount()) {
        throw new IndexOutOfBoundsException(
                "column beyond range of TableModel");
    }
}

here ModelWrapper is modelWrapper DefaultRowSorter has and this model clearly does not have such column (since it has no connection to your table). This leads me to class documentation where it is written:

"The setModelWrapper method must be invoked soon after the constructor is called, ideally from within the subclass's constructor. Undefined behavior will result if you use a DefaultRowSorter without specifying a ModelWrapper."

and to the TableRowSorter constructor you are using:

public TableRowSorter() { this(null); }

So the issue is your table and your row sorter are using different table models. Instead you should create row sorter with the model from your table:

new TableRowSorter<>(table.getModel());

Also since model does not reflect column count from columnModel, you need to initialize your JTable with model that does already know column count like:

JTable table = new JTable(new DefaultTableModel(0, 3));

or the one with column names:

JTable table = new JTable(new DefaultTableModel(new String[]{"Name", "Age", "City"}, 0));

For DefaultTableModel you can even set column count later, not in constructor.

DefaultTableModel model = (DefaultTableModel) table.getModel();
model.setColumnCount(3);

发布者:admin,转转请注明出处:http://www.yc00.com/questions/1745106127a4611564.html

相关推荐

  • Setting RowSorter only once in Java Swing JTable - Stack Overflow

    We have a JTable extension (actually, more than one).Being a JTable, it supports RowSorters. Typically

    16小时前
    30

发表回复

评论列表(0条)

  • 暂无评论

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信