Улучшаем интерфейс — листенеры, модель таблицы и потоки

Переделав наше приложения в плане работы с базой данных, вернемся к пользовательскому интерфейсу. Во-первых, мы не реализовали достаточно много функций, во-вторых — наш интерфейс в общем-то далек от совершенства.

Предварительно опишем (еще раз), что мы теперь хотим получить:

  • В верхней части экрана у нас остается тот же самый спин. Но к нему надо приделать листенер, который будет вызывать обновление списка студентов.
  • Список групп. Мы его оставим таким, как был. Только добавим листенер, который будет реагировать на изменение выделенной группы.
  • Список студентов будет изменен. Во-первых будем показывать студентов из выделенной группы, а во-вторых будем показывать их не списком, а таблицей. Для таблицы мы создадим свою собственную модель. Особенности мы обсудим позже.
  • Добавим три кнопки для редактирования студентов в нижнюю часть таблицы со списком студентов.
  • Добавим две кнопки внизу списка групп — для удаления всех студентов из группы и для перевода всех студентов в другую группу. Хорошо наверно было бы сделать возможность выделять студентов и переводить только выделенных, но это мы оставим для самостоятельного изучения. Кроме этого мы сделаем наш список групп постоянным по ширине — иначе, если группы называются 1, 2, 3 и т.д., то наш список будет очень узким.
  • При добавлении нового студента или редактировании существующего будем выводить диалоговое окно. При добавлении студента диалоговое окно не должно закрываться после добавления студента — пользователь может сразу начать вводить данные другого студента. При этом значения для полей ГРУППА и ГОД должны оставаться такими, как были, а все остальные поля очищаются. Конечно же группы выбираются не по ИД, а из списка.
  • Добавим пункты меню «Отчеты», где мы сможем получать различные отчеты. На сегодня у нас только один отчет — список всех студентов.

Теперь давайте постепенно будем приближаться к нашей цели. Стоит отметить, что наш класс ManagementSystem делает все, что нам надо и то, что касается так называемого back-end (такое название относится к той части системы, которая отвечает за работу с базой данных) нас уже мало волнует — все для работы с базой у нас есть. Мы будем просто вызывать те методы, которые нам потребуются.
Итак, давайте сделаем первую версию нашего интерфейса. Мы добавим кнопки внизу и сделаем нашу панель для групп не сжимаемую по ширине. Кнопки можно увидеть в тексте — там ничего нет сложного. Для «несжимаемости» нам надо будет переопределить метод getPreferredSize(). Его можно увидеть в коде.
Мы пока будем смотреть на один файл — StudentsFrame.java. Все остальные не будут изменяться совсем. Как обычно в конце статьи будут приведены тексты для всех файлов.

 

StudentsFrame.java

Теперь мы сделаем так, чтобы наш интерфейс «ожил». Сделаем реакции на кнопки,изменения групп и спинера. Будем вызывать методы, в которых будут стоять «заглушки» — сообщения о том, что метод вызван. У нас сообщения приходят от четырех видов компонентов — меню, кнопка, спинер, список.

Для меню и кнопок используется один вид листенера — ActionListener.
Для списка используется, как мы уже знаем, ListSelectionListener.
Остается только спинер — для него используется ChangeListener.

Давайте теперь расставим все наши листенеры и сделаем «заглушки». Можно запустить наше приложение и проверить все наши управляющие компоненты — по идее все должно работать, т.е. выдавать сообщения на каждое действие — меню, кнопки, перемещение по списку групп, изменение величины в спинере. Комментарии к коду смотрите прямо в тексте программы.

StudentsFrame.java

Модель для таблицы

Теперь давайте реализуем первую команду — перегрузка списка студентов. Как уже упоминалось выше, нам предстоит определить свою модель. Реализация полной модели, т.е. интерфейса TableModel — задача сложная. В большинстве случаев нам необходимо только реализовать несколько методов. Понимая это разработчики Java создали класс AbstractTableModel, который реализует большинство необходимых методов. Для создания своей модели достаточно переопределить всего 3 метода:

  1. public int getRowCount();
  2. public int getColumnCount();
  3. public Object getValueAt(int row, int column);

Если мы запишем список студентов в вектор и определим порядок столбцов, то реализация будет выглядеть достаточно несложно. Конечно, существует возможность использовать таблицу через стандартные вызовы, отдавая ей векторы. Но могу вас уверить — это будет выглядеть сложнее, чем написать свою модель. Тем более, что бОльшая часть работы уже сделана за нас — классом AbstractTableModel.

 

StudentTableModel.java

И теперь можно привести код для обновленного StudentsFrame — там сделано важное изменение — реализован метод reloadStudents(), который загружает список студентов для выделенной группы и года.

StudentTableModel.java

Кажется, что все замечательно — наша программа научилась обновлять список студентов и все смотрится красиво. Но есть одна неприятность. Представьте себе, что список студентов по какой-либо причине не может быть обновлен быстро. Проведите эксперимент — замените метод reloadStudents() на вот такой код:

После этого запустите пример (подождите 3 секунды — форма появиться не сразу) и попробуйте нажать на стрелочку спинера. Как Вам эффект ? Спинер завис, все приложение не реагирует — крайне неприятная ситуация. Причем можно заметить, что несмотря на то, что мы уже обновили модель, изменения не видны. Мы вынуждены ждать.

Если погрузиться чуть глубже в систему рисования Swing, то вы узнаете, что прорисовка компонентов идет в отдельном потоке (треде — EDT, мы уже упоминали его) и, что самое неприятное, именно в этом же треде вызываются методы листенеров. Т.е. мы исправили модель, но т.к. наш метод еще не завершился, обновление экрана не произошло. И, как вы сами понимаете, зависание какого-либо метода внутри обработки выглядит ужасно. Поэтому очень хорошим выходом из данной ситуации может служить многопотоковость. О потоках вы можете прочитать в документации, либо посмотреть очень приличную статью в FAQ Vingrad Многопоточное программирование.
Если определить в двух словах, то поток это отдельный подпроцесс. Любое приложение имеет как минимум один поток, где все выполняется. Если вы создаете еще один поток, то выполнение их происходит как бы параллельно — выполняется то один, то другой. Потоков можно создать очень много и каждый будет выполнять свою работу самостоятельно. Программа будет переключаться с выполнения одного потока на выполнение другого и будет создаваться впечатление, что потоки выполняются одновременно.
Реализуем наш метод в виде потока. Отметим еще одну особенность — Java позволяет вам создавать «анонимные» классы. Т.е. Вы создаете класс прямо в коде, в нем же переопределяя нужные вам методы. Посмотрите код нашего метода

Теперь даже несмотря на то, что наш метод ждет после обновления модели 3 секунды, само обновление экрана происходит сразу. Что несомненно приятно. Команды «очистить группу», «удалить студента» не требуют каких-либо сложных действий. Желательно просто спросить у пользователя подтверждения и все. Приведем код для методов deleteStudent() и clearGroup()

Для сборки нам потребуется команда

javac students/frame/*.java students/logic/*.java
Для сборки примера
javac -encoding UTF-8 students/frame/*.java students/logic/*.java

Для запуска нам надо указать в CLASSPATH файл mysqlJDBC-3.1.13-bin.jar

java -cp .;mysql-connector-java-3.1.13-bin.jar students.frame.StudentsFrame

Теперь мы подошли вплотную к завершающей стадии. Нам осталось только реализовать команды для добавления и редактирования студентов, переноса студентов из одной группы в другую и отчет по всем студентам. Это мы сделаем в следующей части нашего проекта — Часть 6 — GUI — заключительные классы

Архив с исходными кодами: Исходный код