setAutoCreateRowSorter在更新后没有正确排序表列

时间:2016-04-09 17:48:30

标签: java swing sorting jtable integer

在开发一个小型任务管理器时,我注意到列没有正确排序。为了解决我的程序问题,我创建了一个最小版本,但它仍然无法正确排序唯一列。

import java.awt.BorderLayout;
import java.util.List;
import java.util.Random;
import javax.swing.*;
import javax.swing.table.AbstractTableModel;

public class TableSortTest extends JFrame
{
    private final JTable table;
    private final ATableModel model;

    public TableSortTest ()
    {
        setDefaultCloseOperation (EXIT_ON_CLOSE);
        setSize (1366, 768);
        setLocationRelativeTo (null);

        model = new ATableModel ();
        table = new JTable ();
        table.setFillsViewportHeight (true);
        table.setAutoCreateRowSorter (true);
        table.setModel (model);

        add (new JScrollPane (table), BorderLayout.CENTER);

        setVisible (true);

        Worker worker = new Worker ();
        worker.execute ();
    }

    private class Pair
    {
        int index;
        int value;
    }

    private class Worker extends SwingWorker <Void, Pair>
    {
        @Override
        protected Void doInBackground ()
        {
            while (!isCancelled ())
            {
                Random r = new Random ();
                for (int i = 0; i < 100; i++)
                {
                    int indice = getIndexInRange (0, 99);
                    Pair p = new Pair ();
                    p.index = indice;
                    p.value = Math.abs (r.nextInt ());
                    publish (p);
                }

                try
                {
                    Thread.sleep (1000);
                }
                catch (InterruptedException ie)
                {
                    ie.printStackTrace ();
                }
            }

            return null;
        }

        @Override
        public void process (List <Pair> items)
        {
            for (Pair p : items)
            {
                model.setValueAt (p.value, p.index, 0);
            }
        }
    }

    public static int getIndexInRange (int min, int max)
    {
        return (min + (int) (Math.random () * ((max - min) + 1)));
    }

    private class ATableModel extends AbstractTableModel
    {
        private final Integer [] data;

        public ATableModel ()
        {
            data = new Integer [100];

            Random r = new Random ();

            for (int i = 0; i < 100; i++)
            {
                data [i] = Math.abs (r.nextInt ());
            }
        }

        @Override
        public int getColumnCount ()
        {
            return 1;
        }

        @Override
        public int getRowCount ()
        {
            return data.length;
        }

        @Override
        public Object getValueAt (int rowIndex, int columnIndex)
        {
            return data [rowIndex];
        }

        @Override
        public void setValueAt (Object value, int rowIndex, int columnIndex)
        {
            data [rowIndex] = (Integer) value;
            fireTableRowUpdated (rowIndex, columnIndex);
        }

        @Override
        public Class getColumnClass (int columnIndex)
        {
            return Integer.class;
        }

        @Override
        public String getColumnName (int col)
        {
            return "Column";
        }
    }

    public static final void main (String [] args)
    {
        SwingUtilities.invokeLater (() ->
        {
            try
            {
                new TableSortTest ();
            }
            catch (Exception e)
            {
                e.printStackTrace ();
            }
        });
    }
}

我尝试使用ScheduledExecutorService + RunnableTimer + TimerTask来测试它是否是线程问题,但行为是相同的。我还阅读了有关该主题的Java Tutorial页面。鉴于我的表只使用标准类型,我认为一个简单的table.setAutoCreateRowSorter (true);应该可以完成这项工作,不应该吗?

在每次修改/添加/删除甚至被解雇后,表是否应该对表进行排序?

2 个答案:

答案 0 :(得分:2)

使用setSortsOnUpdates()建议的here @trcs是最好的通用解决方案,但您可以通过选择TableModelEvent可用于子类的优化来优化更新AbstractTableModel

关键问题是setValueAt()的实施。如果您的意思是fireTableRowsUpdated(),而不是fireTableRowUpdated(),请注意参数表示行的范围,不是行和&amp;柱。在这种情况下,因为“表格行中的所有单元格值都可能已更改”,下面的修订示例将调用fireTableDataChanged()。我还更改了模型以管理List<Integer>并将尺寸标准化为N

image

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import javax.swing.*;
import javax.swing.table.AbstractTableModel;

/** @see https://stackoverflow.com/a/36522182/230513 */
public class TableSortTest extends JFrame {

    private final JTable table;
    private final ATableModel model;

    public TableSortTest() {
        setDefaultCloseOperation(EXIT_ON_CLOSE);

        model = new ATableModel();
        table = new JTable(model){
            @Override
            public Dimension getPreferredScrollableViewportSize() {
                return new Dimension(200, 500);
            }
        };
        table.setFillsViewportHeight(true);
        table.setAutoCreateRowSorter(true);

        add(new JScrollPane(table), BorderLayout.CENTER);
        pack();
        setLocationRelativeTo(null);
        setVisible(true);

        Worker worker = new Worker();
        worker.execute();
    }

    private class Pair {

        int index;
        int value;
    }

    private class Worker extends SwingWorker<Void, Pair> {

        private static final int N = 100;
        private final Random r = new Random();

        @Override
        protected Void doInBackground() {
            while (!isCancelled()) {
                for (int i = 0; i < N; i++) {
                    int index = r.nextInt(N);
                    Pair p = new Pair();
                    p.index = index;
                    p.value = Math.abs(r.nextInt());
                    publish(p);
                }

                try {
                    Thread.sleep(1000);
                } catch (InterruptedException ie) {
                    ie.printStackTrace();
                }
            }

            return null;
        }

        @Override
        public void process(List<Pair> items) {
            for (Pair p : items) {
                model.setValueAt(p.value, p.index, 0);
            }
        }
    }

    private class ATableModel extends AbstractTableModel {

        private static final int N = 100;
        private final List<Integer> data = new ArrayList<>(N);

        public ATableModel() {
            final Random r = new Random();
            for (int i = 0; i < N; i++) {
                data.add(Math.abs(r.nextInt()));
            }
        }

        @Override
        public int getColumnCount() {
            return 1;
        }

        @Override
        public int getRowCount() {
            return data.size();
        }

        @Override
        public Object getValueAt(int rowIndex, int columnIndex) {
            return data.get(rowIndex);
        }

        @Override
        public void setValueAt(Object value, int rowIndex, int columnIndex) {
            data.set(rowIndex, (Integer) value);
            fireTableDataChanged();
        }

        @Override
        public Class getColumnClass(int columnIndex) {
            return Integer.class;
        }

        @Override
        public String getColumnName(int col) {
            return "Column";
        }
    }

    public static final void main(String[] args) {
        SwingUtilities.invokeLater(() -> {
            new TableSortTest();
        });
    }
}

认识到这只是一个示例,下面的变体通过发布List<Integer>来优化更新,TableModel通过process()传递给<{1}} import java.awt.BorderLayout; import java.awt.Dimension; import java.util.ArrayList; import java.util.List; import java.util.Random; import javax.swing.*; import javax.swing.table.AbstractTableModel; /** * @ see https://stackoverflow.com/a/36522182/230513 */ public class TableSortTest extends JFrame { private final JTable table; private final ATableModel model; public TableSortTest() { setDefaultCloseOperation(EXIT_ON_CLOSE); model = new ATableModel(); table = new JTable(model) { @Override public Dimension getPreferredScrollableViewportSize() { return new Dimension(200, 500); } }; table.setFillsViewportHeight(true); table.setAutoCreateRowSorter(true); add(new JScrollPane(table), BorderLayout.CENTER); pack(); setLocationRelativeTo(null); setVisible(true); Worker worker = new Worker(); worker.execute(); } private class Worker extends SwingWorker<List<Integer>, List<Integer>> { private static final int N = 100; private final Random r = new Random(); private final List<Integer> data = new ArrayList<>(N); @Override protected List<Integer> doInBackground() throws Exception { while (!isCancelled()) { data.clear(); for (int i = 0; i < N; i++) { data.add(Math.abs(r.nextInt())); } publish(data); try { Thread.sleep(1000); } catch (InterruptedException ie) { ie.printStackTrace(System.err); } } return data; } @Override protected void process(List<List<Integer>> chunks) { for (List<Integer> chunk : chunks) { model.update(chunk); } } } private class ATableModel extends AbstractTableModel { private List<Integer> data = new ArrayList<>(); public void update(List<Integer> data) { this.data = data; fireTableDataChanged(); } @Override public int getColumnCount() { return 1; } @Override public int getRowCount() { return data.size(); } @Override public Object getValueAt(int rowIndex, int columnIndex) { return data.get(rowIndex); } @Override public Class getColumnClass(int columnIndex) { return Integer.class; } @Override public String getColumnName(int col) { return "Column"; } } public static final void main(String[] args) { SwingUtilities.invokeLater(() -> { new TableSortTest(); }); } } xl

Ubound(xl)

答案 1 :(得分:2)

感谢您的快速回答 trashgod 。你是对的,我的意思是fireTableRowsUpdated ()但我编写代码时犯了一个错误,抱歉。关键是fireTableRowsUpdated (rowIndex, rowIndex)fireTableCellUpdated (rowIndex, columnIndex)都无法正确排序列。在实际程序中,大多数表行确实从一次迭代变为下一次迭代,因此调用fireTableDataChanged ()非常有意义。但是我并不想使用它,因为如果我选择一行或多行来向进程发送信号,或者每次更新都会丢失选择。我已经探索过这种方式并找到了两种保留选择的形式,但它有点烦人,其中一种方式会破坏键盘的选择。我接下来会向原始代码展示必要的补充内容。

第一个表单在修改模型之前保存选择,并在每次更新后恢复它:

...
private class Worker extends SwingWorker <Void, Pair>
{
    private int [] selectedRows;

    @Override
    protected Void doInBackground ()
    {
        while (!isCancelled ())
        {
            // Save the selection before modifying the model
            int x = table.getSelectedRowCount ();
            if (x > 0)
            {
                selectedRows = new int [x];
                int [] tableSelection = table.getSelectedRows ();

                for (int i = 0; i < x; i++)
                {
                    selectedRows [i] = table.convertRowIndexToModel (tableSelection [i]);
                }
            }

            Random r = new Random ();
            for (int i = 0; i < table.getRowCount (); i++)
            {
                int indice = getIndexInRange (0, table.getRowCount () - 1);
                Pair p = new Pair ();
                p.index = indice;
                p.value = Math.abs (r.nextInt ());
                publish (p);
            }

            // If I put the code to restore the selection here, it doesn't work...
            try
            {
                Thread.sleep (1000);
            }
            catch (InterruptedException ie)
            {
                ie.printStackTrace ();
            }
        }

        return null;
    }

    @Override
    public void process (List <Pair> items)
    {
       for (Pair p : items)
       {
           model.setValueAt (p.value, p.index, 1);
       }

       // Restore the selection on every update
       if (selectedRows != null && selectedRows.length > 0)
       {
           for (int i = 0; i < selectedRows.length; i++)
           {
               table.addRowSelectionInterval (table.convertRowIndexToView (selectedRows [i]), table.convertRowIndexToView (selectedRows [i]));
           }
       }
    }
}
...

第二种形式使用ListSelectionListenerKeyListener和旗帜。使用键盘进行选择并不起作用。说实话,我不知道我是如何得到这个解决方案的。这可能是偶然的:

public class TableSortTestSolucionConSelectionListener extends JFrame implements KeyListener
{
    ...
    private boolean ctrlOrShiftDown = false;
    private int [] selectedRows;

    @Override
    public void keyPressed (KeyEvent e)
    {
        ctrlOrShiftDown = e.isControlDown () || e.isShiftDown ();
    }

    @Override
    public void keyReleased (KeyEvent e)
    {
       ctrlOrShiftDown = e.isControlDown () || e.isShiftDown ();
    }

    @Override
    public void keyTyped (KeyEvent e)
    {
       ctrlOrShiftDown = e.isControlDown () || e.isShiftDown ();
    }

    public TableSortTestSolucionConSelectionListener ()
    {
        ...
        ListSelectionListener lsl = new ListSelectionListener ()
        {
            @Override
            public void valueChanged (ListSelectionEvent e)
            {
                if (!e.getValueIsAdjusting ())
                {
                    if (!ctrlOrShiftDown)
                    {
                        int x = table.getSelectedRowCount ();
                        if (x > 0)
                        {
                            selectedRows = new int [x];
                            int [] tableSelection = table.getSelectedRows ();

                            for (int i = 0; i < x; i++)
                            {
                                selectedRows [i] = table.convertRowIndexToModel (tableSelection [i]);
                            }
                        }
                    }

                    // Disable the listener to avoid infinite recursion
                    table.getSelectionModel ().removeListSelectionListener (this);

                    if (selectedRows != null && selectedRows.length > 0)
                    {
                        for (int i = 0; i < selectedRows.length; i++)
                        {
                            table.addRowSelectionInterval (table.convertRowIndexToView (selectedRows [i]), table.convertRowIndexToView (selectedRows [i]));
                        }
                    }

                    table.getSelectionModel ().addListSelectionListener (this);
                }
            }
        };

        table.getSelectionModel ().addListSelectionListener (lsl);
        ...      
     }

幸运的是,今天我找到了一种简单的方法来正确排序列并保持当前选择。您只需在代码中添加以下内容:

TableRowSorter trs = (TableRowSorter) table.getRowSorter ();
trs.setSortsOnUpdates (true);

这样fireTableCellUpdated ()fireTableRowsUpdated ()都可以正常工作。据我了解,setAutoCreateRowSorter ()仅用于在单击表标题时对行进行排序。

问候。