Автосортировка JTable после dataModelUpdate

Продолжайте сортировать после изменения модели данных.

Ожидаемое поведение: я щелкаю один из заголовков столбцов для сортировки, и он должен продолжать сортировать этот столбец по мере изменения модели данных. То есть после того, как вы выберете столбец для сортировки, вам не придется щелкать его вручную после каждого обновления базовой модели данных, чтобы увидеть его в этом порядке.

Текущее поведение: кажется, что сортировка сбрасывается после изменения модели данных. То есть, как будто я не щелкнул ни один столбец. Обновление модели данных снова приводит к беспорядку.

Что я пробовал: я написал небольшую программу, которая имитирует изменение базовой модели данных через цикл for каждые 2000 миллисекунд. Поскольку исходные данные сильно меняются от обновления к обновлению, я решил изменить данные с помощью DefaultTableModel.setDataVector(), а не построчно, что кажется громоздким, если меняется много строк. Я использовал то, что имело для меня смысл, то есть TableRowSorter.setSortsOnUpdates(), чтобы ну... сортировать после обновлений x). Я оставил закомментированный код, чтобы показать, что я пробовал разные подходы, некоторые из них можно найти здесь, на stackexchange, но все равно не смог заставить его работать. Я попытался прочитать API и нашел это как возможного создателя проблем:

Если базовая структура модели изменяется (вызывается метод modelStructureChanged), следующие значения сбрасываются до значений по умолчанию: компараторы по столбцам, текущий порядок сортировки и возможность сортировки каждого столбца. Порядок сортировки по умолчанию — естественный (такой же, как в модели), а столбцы по умолчанию сортируются.

Это было найдено в документации по 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);
        }
    }
}

person brat    schedule 10.05.2017    source источник
comment
Вызов setDataVector вызовет событие TableStructureChanged, которое из памяти сбросит сортировщик строк.   -  person MadProgrammer    schedule 10.05.2017


Ответы (4)


Давайте на секунду отвлечемся от проблем с нарушением потока.

Вызов setDataVector для DefaultTableModel запускает TableStructureChanged, что заставляет JTable, среди прочего, отбрасывать/сбрасывать 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);
        }
    }
}

Я решил изменить данные с помощью DefaultTableModel.setDataVector() вместо строки за строкой, которая кажется громоздкой, если меняется много строк.

Хорошо, это кажется разумным. Единственное решение, которое я, кажется, могу заставить работать, это заменить RowSorter, но использовать sortKeys из старого RowSorter

Простое использование того же экземпляра 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);
        }
    }
}

Просто обратите внимание, что этот тип обновления также удалит выбор столбца/строки.

Я решил изменить данные с помощью DefaultTableModel.setDataVector() вместо строки за строкой, которая кажется громоздкой, если меняется много строк.

Другим вариантом было бы не использовать DefaultTableModel, а реализовать свой собственный с использованием AbstractTableModel, где вы выполняете «пакетное» обновление базовых данных модели, а затем запускаете событие tableDataChanged. Вы должны быть осторожны, чтобы убедиться, что вы не изменяете данные, которые могут быть запрошены JTable, пока вы не завершите пакетное обновление.

person MadProgrammer    schedule 10.05.2017
comment
(1+) для воссоздания RowSorter со старыми ключами SortKey. - person camickr; 10.05.2017
comment
@camickr Я думаю, что это немного хакерски, я думаю, что предпочитаю концепцию создания TableModel с пакетной поддержкой - по крайней мере, это не очистит выбор столбца / строки, но по-прежнему использует только одно событие - person MadProgrammer; 10.05.2017
comment
Если подумать, вы правы, модель должна заботиться обо всем. Приложение не должно играть с RowSorter только потому, что данные меняются. Я беру 1+ за предложение повторно использовать старые sortKeys, но восстановить 1 + за предложение добавить пакетное обновление :) - person camickr; 10.05.2017
comment
@MadProgrammer Большое спасибо. Пришлось немного доработать, сделав некоторые поля окончательными. Теперь дело только за мной, чтобы сделать его своим, так сказать. Просто интересно, с моделями (собственное расширение Abstract или Default) представление и все уровни унаследованных методов для каждого задействованного класса... это как сотни возможных вещей, которые можно попробовать, и кажется, что они должны работать. Для нуба вроде меня это все равно, что пытаться найти иголку в стоге сена. Итак, как же вы нашли это решение? Я бы хотел поковыряться в твоих мозгах xD - person brat; 12.05.2017
comment
@brat Годы биться головой о кирпичные стены ;) - Когда я начинал, я никогда не изучал DefaultTableModel, я учился с помощью AbstractTableModel, у меня также не было RowSorter API, мне приходилось сворачивать свой собственный, так что я вроде как многому научился, делая эти вещи вручную - и много блогов и форумов;) - person MadProgrammer; 12.05.2017

Вы хотите сохранить сортировку при установке нового предпочтительного индекса? у вас есть только три заголовка столбца (ограничено), почему бы не установить приоритет для заголовков столбцов? когда вы нажимаете на один заголовок, измените его приоритет на первый, уменьшите другие на один. и снова рассчитать общую сортировку, чтобы показать их.

person astrologer    schedule 10.05.2017

Я решил изменить данные с помощью DefaultTableModel.setDataVector() вместо строки за строкой, которая кажется громоздкой, если меняется много строк.

Кажется разумным, если все данные меняются, поскольку вы будете генерировать только одно событие для перерисовки всей таблицы вместо одного события для каждой строки.

Однако, если вы измените данные только в нескольких случайных строках, я бы просто обновил отдельную строку, и тогда у вас не будет проблемы с сортировкой.

Для первого подхода попробуйте следующее:

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

это должно предотвратить повторное создание TableColumnModel при вызове setDataVector(...), что приводит к генерации события TableStructureChanged, что приводит к потере RowSorter. (или, может быть, вы все еще теряете RowSorter, я никогда не пробовал, но я знаю, что вы не потеряете пользовательские рендереры/редакторы с таким подходом).

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

Это никогда не нужно. TableModel отвечает за вызов соответствующего события при изменении данных.

person camickr    schedule 10.05.2017
comment
Вы проверили это? Я попробовал, и это не работает, кажется, что RowSorter все еще сбрасывается - person MadProgrammer; 10.05.2017
comment
@MadProgrammer, нет, я не пробовал, как я отметил в своем ответе. Думаю, я больше не буду делать это предложение :) - person camickr; 10.05.2017
comment
Блин, я надеялся, что это сработает, а я что-то не так делаю :( - person MadProgrammer; 10.05.2017
comment
Мне трудно понять, почему RowSorter сбрасывается по умолчанию o_O. или почему они вызывают метод setSortOnUpdates(), а затем, по-видимому, не делают этого, если вы не используете определенный метод для изменения данных: o. - person brat; 12.05.2017

Я столкнулся с той же проблемой, и после просмотра исходного кода 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);
    }
}
person Xtra Coder    schedule 18.10.2018