Список контактов в виде простого 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. Сделать мультиязычную версию — это сложное задание. С моей точки зрения, надо сделать отдельный класс, который будет загружать ресурс и обладать набором методов для предоставления нужных данных.

Удачи.

И теперь нас ждет следующая статья: Что такое JAR-файлы.

9 comments to Список контактов — GUI приложение

  • Максим  says:

    Антон, спасибо Вам за учебные материалы, как видео, так и статьи.
    1. В комментарии к методу «public Object getValueAt(int row, int col)» класса «ContactModel extends AbstractTableModel» Вы пишете о том, что мы отдаем строковое представление (однако приведения типов мы почему-то не делали). В то же время когда я пытаюсь вставить туда некий объект (не String), то в выведенной таблице объект отображается в виде «ru.pakage.NameOfClass@40bad95c». Выходит по умолчанию в ячейках таблицы делается toString()? Возможно ли в ячейках таблицы выводить объекты в графическом (не строковом) виде.
    2. Возможно ли вставить таблицу в таблицу (чтобы строка разделилась на 2-3)?

    • admin  says:

      1. Метод отдает тип Object, но когда он отображается в ячейке, то приводится к строке через toString().
      Соответственно, мы и отдаем строковое представление для каждой колонки.
      2. Можно «вручную» рисовать внутри ячейки или вставлять свой компонент туда — но я не сильно в этом разбирался, посему не скажу точно что и как.

  • Максим  says:

    Видимо для реализации моего страстного желания разделения строк, косвенным образом, должен помочь метод «void setValueAt(Object aValue, int rowIndex, int columnIndex)» и двойное употребление цикла forEach (в первом цикле — для внешней коллеции, во втором — для внутреннего списка).

    • admin  says:

      Я уже ответил, что в графической библиотеке я сильно не разбирался.

  • Максим  says:

    Относительно разделения строк разобрался. Для этого пришлось написать:
    1. Метод для определения однозначным образом конкретного элемента внешнего списка по строке таблицы row.
    2. Метод для доступа к строке вложенного списка, учитывая впереди идущие члены внешнего списка, также по строке таблицы row.

  • Дмитрий  says:

    У меня есть вопрос и пара замечаний.
    1. В классе ContactFrame в строке 143, где мы извлекаем из ячейки значение:

    зачем нам getModel()? Без него тоже работает, в чём разница?
    2. Там же, две следующие строчки:

    Мы создаём переменную Contact, но в конструктор для ecd передаём анонимный объект. Так что либо избыточная переменная, либо её передавать в конструктор.

    Ещё замечаю много опечаток в тексте, есть ли какая-нибудь возможность Вам сообщать о них? Не хочется этим забивать комментарии.

    • admin  says:

      Ла, это ошибочка — исправил. Спасибо.
      По поводу опечаток — бывает, не всегда удается отследить. Можно присылать на адрес: webinar@java-course.ru

  • Денис  says:

    Добрый день.
    Можете подсказать по домашнему заданию?
    Я заменил строгие названия кнопок на переменные и передаю в эти переменные данные (русский, английский язык) но перерисовать кнопки с новыми данными в ContactFrame не могу.
    Как можно перерисовать кнопки с новыми данными?

    • admin  says:

      Не очень понимаю идею, но наверно надо вызвать у формы repaint(). Хотя как я у еж неоднократно писал, графическое приложение написано просто для того, чтобы было не так скучно. Я крайне неглубоко погружался в Swing. Так что поищите информацию еще где-нибудь.

Leave a reply

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code class="" title="" data-url=""> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong> <pre class="" title="" data-url=""> <span class="" title="" data-url="">

Лимит времени истёк. Пожалуйста, перезагрузите CAPTCHA.