【问题标题】:setAutoCreateRowSorter doesn't sort table column correctly after update更新后 setAutoCreateRowSorter 无法正确排序表列
【发布时间】:2016-07-30 22:49:23
【问题描述】:

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

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 教程页面。鉴于我的表只使用标准类型,我认为一个简单的 table.setAutoCreateRowSorter (true); 应该可以完成这项工作,不是吗?

不应该在每次修改/添加/删除甚至被触发后对表格进行排序吗?

【问题讨论】:

    标签: java swing sorting jtable integer


    【解决方案1】:

    感谢您的快速回答垃圾神。你是对的,我的意思是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 () 仅用于在单击表头时对行进行排序。

    您好。

    【讨论】:

    • setSortsOnUpdates(true) 在这种情况下可能是最佳的;我会避免KeyListener
    【解决方案2】:

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

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

    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&lt;Integer&gt; 来优化更新,该process()en bloc传递给TableModel

    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();
            });
        }
    }
    

    【讨论】:

      猜你喜欢
      • 2023-02-03
      • 2016-01-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-04-06
      • 1970-01-01
      • 1970-01-01
      • 2014-12-08
      相关资源
      最近更新 更多