dataModelUpdate 后自动排序 JTable

Autosort JTable after dataModelUpdate

数据模型更改后继续排序。

预期行为: 我单击其中一个列标题进行排序,它应该在数据模型更改时继续对该列进行排序。也就是说,一旦您选择了一个列作为排序依据,您就不必在每次更新基础数据模型后手动单击它来按该顺序查看它。

当前行为: 似乎在数据模型更改后重置排序。也就是说,就好像我没有单击任何列一样。更新数据模型又乱了

我尝试了什么: 我写了一个小程序,每 2000 毫秒通过一个 for 循环模拟底层数据模型的变化。 由于原始数据从更新到更新变化如此之大,我选择使用 DefaultTableModel.setDataVector() 来更改数据,而不是逐行更改,如果很多行发生更改,这看起来很庞大。 我使用了对我来说有意义的东西,即 TableRowSorter.setSortsOnUpdates(),好吧……在更新后排序 x)。 我留下注释掉的代码以表明我尝试了不同的方法,其中一些是在 stackexchange 上找到的,但无论如何都无法使其工作。 我尝试阅读 api 并发现这可能是一个问题制造者:

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.

这是在 API 文档中找到的:TableRowSorter

我想有人可能会说我希望它重置为默认排序顺序,而是保留最后选择的排序顺序。肯定有一种简单的方法可以阻止它这样做吗?

顺便说一句,我是菜鸟,请在回答时记住这一点,我希望我在尝试解释预期行为时是有意义的。

到目前为止我的代码:

package TestTable;

import java.awt.Color;
import java.awt.Dimension;
import java.util.Random;
import java.util.Vector;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableRowSorter;
import ui.Runner;

public class starter {
    public static void main(String[] args) {
        starter start = new starter();
        Vector<String> columNames = new Vector<>();
        columNames.add("index");
        columNames.add("years");
        columNames.add("weight");
        Vector<Vector<Object>> data = new Vector<Vector<Object>>();
        Vector<Object> dataInData = new Vector<>();
        Random dice = new Random();

        JTable table = new JTable(data, columNames);
        //table.setAutoCreateRowSorter(true);
        MyFrame frame = new MyFrame(table);
        DefaultTableModel model = (DefaultTableModel)table.getModel();
        TableRowSorter<DefaultTableModel> sorter = new TableRowSorter<>(model);
        //MyRowSorter<DefaultTableModel> sorter = new MyRowSorter<>();
        table.setRowSorter(sorter);
        //DefaultRowSorter sorter = (DefaultRowSorter)table.getRowSorter();
        //TableModelEvent ev = new TableModelEvent(model);
        sorter.setSortsOnUpdates(true);//<---------------<<<<<

        data.add(new Vector<Object>());
        data.add(new Vector<Object>());
        data.add(new Vector<Object>());

        for(int x = 0; x < 3; x++) {
            dataInData = data.get(x);
            for(int y = 0; y < 5; y++) {
                dataInData.add(dice.nextInt(10));
            }
        }

        for(int z = 0; z < 10; z++) {
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            for(int x = 0; x < 3; x++) {
                dataInData = data.get(x);
                for(int y = 0; y < dataInData.size(); y++) {
                    dataInData.set(y, dice.nextInt(10));
                }
            }

            model.setDataVector(data, columNames);//<-------------------<<
            //model.fireTableDataChanged();
            //sorter.setSortsOnUpdates(true);
            //sorter.rowsUpdated(0, data.size() -1);
            model.fireTableRowsUpdated(0, model.getRowCount() -1 );
            //model.fireTableDataChanged();
            //model.fireTableChanged(ev);
            //model.fireTableStructureChanged();
            //sorter.rowsUpdated(0, data.size() );
            //sorter.sort();
        }
    }

    public static class MyFrame extends JFrame{
        JTable table;
        Dimension dim = new Dimension(300, 500);
        JScrollPane pane;
        Vector<String> columNames = new Vector<>();
        Vector<Vector<Object>> dataVector;
        Runner thread;
        int[] i = {0, 1, -1};

        public MyFrame(JTable table) {
            pane = new JScrollPane(table);
            table.setFillsViewportHeight(true);
            setSize(300, 500);
            setPreferredSize(dim);
            setMinimumSize(dim);
            setTitle("Failing autoupdate of JTable");
            setBackground(Color.BLACK);
            add(pane);
            setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            setVisible(true);
        }
    }
}

设置新的首选索引时要保持排序吗? 你只有三个columnheaders(有限),为什么不设置columnheaders的优先级? 当您点击一个 header 时,将其优先级更改为第一,将其他的降低一个。 并再次计算总排序以显示它们。

I chose to change the data with DefaultTableModel.setDataVector(), instead of row by row which seems bulky if lots of rows change.

如果所有数据都在变化,这似乎是合理的,因为您将只生成一个事件来重新绘制整个 table,而不是为每一行生成一个事件。

但是,如果您只更改一些随机行的数据,那么我将只更新单个行,那么您就不会有排序问题。

对于第一种方法,请尝试以下方法:

JTable table = new JTable(data, columNames); 
table.setAutoCreateColumnsFromModel(false);

这应该可以防止在调用 setDataVector(...) 时重新创建 TableColumnModel,这会导致生成 TableStructureChanged 事件,从而导致您丢失 RowSorter。 (或者你可能仍然失去了 RowSorter,我从未尝试过,但我知道你不会失去自定义 renderers/editors 使用这种方法)。

//model.fireTableRowsUpdated(0, model.getRowCount() -1 );

那是永远不需要的。 TableModel 负责在数据更改时调用适当的事件。

让我们暂时跳过线程违规问题。

DefaultTableModel 上调用 setDataVector 会触发 TableStructureChanged,这会强制 JTable discard/reset rowSorter ],而是直接更新模型中的值,例如

for (int x = 0; x < 3; x++) {
    for (int y = 0; y < table.getColumnCount(); y++) {
        model.setValueAt(dice.nextInt(10), x, y);
    }
}

我使用 Swing Timer 对此进行了测试,以确保更新正确地发生在 EDT

import java.awt.Color;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Random;
import java.util.Vector;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableRowSorter;

public class Main {

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

    public Main() {
        Vector<String> columNames = new Vector<>();
        columNames.add("index");
        columNames.add("years");
        columNames.add("weight");
        Vector<Vector<Object>> data = new Vector<Vector<Object>>();
        Random dice = new Random();

        JTable table = new JTable(data, columNames);
        MyFrame frame = new MyFrame(table);
        DefaultTableModel model = (DefaultTableModel) table.getModel();
        TableRowSorter<DefaultTableModel> sorter = new TableRowSorter<>(model);
        table.setRowSorter(sorter);
        sorter.setSortsOnUpdates(true);//<---------------<<<<<

        for (int x = 0; x < 3; x++) {
            model.addRow(new Vector());
            for (int y = 0; y < table.getColumnCount(); y++) {
                model.setValueAt(dice.nextInt(10), x, y);
            }
        }

        Timer timer = new Timer(2000, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                for (int x = 0; x < 3; x++) {
                    for (int y = 0; y < table.getColumnCount(); y++) {
                        model.setValueAt(dice.nextInt(10), x, y);
                    }
                }
            }
        });
        timer.start();
    }

    public static class MyFrame extends JFrame {

        JTable table;
        Dimension dim = new Dimension(300, 500);
        JScrollPane pane;
        Vector<String> columNames = new Vector<>();
        Vector<Vector<Object>> dataVector;
        int[] i = {0, 1, -1};

        public MyFrame(JTable table) {
            pane = new JScrollPane(table);
            table.setFillsViewportHeight(true);
            setSize(300, 500);
            setPreferredSize(dim);
            setMinimumSize(dim);
            setTitle("Failing autoupdate of JTable");
            setBackground(Color.BLACK);
            add(pane);
            setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            setVisible(true);
        }
    }
}

I chose to change the data with DefaultTableModel.setDataVector(), instead of row by row which seems bulky if lots of rows change

好的,这似乎是合理的。我似乎能够为此工作的唯一解决方案是替换 RowSorter,但使用旧 RowSorter

中的 sortKeys

仅使用 RowSorter 的相同实例对我不起作用

import java.awt.Color;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Random;
import java.util.Vector;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.RowSorter;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableModel;
import javax.swing.table.TableRowSorter;

public class Main {

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

    Random dice = new Random();

    public Main() {
        Vector<String> columNames = new Vector<>();
        columNames.add("index");
        columNames.add("years");
        columNames.add("weight");
        Vector<Vector<Object>> data = new Vector<Vector<Object>>();

        JTable table = new JTable();
        makeModel(table, columNames);
        MyFrame frame = new MyFrame(table);

        Timer timer = new Timer(2000, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                makeModel(table, columNames);
            }
        });
        timer.start();
    }

    protected void makeModel(JTable table, Vector columnNames) {
        DefaultTableModel model = new DefaultTableModel(columnNames, 0);

        for (int x = 0; x < 3; x++) {
            Vector row = new Vector();
            for (int y = 0; y < model.getColumnCount(); y++) {
                row.add(dice.nextInt(10));
            }
            model.addRow(row);
        }
        TableRowSorter<DefaultTableModel> sorter = new TableRowSorter<>(model);
        RowSorter<? extends TableModel> oldSorter = table.getRowSorter();
        if (oldSorter != null) {
            sorter.setSortKeys(oldSorter.getSortKeys());
        }

        table.setModel(model);
        table.setRowSorter(sorter);
    }

    public static class MyFrame extends JFrame {

        JTable table;
        Dimension dim = new Dimension(300, 500);
        JScrollPane pane;
        Vector<String> columNames = new Vector<>();
        Vector<Vector<Object>> dataVector;
        int[] i = {0, 1, -1};

        public MyFrame(JTable table) {
            pane = new JScrollPane(table);
            table.setFillsViewportHeight(true);
            setSize(300, 500);
            setPreferredSize(dim);
            setMinimumSize(dim);
            setTitle("Failing autoupdate of JTable");
            setBackground(Color.BLACK);
            add(pane);
            setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            setVisible(true);
        }
    }
}

请注意,这种更新也会删除 column/row 选择

I chose to change the data with DefaultTableModel.setDataVector(), instead of row by row which seems bulky if lots of rows change

另一种选择是不使用 DefaultTableModel,而是使用 AbstractTableModel 实现您自己的选择,在其中对基础模型数据进行 "batch" 更新,然后触发 tableDataChanged 事件。在完成批量更新之前,您必须小心确保不修改 JTable 可能要求的数据

我遇到了同样的问题,在查看 JDK 源代码后,恢复排序的最简单方法似乎是使用以下代码对 JTable 进行子类化(但这并没有解决另一个问题 - 选择也正在重置并需要恢复)。

RowSorter<? extends TableModel> lastRowSorter;
List<? extends RowSorter.SortKey> lastSortKeys;

@Override
public void tableChanged(TableModelEvent e) {
    lastRowSorter = getRowSorter();
    lastSortKeys = (lastRowSorter == null) ? null : lastRowSorter.getSortKeys();

    super.tableChanged(e);
}

@Override
public void createDefaultColumnsFromModel() {
    super.createDefaultColumnsFromModel();
    if( lastSortKeys != null ) {
        setRowSorter(lastRowSorter);
        lastRowSorter.setSortKeys(lastSortKeys);
    }
}