Примеры использования компонента JTable

Возможно, вам приходилось сталкиваться с необходимостью вывода каких-либо данных в виде таблицы, используя Swing(Java). Самый естественный подход — использование JTable. Но это не самый удобный компонент, поскольку базовая версия не обладает должной гибкостью, а реализация своих способов вывода и отображения информации вызывает определенные трудности. Поэтому я хочу поделится с вами своим опытом работы с JTable и привести максимально доступные для понимания примеры. Наша цель — корректное отображение динамических данных в JTable. Заинтересованных читателей прошу под кат.

Разработчики JTable создали его по парадигме «модель-вид-контроллер». Другими словами, это разделило JTable на части таким образом, что одна отвечает за способы отображения информации, другая за внешний вид, третья за получение данных. Не будем углубляться в подробности, кому интересно, можете поискать в сети. Наша задача разобраться на примерах, как организовать вывод данных из какой-либо структуры в таблицу. За это отвечает модель таблицы.

Самый простой способ создания JTable — массив. Конструктору передается массив названий столбцов и массив значений. Все остальное таблица делает сама. Приведу пример.

В результате получится вот такое вот окно: Рассмотрим подробнее. Сначала создаются два массива. Один содержит заголовки, второй данные для таблицы. Массив с данными, в каждой строке должен содержать такое же количество элементов, как и массив с заголовками. Таблица создается конструктором, принимающим два элемента, в качестве которых выступают наши массивы. При этом таблица создает модель данных сама. Потом мы создаем JScrollPane, которая нужна для возможности прокрутки содержимого таблицы. Добавляем таблицу на объект JScrollPane, а его в наш контейнер. Отображаем окно. В принципе ничего сложного.

Но использование массивов для задания значений неудобно. Во первых данные могут лежать в БД, тогда будет необходимо их преобразовать в массив. Это неудобно и неэффективно. Кроме-того, при изменении данных, придется заново создавать таблицу, с новыми массивами.

Решением этой проблемы является создание своей модели таблицы. Модель таблицы можно создать разными способами. Можно пойти более сложным путем, создав класс, реализующий интерфейс TableModel. Тогда придется описывать все необходимые методы интерфейса и разбираться, как они работают. Кроме того, нужно будет хранить слушатели событий для модели данных. Мы рассмотрим более простой способ. Класс AbstractTableModel содержит реализацию всех методов интерфейсаTableModel за исключением getValueAt(), getRowCount() и getColumnCount(). Создадим свой класс, унаследовав его от AbstractTableModel и реализуем эти методы.

Я максимально упростил реализацию методов моей модели. Теперь в нашем классе JTableExample изменим строчку с конструктором таблицы:

Запустим приложение и посмотрим что получилось. Вот мой результат: Сам объект jTabPeople использует методы модели MyTableModel для получения значений в столбцах. Мы можем, например, так же переопределить метод getColumnName(). Это позволит задать произвольные имена столбцов. Сначала изменим ширину окна и таблицы в классе JTableExample, что бы можно было нормально отобразить все названия:

Теперь добавим метод getColumnName() в класс MyTableModel:

Наша реализация getColumnName() возвращает для первых трех столбцов «Column» и его порядковый номер, начиная с единицы, а для остальных — «Other Column». Вот что получилось:

На основе этих знаний попробуем создать таблицу, отображающую полезные данные. Например, у нас есть класс Human, у которого есть 3 поля name, surname и telephone. Реализация очень проста:

Предположим, что нам нужно хранить список сущностей класса Human, используя ArrayList. Наша таблица должна отображать информацию о каждой сущности.  Для начала изменим модель. Добавим поле humans в нашу модель, а так же изменим конструктор модели:

Примечание: Не совсем правильно поступать так с точки зрения парадигмы ООП, но это всего лишь пример. Я предпочитаю создавать отдельный класс, который хранит в себе все сущности, и контролирует добавление, удаление и связанные с этим события.

Конструктор очень простой. Сначала выполняется создание супер-класса, после чего полю humans присваивается ссылка на аналогичный ArrayList. Для чего это нужно? Данные о сущностях находится вне нашей таблицы, но она должна иметь возможность с ними работать. Поэтому, мы просто передаем ссылку на ArrayList.

Теперь нужно изменить количество столбцов и строк. Количеством строк будет количество наших сущностей (по строчке на одного человека). Количество столбцов — 3, то есть имя, фамилия и телефон. Получилось вот так:

Теперь нужно изменить названия столбцов. Просто подкорректируем наш метод, возвращающий названия столбцов:

Ничего сложно. Ну и осталось изменить метод getValueAt(). У меня получилось вот так:

Оператор switch определяет столбец, и в зависимости от него возвращает либо имя, либо фамилию, либо телефон. Сам объекта humans с помощью метода get() возвращает элемент по нужныму номером. В нашем случае номер строки (Row) и является номером элемента в humans.

Осталось немного изменить процесс создания JTable. Например, вот так:

Так же можно изменить размеры окна и прокручиваемой области, так как содержимое таблицы прилично уменьшилось. Запустим приложение. У меня получилось вот так:

Уже более практично. Есть один нюанс. Таблица запрашивает у модели данные, только в случаи каких-либо событий. Если мы просто добавим в список humans новую сущность, ничего не произойдет. Для того, что бы таблица узнала об изменении данных, нужно вызвать у модели метод fireTableDataChanged(). Он «пошлет сигнал», что данные изменились и тогда таблице придется перерисовать записи. Давайте проверим. Для начала создадим кнопку и повесим на нее слушатель:

Как видите, кнопка добавляет новую запись в наш список. Запустим приложение, нажмем на кнопку — и ничего не происходит. Теперь после добавления элемента, сразу же вызовем fireTableDataChanged(), то есть вот так:

Ситуация изменилась:

Теперь после каждого нажатия кнопки, данные обновляются в таблице.

Кстати, с коллегой обсуждали еще такой вопрос, если интересно — Как добавить строки в JTable с помощью метода AbstractTableModel?

Вот на сегодня и все. Пробуйте, старайтесь, задавайте свои ответы. Удачи!

Комментарии 32

  • спс помогло ознакомиться с компонентом — шоб мы все делали без интернета 🙂

  • Спасибо, но желательно еще выкладывать код целиком, так легче разобраться (по крайней мере мне)

  • Я в начале выложил весь код, а потом уже модифицировал существующий. Думал так будет понятно. В следующий раз учту ваше мнение.

  • Нормально! Как раз сдаю Swing на http://knowledgeblackbelt.com
    Осталось только JTable доизучать. А есть намерение по JTree накидать что-нибудь?

    • Пока нету. А нужно?

      • //Создадим список из сущностей класса Human
        humans = new ArrayList();
        humans.add(new Human(«John», «Smith», «1231231»));
        humans.add(new Human(«George», «White», «321321312»));
        humans.add(new Human(«Olga», «Bregneva», «7171711»));
        //Создадим модель таблицы
        tModel = new MyTableModel(humans);
        //На основе модели, создадим новую JTable
        jTabPeople = new JTable(tModel);

        непонятно куда этот код вставлять?
        в конструктор класса JTableExample?

  • Что можно еще здесь докрудить, чтобы пользователю дать возможность удобно отредактировать данные, в том числе обеспечить элементарные операции по суммированию значений ввиде итога по столбцу?

    • Ну смотрите. Можно сделать так. Пользователь выделяет ячейки, жмет кнопку. Вы по ивенту все выделенные ячейки суммируете и записываете в нужное место результат. Докрутить придется работу с выделенными ячейками.
      В данный момент я занят, кодом не помогу. Но если у вас еще есть вопросы, кроме этого, то в свободное время я могу написать статью, где попытаюсь на них ответить.

  • Привет. Как сделать генерацию первого рядка и столбца, аля Excel, что бы генерировалось только при прокрутке?

  • Подскажите пожалуйста, как в этом примере брать данные из базы данных? Спасибо.

  • Спасибо за пояснения, очень доходчиво.
    Единственное до чего долго не мог додуматься — от куда набираются данные в методе getValueAt(), класса MyTableModel. Потом понял, что там проходит итерация и значениями выступают номера стлбцов и строк.

    Добавлю свои «5 копеек» в правку урока (на больше не тянет), наверняка новичкам нервы сэкономит.

    В этом паттерне используется переменная «tModel», которая не была объявлена до конструктора.


    //Создадим список из сущностей класса Human
    humans = new ArrayList();
    humans.add(new Human("John", "Smith", "1231231"));
    humans.add(new Human("George", "White", "321321312"));
    humans.add(new Human("Olga", "Bregneva", "7171711"));
    //Создадим модель таблицы
    tModel = new MyTableModel(humans);
    //На основе модели, создадим новую JTable
    jTabPeople = new JTable(tModel);

    Объявить переменную нужно до конструктора:

    public class JTableExample {
    MyTableModel tModel;
    //Конструктор
    }

    • В принципе, когда писал об этом не задумывался. Так как я не скинул готовую программу, а просто написал статью. Думаю, логично, если переменная не объявлена — ее нужно объявить :).

  • Наполовину заработало!
    Обьясните пожалуйста срочно кто-нибудь, как правильно вставить (где изменить) вот этот кусок:

    /*Добавим поле humans в нашу модель,
    а так же изменим конструктор модели:*/ ArrayList<Human> humans;
    MyTableModel(ArrayList<Human> humans) {
    super();
    this.humans = humans;
    }

  • Понимаю, что нужно так вставлять:

    public class MyTableModel extends AbstractTableModel{

    MyTableModel(ArrayList<Human> humans) {
    super();
    this.humans = humans;
    }

    Но дает ошибку The constructor MyTableModel() is undefined

  • подскажите как выравнивать текст в столбцах.например первый столбец текст по середине. второй по умолчанию, третий по левому краю. спасибо.

  • Наконец-то обновил статьи. Исправил пару ошибок. Должно быть все ОК.

  • Как сделать, чтобы данные из таблицы сохранялись и читались из файла xls?

    • Для этого вам нужно реализовать интерфейс взаимодействия с xls файлами, либо найти готовый. По сути нужно просто считывать ячейки и создавать на их основе таблицу и наоборот. У вас будет своеобразная TableModel вот и все.

  • Огромное спасибо за статью! Нашел ответы на вопросы.

  • Дом меня всё равно не доходит, что делать с этим куском кода
    humans = new ArrayList();
    humans.add(new Human(«John», «Smith», «1231231»));
    humans.add(new Human(«George», «White», «321321312»));
    humans.add(new Human(«Olga», «Bregneva», «7171711»));
    tModel = new MyTableModel(humans);

    Выдаёт ошибку Cannot resolve symbol ‘humans’

    • Дело в том, что этот кусок кода относиться к коду, который выше. И без объявления humans у вас само собой не работает. Нажмите на странице Ctrl + F и впишите «ArrayList humans;».

  • Куда вставить кусок кода с кнопкой? Ничего не получается 🙁 Надо ли что-то дополнительно дописывать?

  • Внесу и свой маленький вклад в развитие данной статьи.
    Несколько часов потратил на то, чтобы разобраться, почему же не обновляется таблица после добавления элемента.
    Дело в том, что метод fireTableDataChanged должен вызываться из класса, расширяющего AbstractTableModel, а не из Listener’a, как показано в вашем примере.
    Возможно, я ошибаюсь, и вы имели в виду именно то, что я описал выше. Однако, даже если это так, это не совсем очевидно.

    • Да, забыл оговориться, что у меня для хранения используется другая коллекция, на основе которой создается коллекция типа ArrayList. Скорее всего, моя ошибка заключалась в том, что я добавлял элементы в «родную» коллекцию, а не в коллекцию типа ArrayList.

      • В любом случае, на мой взгляд, правильнее было бы описать в классе MyTableModel следующий метод:
        public void updateTable(String name, String surname, String telephone){
        humans.add(new Human(name, surname, telephone));
        fireTableDataChanged();
        }
        Тогда код для Listener’a будет выглядеть следующим образом:

        @Override
        public void actionPerformed(ActionEvent ae) {
        tModel.updateTable(«Vasya», «Pupkin», «12300123»);
        }

  • Отличная статья! Так держать…

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *