Список контактов в виде простого GUI-приложения

Пришла пора сделать из нашего приложения «Список контктов» более элегантное решение. Для этого я расширил наше приложение из раздела Список контактов – начало. Теперь самое время разобраться, что именно я предлагаю вам посмотреть.
В общем-то в части уже готовых классов я поменял только два класса — ContactTest и ContactSimpleDAO.
ContactTest теперь служит просто для запуска основной формы, котораяотображает список контактов. На этом его функциональность заканчивается.

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

Теперь все наше внимание будет сосредоточено на том, каким образом я сделал графический интерфейс (возможно не идеально — серьезные специалисты по GUI наверняка что-нибудь могут подсказать — я в этой области не имел большого опыта).
Графический интерфейс состоит из трех классов:

  1. ContactFrame — основная форма для отображения. Содержит кнопки для редактирования списка контактов и отображает эти самые контакты.
  2. EditContactDialog — диалоговое окно для редактиварония данных выделенного контакта. Появляется при нажатии кнопок «Добавить» или «Исправить»
  3. ContactModel — этот класс предназначен для отображения таблицы контактов. Зачем он нужен — мы узнаем в свое время.

Чтобы наглядно продемонстрировать, что именно у нас получится, предлагаю посмотреть картинки, на которых изображены основная форма и диалоговая форма для ввода данных.
Наша основная форма будет выглядеть вот так:

Назначение кнопок следующее:

  • «Обновить» — перегрузить список контактов.
  • «Добавить» — открыть диалоговое окно для ввода данных нового контакта и сохранения их в нашем DAO
  • «Исправить» — открыть диалоговое окно, загрузить в него данные из выделенной строки, редактировать и сохранить в DAO
  • «Удалить» — удалить выделенную запись

Диалог для ввода данных выглядит вот так:

Давайте сначала разберемся с классом ContactModel. Приведу его код а потом мы рассмотрим вопросы, связанные с этим классом.

Появление этого класса связано с достаточно известным шаблоном проектирования — MVC (Model, View, Controller) — Модель, Отображение, Контроллер (Управление). В интернете можно найти много картинок и тектса, связанных с этим шаблоном, но тем не менее я опише своими словами свое понимание этого шаблона.

Я бы выделил в MVC несколько моментов, которые являются основополагающими:

  1. Есть данные и есть их отображатель — это РАЗНЫЕ вещи. Такая конструкция гораздо удобнее, нежели когда вы смешиваете все вместе.
  2. Отображение данных и их изменение должны быть взаимосвязаны — если данные изменились, значит надо менять изображение

Т.е. если я поменял данные в таблице или списке, то их изображение должно измениться/обновиться. Сигнал об изменении идет через контроллер, который разобравшись, что надо делать (например, надо удалить), меняет модель. А т.к. модель «связана» с отображением, то она «посылает сигнал об изменении», тем самым побуждая отображение перерисовать модель.
Иногда контроллер и отображатель совмещены в одном графическом элементе. Например, для передвижения по таблице мы используем клавиши стрелок вверх/вниз. Таблица «ловит» наши нажатия и передает в модель факт того, что была отмечена другая строка — модель делает себе отметку, что текущая строка такая-то и таблица отображает сей факт. Но мжет быть и не так — например в нашем приложении добавление, изменение и удаление будет производится через выполнение кода обработки нажатий наших кнопок. И наша задача — воздействовать именно на модель, а не на отображение, которым является таблица — в нашем случае это класс JTable (мы скоро увидим использование этого стандартного класса).
И еще раз — постарайтесь увидеть эту связь. Модель (как отдельный объект) может подвергаться изменениям. Но т.к. она связана с отображением, то каждый раз при своем изменении модель посылает отображению сигнал, чтобы оторажение себя перерисовало. Но т.к. отображение при своем рисовании берет данные из модели — мы получим обновление данных уже в изображении.
Так вот, для класса JTable надо, чтобы модель реализовывала интерфейс TableModel. В обычной жизни заниматься реализацией всех функций этого интерфейса нет необходимости и разработчики Java предлагают уже ПОЧТИ готовый класс AbstractTableModel. В этом классе нам достаточно переопределить всего 4 метода.
Теперь наша модель — класс ContactModel — может использоваться совместно со стандартным классом JTable.
В нашем примере мы не используем возможности редактирования модели — просто при загрузке контактов создаем новую модель и отдаем ее таблице.

Класс ContactFrame

Наш класс ContactFrame — я сделал комментарии в коде, которые должны помочь вам разобраться.

Давайте расмотрим вахные (на мой взгляд) моменты. Изучение и анализ кода я бы советовал начинать с двух частей:

  1. Конструктор ContactFrame — в нем мы «строим» нашу форму. Создаем панели, кнопки, настраиваем их взаимодействие
  2. Обработчки нажатий кнопок actionPerformed — именно сюда приходят все команды от кнопок и в нем мы выполняем действия оп редактированию нашего списка контактов

Конструктор ContactFrame

Наш конструктор должен создать необходимые элементы нашей яормы — вот и будем смотреть, как это происходит. На рисунке нашей формы мы видим две области — слева область кнопок, справа — таблица с данными.
Прежде, чем вы станете читать дальше, я вам настоятельно рекомендую посмотреть статью Что такое LayoutManager. В ней вы найдете много информации, которая поможет вам понять, что и как мы делаем.
По умолчанию форма использует BorderLayout, который делит всю форму на пять частей — Север, Юг, Запад, Восток и Центр. Панель с кнопками мы поместим слева, т.е. на Западе. Все остальное пространство будет отдано Центру (т.к. остальные области ничего не содержат).
Панель с кнопками (btnPanel) использует достаточно интересный (и сложный) LayoutManager — GridBagLayout, который хоть и располагает элементы в виде сетки, но предоставляет очень мощные инструменты управлния. Чтобы панель с кнопками не «расползалась» на всю левую сторону, я сначала «кладу» ее на другую панель left (у которой выставляю BorderLayout), и уже эту панель кладу на форму на Запад (слева).
С таблицей все гораздо проще — мы размещаем ее в объекте класса JScrollPane, который позволяет прокручвать элемент внутри себя и уже его кладем на форму в Центр.
Нажатия от кнопок будут обрабатываться нашей формой — пример такой обработки мы уже расcматривали в разделе Интерфейсы. Для этого наша форма реализует интерфейс ActionListener.

Обработка кнопок

Все кнопки вызывают метод actionPerformed и передают туда объект класса ActionEvent. Нас в этом объекте интересует метод getActionCommand(). При создании кнопок мы каждой «выдали» определнное значение, по которому мы теперь и сможем понять, какая именно кнопка была нажата и какая послала нам сообщение. Дальше достаточно просто аккуратно пройти по шагам и вы сами увидите, что мы для каждой кнопки вызываем отдельный метод, который выполняет нужную функцию.
Перегрузка (метод loadContact) и удаление (метод deleteContact) достаточно простые и думаю, что будет достаточно просто посмотреть комментарии (и возможно глянуть документацию по классу JTable).
Что же касается добавления и редактирования (методы addContact и editContact), то они в общем тоже не представляют проблемы — в них мы вызываем диалоговое окно. Но при редактировании мы передаем в это окно выделенный контакт, чтобы заполнить поля в диалоге.

Класс ContactDialog

Думаю, что вы уже сможете разобраться в коде самостоятельно. Но кое-какие моменты хотелось бы обозначить.

  1. Точно так же есть смылс смотреть две «входные точки» — конструктор, где мы строим все элементы и обработчик нажатия кнопок
  2. Я сделал ДВА конструктора — один является основным и выполняет все настройки а также проверяет, что если контакт передали, то надо заполнить поля и (ЧТО ВАЖНО) присваивает ID контакта переменной contactId. Второй сделан исключительно для красоты — и вызывается тогда, когда мы создаем новый контакт. В принципе можно было обойтись одним конструктором — просто передавать в него null
  3. Это есть в статье по LayoutManager — мы полностью отключаем LayoutManager и это позволяет нам размещать элементы жестко по координатам. В этом есть смысл, т.к. диталог не меняет размер и ничего страшного в абсолютных координатах в данном случае я не вижу.
  4. Механизм возврата введеных данных через метод getContact — я сделал его в таком виде. Хотя это не значит, что нельзя сделать иначе (можете подумать и поискать иные варианты). Замечу, что мы создаем контакт и передаем туда переменную contactId. Если это новый контакт — значит она будет равна null и мы можем считать, что то новый контакт. Если же там есть какое-то число — значит это существующий контакт и мы должны его обновить

Полный код примера можно скачать отсюда — ContactProject_02.zip

Домашнее задание

  1. Разобраться в работе примера и сделать несложные изменения — перенести в основной форме кнопки на правую сторону и вниз.
  2. Сделать мультиязычную версию — это сложное задание. С моей точки зрения, надо сделать отдельный класс, который будет загружать ресурс и обладать набором методов для предоставления нужных данных.

Удачи.