Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > Java: GUI и Java FX приложения > JTable, AbstractTableModel и множество потоков


Автор: User1302 18.11.2010, 01:53
Здравствуйте, уважаемые форумчане! smile 

Я новичок в Java.
У меня возникла проблеммка с обновлением данных в JTable, кот. имеет моделью AbstractTableModel с List<> в качестве источника записей.
Раньше не программировал интерфейс и возникшая ситуация ставит под сомнение правильность всей моей модели приложения.

Попробую объяснить:

1. Есть обьекты, названные мною "Section", имеют свои поля (Код, Дата, Комментарий и проч.)
Эти обьекты я хочу видеть в таблице JTable в отдельной строчке для каждого обьекта, где в столбцах - значения полей обьекта Section.
Обьекты Section хранятся в массиве типа List<Section>. Массив List<Section> может пополняться новыми обьектами Section, также обьекты могут быть удалены из массива.
2. Таблица JTable использует AbstractTableModel, кот. берет данные из массива обьектов List<Section>. Вот примерный код, как это происходит:

Код

List<Section> sectionList = SomeClass.getSectionList();

public int getRowCount() {
        return sectionList.size();
}

public Object getValueAt(int rowIndex, int columnIndex) {
        Section section;
        section = sectionList.get(rowIndex);
        switch (columnIndex) {
            case 0:
                return section.getId(); //Для поля КОД
            case 1:
                return section.getDate(); //Для поля ДАТА
//не привожу здесь все методы, дабы не загромождать код
            case 6:
                return section.getComment(); //Для поля Комментарий
            default:
                return null;
        }
    }


3. События.
Действия, кот. я хочу выполнять над обьектом Section: a) изменять его поля;
Действия, кот. я хочу выполнять над массивом List<Section>: a) добавлять обьекты Section в массив; b) удалять обьекты Section из массива;

Требуется, чтоб об этом узнавала таблица (модель) и при каждом изменении обьекта/списка обьектов обновлялись данные таблицы.
Для простоты и по неопытности сейчас я это сделал так:
Модель является подписчиком событий изменения обьекта Section - метод sectionUpdated(Section section){} и подписчиком событий изменения массива List<Section> - методы sectionAdded(Section section) и sectionWillBecomeRemoved(Section section).
Соответственно в указанных методах вызываются методы модели AbstractTableModel - fireTableRowsUpdated(r, r);fireTableRowsInserted(r, r);fireTableRowsDeleted(r, r);

А теперь собственно проблема: все работает, пока обновления происходят в одном потоке.
Поскольку действия, производимые над обьектом Section занимают некот. время и над разными экземплярами Section могут одновременно выполняться действия, каждое действие выполняется в отдельном потоке. Когда действие завершается, обьект меняет свое состояние и об этом узнают все подписчики обьекта Section ч-з метод sectionUpdated(this), в их числе оказывается и моя модель таблицы.
В отдельном потоке также могут производиться действия по добавлению и удалению в List<Section> обьектов.

И вот когда происходит удаление подряд сразу нескольких обьектов Section - уведомляется об этом модель таблицы, выполняется fireTableRowsDeleted(r, r); и вылетает исключение:
Exception in thread "AWT-EventQueue-0" 
java.lang.ArrayIndexOutOfBoundsException: 1
Причем не всегда и при малом количестве обьектов (до десяти) все в порядке. Больше - обязательно исключение. Хотя пару раз не вылетало и при сотне обьектов.
Блокировки synchronized не помогают.

Думаю, проблема вот. в чем - из модели удаляются обьекты(строки) в том потоке, кот. это вызвал, а вот слушатели модели, в том числе JTable обновляются в другом потоке (EventDispatchThread???) и пока это происходит другой поток изменяет модель и уменьшает количество обьектов в массиве List<Section> и получается выход за пределы массива.
Если я неправ, прошу меня поправить!

В общем, прошу помощи в виде более-менее конкретных решений, типа "посмотри в сторону EventQueue.InvokeLater", или "SwingWorker", "EventDispatchThread".
Перечисленные вещи крутятся в голове, но вплотную работать с ними не приходилось пока.
Самое простое решение, кот. пришло в голову - запускать в самой модели отдельный поток на изменение модели, а удаляемые обьекты ставить в очередь.
Но сейчас мне кажется это глупым.

Буду очень признателен за все возможные варианты решений, в том числе радикальных, требующих пускай даже изменения всей модели приложения, ведь истина дороже! smile 
Повторяю, написать готовое решение я не прошу, достаточно совета, но за код тоже буду благодарен!
На всякий случай прикрепляю рабочий вариант приложения, кот. сваял - очень сильно упрощенный вариант для удобства, но ошибка в нем присутствует.
компилируется просто: javac Main.java, запускаетя тоже: java Main

Всех заранее благодарю!

Автор: COVD 18.11.2010, 05:01
Цитата

Думаю, проблема вот. в чем ...

Да. Любые изменения модели следует выполнять в потоке EventDispatchThread. Тогда изменение и перерисовка изменения будут выполняться в одном потоке, т.е. последовательно, а не одновременно. 

Автор: User1302 18.11.2010, 10:38
Цитата

Да. Любые изменения модели следует выполнять в потоке EventDispatchThread. Тогда изменение и перерисовка изменения будут выполняться в одном потоке, т.е. последовательно, а не одновременно. 


Попробовал сделать вот так:
Код

public synchronized void sectionRemoved(final Section section) {
        SwingUtilities.invokeLater(new Runnable() {

            @Override
            public void run() {
                int r = sectionList.indexOf(section);
                fireTableRowsDeleted(r, r);
                sectionList.remove(section);
            }
        });
    }


В итоге вроде все хорошо работает. Правильно так делать?

Автор: Skipy 18.11.2010, 11:31
А sectionList у Вас - это модель таблицы? Не получится, что после получения события таблица обратится к модели для перерисовки, а Вы еще не удалили строку? Я бы поставил sectionList.remove(section) перед уведомлением.

Автор: User1302 18.11.2010, 12:30
Цитата

А sectionList у Вас - это модель таблицы? Не получится, что после получения события таблица обратится к модели для перерисовки, а Вы еще не удалили строку? Я бы поставил sectionList.remove(section) перед уведомлением.


Думаю, вы правы - исправил, хотя работает и так, и так.
sectionList - это список в модели, откуда строчки дергаются.
...
Разобрался немного с моделью событий Swing.

Запускать изменения интерфейса через "SwingUtilities.invokeLater()" мне кажется правильным решением.
Если это не единственное решение, прошу показать еще варианты, если они есть.
А в общем, думаю, тему можно считать закрытой.

Спасибо COVD, Skipy, плюсики ставить еще не получается

Автор: jk1 18.11.2010, 13:54
Цитата

Спасибо COVD, Skipy, плюсики ставить еще не получается

Модератор:  Поставил

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)