Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
371 views
in Technique[技术] by (71.8m points)

java - setAutoCreateRowSorter doesn't sort table column correctly after update

While developing a small task manager, I have noticed that columns aren't sorted correctly. To discard problems with my program, I have created a minimal version but it still fails to order the unique column right.

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

I have tried with a ScheduledExecutorService + Runnable and a Timer + TimerTask just to test if it was a threading problem, but the behavior is the same. I have also read the Java Tutorial page about the subject. Given that my table only uses standard types I think that a simple table.setAutoCreateRowSorter (true); should do the job, shouldn't it?

Shouldn't the table be sorted after every modification/addition/removal even is fired?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

Thanks for your quick answer trashgod. You're right, I meant fireTableRowsUpdated () but I made a mistake when I wrote the code, sorry. The point is that fireTableRowsUpdated (rowIndex, rowIndex) and fireTableCellUpdated (rowIndex, columnIndex) both fail to sort the column correctly. In the real program most of the table rows do change from one iteration to the next so calling fireTableDataChanged () makes perfect sense. But I didn't want to use it because if I select one or more rows to send a signal to the processes or whatever the selection is lost on every update. I have explored this way and found two forms of preserving the selection but it's a bit annoying and one of them breaks the selection with the keyboard. I show the necessary additions to the original code next.

The first form saves the selection before modifying the model and restores it after every update:

...
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]));
           }
       }
    }
}
...

The second form uses a ListSelectionListener, a KeyListener, and a flag. Selection with the keyboard doesn't work. To be honest, I don't know how did I come to get this solution. It probably was by chance:

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

Fortunately today I have found a simple way to get the column sorted correctly and keep the current selection. You only have to add the following to your code:

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

With this both fireTableCellUpdated () and fireTableRowsUpdated () work as I expected. To my understanding, setAutoCreateRowSorter () is only used to sort the rows when you click on the table header.

Greetings.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...