Потерянный список JTable, потерянный при обновлении строки

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

Я считаю, что столкнулся с этой ошибкой GlazedList, но я не могу найти обходной путь для нее: https://java.net/jira/browse/GLAZEDLISTS-194

Я создал некоторый исходный код, который демонстрирует проблему:

public class SelectionLost {

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable(){
            @Override
            public void run() {
                /*
                 * Create and set up the table.
                 */
                final JTable table = new JTable();
                final EventList<DisplayElement> data = new BasicEventList<>();
                ObservableElementList<DisplayElement> observeData = 
                        new ObservableElementList<>(data, new DisplayConnector());
                final SortedList<DisplayElement> sortedData = 
                        new SortedList<>(observeData);
                sortedData.setMode(SortedList.STRICT_SORT_ORDER);
                //populate our list with some data.
                data.add(new DisplayElement("a", "a"));
                data.add(new DisplayElement("b", "b"));
                data.add(new DisplayElement("c", "c"));
                data.add(new DisplayElement("d", "d"));
                //Set up the table models.
                AdvancedTableModel<DisplayElement> model
                        = GlazedListsSwing.eventTableModelWithThreadProxyList(sortedData, new DisplayTableFormat());
                table.setModel(model);
                final AdvancedListSelectionModel<DisplayElement> select
                        = GlazedListsSwing.eventSelectionModelWithThreadProxyList(sortedData);
                table.setSelectionModel(select);
                TableComparatorChooser<DisplayElement> tc = TableComparatorChooser.install(
                        table, sortedData, TableComparatorChooser.MULTIPLE_COLUMN_MOUSE_WITH_UNDO);
                //sort on the first column
                tc.appendComparator(0, 0, false);
                //select the first row
                table.getSelectionModel().setSelectionInterval(0, 0);

                /*
                 * Create UI elements
                 */
                JFrame frame = new JFrame();
                JPanel panel = new JPanel(new BorderLayout());
                frame.add(panel);
                JScrollPane scroll = new JScrollPane(table);
                panel.add(scroll);
                frame.pack();
                frame.setVisible(true);
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                final Random r = new Random();
                /*
                 * Create a timer which will change the elements data. When the selected
                 * element's data is changed so that it moves into a new index position, 
                 * the selection is lost.
                 */
                TimerTask tt = new TimerTask() {
                    @Override
                    public void run() {
                        data.getReadWriteLock().writeLock().lock();
                        try{
                            int row = r.nextInt(data.size());
                            int col = r.nextInt(2);
                            Character c = (char)(r.nextInt(26)+'a');
                            DisplayElement d = data.get(row);
                            if(col == 0)
                                d.setFirst(c.toString());
                            else
                                d.setSecond(c.toString());
                        }
                        finally{
                            data.getReadWriteLock().writeLock().unlock();
                        }
                    }
                };
                java.util.Timer t = new java.util.Timer();
                t.schedule(tt, 250, 250);
            }
        });
    }
    /*
     * These are the data elements stored in the table
     */
    public static class DisplayElement implements Comparable<DisplayElement>{
        private String first, second;
        private PropertyChangeSupport pcs = new PropertyChangeSupport(this);

        public DisplayElement(String first, String second) {
            this.first = first;
            this.second = second;
        }

        public String getFirst() {
            return first;
        }

        public void setFirst(String first) {
            this.first = first;
            pcs.firePropertyChange("first", null, null);
        }

        public String getSecond() {
            return second;
        }

        public void setSecond(String second) {
            this.second = second;
            pcs.firePropertyChange("second", null, null);

        }

        @Override
        public int compareTo(DisplayElement o) {
            int comp = first.compareTo(o.first);
            if(comp != 0)
                return comp;
            return second.compareTo(o.second);
        }
        public void addPropertyChangeListener(PropertyChangeListener l){
            pcs.addPropertyChangeListener(l);
        }
        public void removePropertyChangeListener(PropertyChangeListener l ){
            pcs.removePropertyChangeListener(l);
        }
    }
    /*
     * Table format for glazed lists
     */
    public static class DisplayTableFormat implements AdvancedTableFormat<DisplayElement>, WritableTableFormat<DisplayElement>{
        @Override
        public int getColumnCount() {
            return 2;
        }

        @Override
        public String getColumnName(int i) {
            if(i == 0 )
                return "first";
            else
                return "second";
        }

        @Override
        public Object getColumnValue(DisplayElement e, int i) {
            if(i == 0)
                return e.first;
            else
                return e.second;
        }

        @Override
        public Class getColumnClass(int i) {
            return String.class;
        }

        @Override
        public Comparator getColumnComparator(int i) {
            return GlazedLists.comparableComparator();
        }

        @Override
        public boolean isEditable(DisplayElement e, int i) {
            return true;
        }

        @Override
        public DisplayElement setColumnValue(DisplayElement e, Object o, int i) {
            if(i == 0)
                e.first = (String) o;
            else
                e.second = (String)o;
            return e;
        }
    };
    /*
     * Connector for observable lists.
     */
    public static class DisplayConnector implements ObservableElementList.Connector<DisplayElement>{
        ObservableElementList<? extends DisplayElement> list;
        PropertyChangeListener myListener = new PropertyChangeListener() {
            @Override
            public void propertyChange(PropertyChangeEvent evt) {
                list.elementChanged(evt.getSource());
            }
        };
        @Override
        public EventListener installListener(DisplayElement e) {
            e.addPropertyChangeListener(myListener);
            return myListener;
        }

        @Override
        public void uninstallListener(DisplayElement e, EventListener el) {
            e.removePropertyChangeListener(myListener);
        }

        @Override
        public void setObservableElementList(ObservableElementList<? extends DisplayElement> oel) {
            list = oel;
        }
    }
}

1 ответ

Решение

Попробовав многочисленные исправления, я наконец нашел то, что решило проблему. Я создал слушателя, который восстановил выбор. Единственным недостатком этого является то, что пользователь больше не может "отменить выбор" строки, нажав CTRL+ щелкнув по ней. Если кто-нибудь может придумать лучшее решение, я буду рад его увидеть. Я нахожу это немного хакерским.

    /*
     * This listener fixes the problem where we would lose selection.
     * Note that it prevents the user from "unselecting" a row.
     */
    public static class GlazedListBug194Listener <T> implements ListSelectionListener {
        private T lastSelectedElement;
        private final JTable table;
        private final AdvancedTableModel<T> model;
        private final AdvancedListSelectionModel<T> select;

        public GlazedListBug194Listener(JTable table, 
                AdvancedTableModel<T> model, 
                AdvancedListSelectionModel<T> select) {
            this.table = table;
            this.model = model;
            this.select = select;

        }

        @Override
        public void valueChanged(ListSelectionEvent e) {
            int selectedRow = table.convertRowIndexToModel(table.getSelectedRow());
            if(selectedRow < 0){
                if(table.getRowCount() == 0){
                    //table was cleared
                    lastSelectedElement = null;
                }
                else{
                    //restore selection
                    for(int i = 0; i < table.getRowCount(); i++){
                        if(model.getElementAt(i) == lastSelectedElement){
                            select.setSelectionInterval(i, i);
                            break;
                        }
                    }
                }
            }
            else{
                lastSelectedElement = model.getElementAt(selectedRow);
            }
        }
    }
Другие вопросы по тегам