1
votes

I have a JTable that is sorted and filtered using a RowSorter and updates in a real time manner. I have an issue when users are trying to create a selection interval with either the mouse (click + drag over rows) or via the keyboard (SHIFT + arrow down multiple times).

The selection interval start and end get corrupted as soon as a table-model update event occurs. I have discovered that if a table is sorted and there is more than one selected row the selection model fires multiple list selection events that clear then recreate the selection on every table model update.

If multiple rows are selected one at a time - all is well. What the user selected remain selected. If however the user tries to create a selection-interval by clicking and dragging the mouse down over multiple rows - the rows get selected correctly until a table model update event fires. The selection then starts building from the currently selected row.

I've attached a small demo application. Try selecting multiple rows using the mouse - every second the selection will get corrupted....

This seems to be a bug (or unwanted behavior) in the Java classes. I'm hoping someone can provide a creative solution/work-around.

    public class TableTest extends JFrame {
    private static final long serialVersionUID = 1L;

    public TableTest() {
        super("Table Selection Problem");
        setBounds(50, 50, 800, 600);
        setDefaultCloseOperation(EXIT_ON_CLOSE);

        // Create a table model
        final DefaultTableModel tableModel = new DefaultTableModel(40,10);

        // Create table row sorter sorted on column 1
        TableRowSorter<TableModel> sorter = new TableRowSorter<TableModel>(tableModel);
        sorter.setSortsOnUpdates(true);
        sorter.setSortKeys(Arrays.asList(new RowSorter.SortKey(1,SortOrder.DESCENDING)));

        // Create a table and bind the sorter
        JTable table = new JTable(tableModel);
        table.setRowSorter(sorter);

        // Bind a list selection listener, this doesn't do anything besides showing the selection change(s) on every table model update
        table.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
            @Override
            public void valueChanged(ListSelectionEvent e) {
                System.out.println(e); 
            }
        });


        // place the table in the frame
        getContentPane().add(new JScrollPane(table), BorderLayout.CENTER);

        // Simulate some application events that trigger a table update...
        new Thread() {
            public void run() {
                try {
                    final Random r = new Random();
                    for(int x = 0; x < 1000; x++) {
                        // update a table cell (not even an add or delete)
                        SwingUtilities.invokeLater(new Runnable() {
                            public void run() {
                                tableModel.setValueAt(r.nextInt(1000), r.nextInt(40), r.nextInt(10));
                            }
                        });
                        // wait 1 second before next table update
                        Thread.sleep(1000);
                    }
                } catch(Throwable t) {
                    t.printStackTrace();
                }
            }
        }.start();      
    }


    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                (new TableTest()).setVisible(true);                     
            }
        });     
    }
}
1
Have to say, not really seeing a problem. As from perspective, the selection is been between sorts, that is, if I select row 1-5 and a new row is inserted before it, that shift down. If a new row is inserted somewhere between them, then the selection splits, which would be my expectation, as the selection is based on the data, not the row (from a high level perspective)MadProgrammer
That's how the code works when there isn't a table model update while extending the selection interval. If you run my test app - it's sending a table model update every second. Click on row 2 of the table and start dragging the mouse up/down say to select rows 2 thu 7 then back up a bit so rows 2 thru 4 are selected then back down so rows 2 thru 10 are selected. A table model update occurs during the selection process. The anchor selection (row 2) gets trashed and the selection now starts expanding from where ever the mouse is (when the update occurred) - to where ever it is moved to.Graeme Ingleby
Okay, so maybe you need to isolate the updates in someway, so that while a list selection update is occurring, the updates are queued until the list selection is complete, as an example...MadProgrammer
Just to clarify - what MadProgrammer describes (select rows 1-5 and new row inserted before it shifts down, if new row inserted between selection splits) is also the behavior I would expect. My issue occurs when you are dragging the selection with the mouse over a period of time. A quick selection e.g. click 2 shift click 10 works fine. It's when you hold shift key down and keep dragging the mouse it goes wrong. (if table update occurs while SelectionModel.isValueAdjusting=true)Graeme Ingleby
I agree that live updates while making a selection are a bad idea. ..and I do pause the event stream so no updates occur while the user is making a selection - but in the full app changed cells are flashed (black background for 8 seconds). I stop the updates, start the selection process but when flashed cells update a table-cell update occurs which then breaks the selection :)Graeme Ingleby

1 Answers

2
votes

When I ran your code using JDK7 on Windows 7 I found that the setSortsOnUpdates(...) was causing problems with the data in the table. That is I let the code run for a while and I noticed that no values in column 1 where being displayed. So then I increased the width of the frame and suddenly the values appeared in sorted order. So for some reason the table was not being repainted even though the data changed. When I commented the setSortsOnUpdates(...) the changes to the TableModel appeared right away. I have no idea what the problem is.

So then I changed the approach to manually invoke a sort when the data is changed. However, I only did the sort when the ListSelectionListener was not adjusting. Here is the code:

import java.awt.*;
import java.awt.event.*;
import java.util.Random;
import java.util.Arrays;
import javax.swing.*;
import javax.swing.table.*;
import javax.swing.event.*;

    public class TableTest5 extends JFrame {
    private static final long serialVersionUID = 1L;

    private boolean isAdjusting = false;
    private boolean isModelChanged = false;
    TableRowSorter<TableModel> sorter;
    final Random r = new Random();

    public TableTest5() {
        super("Table Selection Problem");
        setBounds(50, 50, 800, 600);
        setDefaultCloseOperation(EXIT_ON_CLOSE);

        // Create a table model
        final DefaultTableModel tableModel = new DefaultTableModel(30,3);

        // Create table row sorter sorted on column 1
//        TableRowSorter<TableModel> sorter = new TableRowSorter<TableModel>(tableModel);
        sorter = new TableRowSorter<TableModel>(tableModel);
        sorter.setSortsOnUpdates(true);
        sorter.setSortKeys(Arrays.asList(new RowSorter.SortKey(1,SortOrder.DESCENDING)));

        // Create a table and bind the sorter
        JTable table = new JTable(tableModel);
        table.setRowSorter(sorter);

        // Bind a list selection listener, this doesn't do anything besides showing the selection change(s) on every table model update
        table.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
            @Override
            public void valueChanged(ListSelectionEvent e) {
                isAdjusting = e.getValueIsAdjusting();

                if (!isAdjusting && isModelChanged)
                {
                    isModelChanged = false;
                    sorter.sort();
                }
            }
        });


        // place the table in the frame
        getContentPane().add(new JScrollPane(table), BorderLayout.CENTER);

        // Simulate some application events that trigger a table update...
        new Thread() {
            public void run() {
                try {
                    //final Random r = new Random();
                    for(int x = 0; x < 1000; x++) {
                        // update a table cell (not even an add or delete)
                        SwingUtilities.invokeLater(new Runnable() {
                            public void run() {

                                int value = r.nextInt(1000);
                                int row = r.nextInt(30);
                                int column = r.nextInt(5);
                                System.out.println(value + " : " + row + " : " + column);
                                tableModel.setValueAt(value, row, column);

                                if (!isAdjusting)
                                    sorter.sort();
                                else
                                    isModelChanged = true;
                            }
                        });
                        // wait 1 second before next table update
                        Thread.sleep(1000);
                    }
                } catch(Throwable t) {
                    t.printStackTrace();
                }
            }
        }.start();

    }


    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                (new TableTest5()).setVisible(true);
            }
        });
    }
}

You might want to consider using a TableModelListener to listen for TableModel changes to manually invoke the sort.