Библиотека swing для java руководство для начинающих

Хотите быстро узнать о создании оконных приложений в Java? Да? Тогда данное руководство специально для вас!

Описание: Это практическое введение в Swing — первая часть серии по Swing-программированию, состоящей из двух частей. В данном руководстве рассмотрены основные компоненты библиотеки Swing. Java-программист и любитель Swing Майкл Абернети рассказывает об основных строительных блоках и о процессе создания простого, но функционального Swing-приложения.

Содержание

  • Перед началом работы
    • О данном руководстве
    • Загрузка инструментальных средств и исходных кодов
  • Введение в Swing
    • Введение в UI
    • Роль Swing
    • MVC
    • JComponent
  • Простые Swing-виджеты
    • JLabel
    • JButton
    • JTextField
    • JFrame
    • Простое приложение
  • Дополнительные Swing-виджеты
    • JComboBox
    • JPasswordField
    • JCheckBox/JRadioButton
    • JMenu
    • JSlider
    • JSpinner
    • JToolBar
    • JToolTip
    • JOptionPane
    • JTextArea
    • JScrollPane
    • JList
    • JTable
    • JTree
  • Концепции Swing
    • Схемы, модели и события
    • Простые схемы
    • GridBagLayout
    • События
    • Модели
    • Примеры модели
  • Объединение всего этого вместе
    • Пример приложения
    • Шаг 1: Размещение компонентов
    • Шаг 2: Инициализация данных
    • Шаг 3: Управление событиями
    • Шаг 4: Модели
    • Шаг 5: Звонки и свистки
    • И напоследок…
  • Резюме
    • Резюме
    • Дальнейшие действия
  • Ресурсы
    • Научиться
    • Получить продукты и технологии
  • Об авторе

Перед началом работы

О данном руководстве

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

Во время работы с данным руководством вы изучите все начальные компоненты Swing; под начальными компонентами я понимаю те компоненты, используя которые можно создавать простые пользовательские интерфейсы (UI). Вы узнаете, как использовать базовые методы для установки их свойств, и как эти Swing-компоненты взаимодействуют с другими компонентами. Вы также прочтете о других UI-концепциях, необходимых для полного знания Swing, включая схемы, события/прослушиватели и модели данных. К концу руководства вы должны уметь создавать простое Swing-приложение.

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

Если после завершения работы с этим руководством вы заинтересуетесь дальнейшим изучением Swing-программирования, вы должны прочитать сопутствующее руководство «Swing для среднего уровня«, которое базируется на концепциях и примере приложения, разработанном здесь.

Загрузка инструментальных средств и исходных кодов

Для работы с данным руководством вам необходимо следующее программное обеспечение:

  • JDK 5.0.
  • IDE или текстовый редактор. Я рекомендую Eclipse (ссылки на дополнительную информацию по Eclipse можно найти в разделе Ресурсы).
  • swing1.jar для системы резервирования билетов.

Введение в UI

Перед началом изучения Swing вы, как настоящий новичок, должны задать вопрос, что такое UI? Ответ для новичков — это «пользовательский интерфейс». Но поскольку цель данного руководства заключается в том, чтобы вы больше не были новичком, нам нужно более широкое определение, чем это.

Итак, я опять задаю вопрос: что такое UI? Вы могли бы определить его как кнопки, на которые нажимаете, поле адреса, в которое вводите информацию, и окна, которые открываете и закрываете. Все это — элементы UI, но это не только вещи, которые вы видите на экране. Мышка, клавиатура, громкость музыки, цвета экрана, используемые шрифты и расположение объекта по отношению к другому объекту — все это тоже составляет UI. Вообще говоря, любой объект, принимающий участие во взаимодействии пользователя и компьютера, является частью UI. Это кажется таким простым, что вы, должно быть, удивитесь тому, как много людей и корпораций занимаются этим годами. Действительно, сейчас есть дисциплины в колледжах, полностью посвященные этому взаимодействию.

Роль Swing

Технология Swing — это UI Java-платформы. Она выступает как программное обеспечение, управляющее всем взаимодействием пользователя и компьютера. По существу, она служит посредником между пользователем и внутренностями компьютера. Как Swing делает это? Он предоставляет механизмы для управления аспектами UI, описанными в предыдущем разделе:

  • Клавиатура: Swing предоставляет способ перехвата пользовательского ввода.
  • Цвета: Swing предоставляет способ менять цвета, которые вы видите на экране.
  • Текстовое поле для ввода: Swing предоставляет текстовые компоненты для обработки всех повседневных задач.
  • Громкость музыки: Ну что ж … Swing не совершенен.

В любом случае Swing предоставляет вам все инструменты, необходимые для создания вашего собственного UI.

MVC

Swing идет даже на шаг дальше и реализует известный шаблон проектирования c основными принципами UI. Этот шаблон проектирования называется Модель-Представление-Контроллер (Model-View-Controller — MVC) и стремится «разделить роли». MVC хранит код, ответственный за внешний вид чего-нибудь, отдельно от кода, обрабатывающего данные, и отдельно от кода, реагирующего на взаимодействие и выполняющего изменения.

Вы смущены? Будет легче, если я вам приведу не технический пример этого шаблона проектирования из реального мира. Представьте показ мод. Считайте, что это ваш UI и вообразите одежду данными, компьютерной информацией, которую вы представляете вашему пользователю. Теперь представьте, что этот показ мод состоит только из одного человека. Этот человек спроектировал одежду, модифицировал ее и показал на подиуме. Это не выглядит хорошо продуманным или эффективным проектом.

Теперь представьте себе этот же показ мод, использующий шаблон проектирования MVC. Вместо одного человека, делающего все, роли разделяются. Модели для показа (естественно, не путайте с моделью как акронимом MVC) представляют одежду. Они выступают как представление. Они знают, как правильно показать одежду (данные), но абсолютно не знают, как создавать или проектировать ее. C другой стороны, дизайнер одежды работает за кулисами, изменяя ее при необходимости. Дизайнер выступает в роли контроллера. Это человек не знает, как пройтись по подиуму, но может создавать и изменять одежду. Дизайнер и модели работают с одеждой независимо друг от друга и имеют свою область компетенции.

Эта концепция лежит в основе шаблона проектирования MVC: пусть каждый аспект UI имеет дело с тем, что он хорошо знает. Если вы все еще смущены, примеры в оставшейся части руководства, будем надеяться, смягчат это; но помните об основном принципе: визуальные компоненты отображают данные, а другие классы управляют ими.

JComponent

Базовым строительным блоком всей библиотеки визуальных компонентов Swing является JComponent. Это суперкласс каждого компонента. Он является абстрактным классом, поэтому в действительности вы не можете создать JComponent, но он содержит буквально сотни функций, которые каждый компонент Swing может использовать как результат иерархии классов. Очевидно, что некоторые концепции более важны, чем другие, но для целей этого руководства важными являются:

  • JComponent — базовый класс не только для Swing-компонентов, но также и для пользовательских компонентов. (более подробная информация приведена в руководстве «Swing для среднего уровня«).
  • Он обеспечивает инфраструктуру окрашивания для всех компонентов — нечто, становящееся удобным для пользовательских компонентов.
  • Он знает, как обрабатывать все нажатия клавиш на клавиатуре. Подклассы, следовательно, должны только прослушивать определенные клавиши.
  • Он содержит метод add(), который позволяет вам добавить другие JComponent. Этим способом вы можете добавить любой Swing-компонент к любому другому Swing-компоненту для создания вложенных компонентов (например, JPanel, содержащую JButton, или даже более причудливые комбинации, например JMenu, содержащее JButton).

Простые Swing-виджеты

JLabel

Самым основным компонентом в библиотеке Swing является JLabel. Он делает именно то, чего вы от него ожидаете: располагается в нужном месте, улучшает вид приложения и описывает другие компоненты. На приведенном ниже изображении показан JLabel в действии:

Не очень впечатляюще, но все равно полезно. Фактически, вы используете компоненты JLabel по всему приложению не только как текстовые описания, но и как графические описания. Когда бы вы ни увидели изображение в Swing-приложении, есть шанс, что это JLabel. У JLabel не много методов для новичков в Swing, кроме, возможно, ожидаемых вами. К основным методам относится установка текста, изображения, выравнивания и других компонентов, которые описывает метка:

  • get/setText(): Получить/установить текст в метке.
  • get/setIcon(): Получить/установить изображение в метке.
  • get/setHorizontalAlignment(): Получить/установить горизонтальную позицию текста.
  • get/setVerticalAlignment(): Получить/установить вертикальную позицию текста.
  • get/setDisplayedMnemonic(): Получить/установить мнемонику (подчеркнутый символ) для метки.
  • get/setLabelFor(): Получить/установить компонент, к которому присоединена данная метка; когда пользователь нажимает комбинацию клавиш Alt+мнемоника, фокус перемещается на указанный компонент.

JButton

Основным активным компонентом в Swing является JButton, кнопка, которую вы видите (с надписями OK и Cancel) в каждом окне; он делает именно то, что ожидается от кнопки — вы нажимаете на нее, и что-то происходит. Что именно происходит? Да, вы должны определить это (дополнительная информация приведена в разделе События). JButton в действии выглядит следующим образом:

Методы, используемые для изменения свойств JButton, аналогичны методам JLabel (вы обнаружите, что они аналогичны для большинства Swing-компонентов). Они управляют текстом, изображениями и ориентацией:

  • get/setText(): Получить/установить текст в кнопке.
  • get/setIcon(): Получить/установить изображение в кнопке.
  • get/setHorizontalAlignment(): Получить/установить горизонтальную позицию текста.
  • get/setVerticalAlignment(): Получить/установить вертикальную позицию текста.
  • get/setDisplayedMnenomic(): Получить/установить мнемонику (подчеркнутый символ), которая в комбинации с кнопкой Alt вызывает нажатие кнопки.

Кроме этих методов я познакомлю вас с еще одной группой методов, которые содержит JButton. Эти методы используют все различные состояния кнопки. Состояние — это свойство, описывающее компонент, обычно имеющее значение true/false. В случае с JButton он имеет следующие возможные состояния: активная/неактивная, выбранная/не выбранная, мышка сверху/мышки нет, нажата/отжата. Кроме того, вы можете комбинировать состояния, например: кнопка может быть выбрана и над ней находится мышка. Сейчас вы, возможно, спрашиваете себя, для чего мне все эти состояния. В качестве примера переместитесь к кнопке Back вашего браузера. Обратите внимание на то, как меняется изображение при наведении на нее указателя мышки, и как оно меняется при нажатии на кнопку. Эта кнопка использует различные состояния. Использование различных изображений для каждого состояния является популярным и очень эффективным способом отображать действия пользователей. Методами состояния для JButton являются:

  • get/setDisabledIcon()
  • get/setDisabledSelectedIcon()
  • get/setIcon()
  • get/setPressedIcon()
  • get/setRolloverIcon()
  • get/setRolloverSelectedIcon()
  • get/setSelectedIcon()

JTextField

Основным текстовым компонентом в Swing является JTextField; он позволяет пользователям вводить текст в UI. Я уверен, что вы знакомы с текстовым полем; вы должны были использовать его при вводе имени пользователя и пароля для получения данного руководства. Вы вводите, удаляете, выделяете текст, перемещаете курсор — Swing заботится обо всем, что вы делаете. Как UI-разработчику вам действительно не нужно делать много для того, чтобы использовать возможности JTextField.

В любом случае, вот как выглядит JTextField в действии:

Вы должны интересоваться только одним методом при работе с JTextField, и это должен быть, очевидно, метод, устанавливающий текст: get/setText(). Этот метод получает/устанавливает текст внутри JTextField.

JFrame

Пока я рассмотрел три основных строительных блока Swing: метку, кнопку и текстовое поле; теперь вы должны куда-то поместить их. Они не могут просто летать по экрану в надежде на то, что пользователь знает, как работать с ними. КлассJFrame делает именно это — он является контейнером, позволяющим добавлять к себе другие компоненты для их организации и предоставления пользователю. Он имеет много других бонусов, но я считаю, что легче всего сначала на него посмотреть:

JFrame

JFrame на самом деле делает больше, чем просто позволяет вам размещать компоненты на нем и предоставлять их пользователю. Несмотря на всю его кажущуюся простоту, он фактически является одним из самых сложных компонентов в Swing-пакетах. Говоря очень упрощенно, JFrame выступает в качестве моста между независимыми от OS Swing-частями и реальной OS, на которой они работают. JFrame регистрируется как окно в OS и таким образом получает многие из знакомых свойств окна операционной системы: минимизация/максимизация, изменение размеров и перемещение. Хотя для целей данного руководства совершенно достаточно считать JFrame палитрой, на которой вы размещаете компоненты. Некоторыми из методов, которые вы можете вызвать с JFrame для изменения его свойств, являются:

  • get/setTitle(): Получить/установить заголовок фрейма.
  • get/setState(): Получить/установить состояние фрейма (минимизировать, максимизировать и т.д.).
  • is/setVisible(): Получить/установить видимость фрейма, другими словами, отображение на экране.
  • get/setLocation(): Получить/установить месторасположение в окне, где фрейм должен появиться.
  • get/setSize(): Получить/установить размер фрейма.
  • add(): Добавить компоненты к фрейму.

Простое приложение

Как и во всех руководствах «Введение в х», у нас есть необходимый элемент — демонстрация HelloWorld. Однако этот пример полезен не только для того, чтобы увидеть, как работает Swing-приложение, но также и для проверки правильности установки системы. Если это простое приложение работает, любое другое тоже будет работать нормально. На приведенном ниже рисунке показан завершенный пример:

Пример HelloWorld

Первым вашим действием является создание класса. Swing-приложение, которое размещает компоненты в JFrame, должно быть подклассом класса JFrame, например так:

public class HelloWorld extends JFrame

Сделав так, вы получаете все свойства JFrame, описанные выше, и, что самое важное, поддержку окна в OS. Следующий шаг — размещение компонентов на экране. В данном примере мы используем схему null. Более подробно о схемах и менеджерах схем вы узнаете позже. Для этого примера числа указывают позицию в JFrame в пикселях:

   public HelloWorld()
   {
      super();
      this.setSize(300, 200);
      this.getContentPane().setLayout(null);
      this.add(getJLabel(), null);
      this.add(getJTextField(), null);
      this.add(getJButton(), null);
      this.setTitle("HelloWorld");
   }

   private javax.swing.JLabel getJLabel() {
      if(jLabel == null) {
         jLabel = new javax.swing.JLabel();
         jLabel.setBounds(34, 49, 53, 18);
         jLabel.setText("Name:");
      }
      return jLabel;
   }

   private javax.swing.JTextField getJTextField() {
      if(jTextField == null) {
         jTextField = new javax.swing.JTextField();
         jTextField.setBounds(96, 49, 160, 20);
      }
      return jTextField;
   }

   private javax.swing.JButton getJButton() {
      if(jButton == null) {
         jButton = new javax.swing.JButton();
         jButton.setBounds(103, 110, 71, 27);
         jButton.setText("OK");
      }
      return jButton;
   }

Теперь, когда компоненты размещены в JFrame, необходимо, чтобы JFrame показался на экране; также нужно сделать ваше приложение запускаемым. Как и во всех Java-приложениях, вы должны добавить метод main, для того чтобы сделать Swing-приложение запускаемым. Внутри этого метода main необходимо создать объект вашего приложения HelloWorld и вызвать setVisible():

   public static void main(String[] args)
   {
      HelloWorld w = new HelloWorld();
      w.setVisible(true);
   }

Это все, что нужно для создания приложения!

Дополнительные Swing-виджеты

JComboBox

В этом разделе мы рассмотрим все остальные компоненты в библиотеке Swing: как их использовать, и на что они похожи. Это даст лучшее представление о той мощи, которую дает вам (как UI-разработчику) Swing.

Мы начнем с JComboBox. Комбинированный список является знакомым ниспадающим списком элементов, в котором пользователи могут либо выбрать ноль или один (и только один) элемент из списка. В некоторых версиях комбинированного списка вы можете ввести ваш собственный вариант. Хорошим примером является адресная строка вашего браузера; это комбинированный список, позволяющий ввести собственный вариант. Вот как выглядит JComboBox в Swing:

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

  • addItem(): Добавить элемент к JComboBox.
  • get/setSelectedIndex(): Получить/установить индекс выбранного элемента в JComboBox.
  • get/setSelectedItem(): Получить/установить выбранный объект.
  • removeAllItems(): Удалить все объекты из JComboBox.
  • remoteItem(): Удалить конкретный объект из JComboBox.

JPasswordField

Разновидностью JTextField является JPasswordField, который позволяет скрывать все символы, отображаемые на экране в текстовом поле. В конце концов, что это за пароль, который могут видеть все желающие в то время, когда вы его вводите? Возможно, это вообще не пароль, и в настоящее время, когда ваши персональные данные очень важны, вы должны использовать все возможности по их защите, которые можете получить. Вот как JPasswordField выглядит в Swing:


Дополнительные «защищенные» методы JPasswordField незначительно меняют свое поведение по сравнению с JTextField, для того чтобы вы не смогли прочитать текст:

  • get/setEchoChar(): Получить/установить символ, который появляется в JPasswordField при вводе текста. При получении пароля символ «echo» не возвращается; вместо него возвращается реальный символ.
  • getText(): Вы не должны использовать эту функцию, поскольку она имеет некоторые проблемы с защитой (для интересующихся — String хранится в оперативной памяти, и возможный дамп кучи может открыть пароль).
  • getPassword(): Это корректный метод для получения пароля из JPasswordField, поскольку возвращает массивchar[], содержащий пароль. Для гарантирования нормальной защищенности массив должен быть очищен в 0, для того чтобы он не оставался в оперативной памяти.

JCheckBox/JRadioButton

Компоненты JCheckBox и JRadioButton предоставляют пользователю варианты для выбора, обычно в многовариантном формате. В чем отличия? С практической точки зрения они одинаковы. Их поведение одинаково. Однако в обычной UI-практике они имеют небольшое различие. JRadioButton обычно группируются вместе для предоставления пользователю вопроса с принудительным ответом (ответы взаимоисключающие — может быть только один ответ на вопрос). Поведение JRadioButton заставляет делать именно так. Как только вы выбрали JRadioButton, вы не можете снять его отметку до тех пор, пока не выберете другой вариант из группы. JCheckBox работает иначе. Он позволяет отмечать/снимать отметку с варианта в любое время и выбирать несколько ответов на вопрос.

Вот пример. На вопрос «Вы юноша или девушка?» есть два уникальных варианта ответа: «Юноша» или «Девушка». Пользователь должен выбрать один и не может выбрать оба. С другой стороны, на вопрос «Каково ваше хобби?» можно дать несколько ответов: «Бег», «Сон» или «Чтение».

Классом, который позволяет группировать вместе компоненты JCheckBox или JRadioButton, является классButtonGroup. Он позволяет группировать варианты (например, «Юноша» и «Девушка») таким образом, что при выборе одного, с другого отметка автоматически снимается.

Вот как JCheckBox и JRadioButton выглядят в Swing:


Важными методами ButtonGroup, которые стоит запомнить, являются:

  • add(): Добавить JCheckBox или JRadioButton к ButtonGroup.
  • getElements(): Получить все компоненты в ButtonGroup, для того чтобы можно было выполнить итерацию по ним для поиска выбранного.

JMenu/JMenuItem/JMenuBar

Компоненты JMenu, JMenuItem и JMenuBar являются главными строительными блоками для разработки системы меню в вашем JFrame. Основой любой системы меню является JMenuBar. Он простой и скучный, но он необходим, поскольку каждый JMenu и JMenuItem создается с ним. Для присоединения JMenuBar к JFrame используется метод setJMenuBar(). После его закрепления в JFrame вы можете добавлять все меню, подменю и элементы меню, какие хотите.

Различие JMenu/JMenuItem, возможно, очевидно, но фактически оно скрыто внутри и как раз не является очевидным. Если вы посмотрите на иерархию классов, JMenu является подклассом JMenuItem. Однако по внешнему виду они имеют отличие: JMenu используется для содержания других JMenuItem и JMenu; JMenuItem при выборе активизирует действие.

JMenuItem также поддерживает сокращенные клавиатурные команды. Как и большинство приложений, которые вы используете, Swing-приложения позволяют нажать Ctrl+(клавиша) для активизации действия, аналогично нажатию соответствующего элемента меню. Вспомните Ctrl+X и Ctrl+V, которые вы используете для копирования и вставки.

Кроме того, JMenu и JMenuItem поддерживают мнемоники. Вы можете использовать клавишу Alt вместе с буквой, назначенной меню в качестве мнемоники (например, нажатие Alt+F, а затем Alt+x закрывает приложение в Windows).

Вот как выглядят JMenuBar с компонентами JMenu и JMenuItem в Swing:


Важными методами, которые вам нужны в этих классах, являются:

  • JMenuItem и JMenu:
    • get/setAccelerator(): Получить/установить комбинацию Ctrl+клавиша, используемую в качестве клавиатурного сокращения.
    • get/setText(): Получить/установить текст для меню.
    • get/setIcon(): Получить/установить изображение, используемое в меню.
  • Только JMenu:
    • add(): Добавить еще один JMenu или JMenuItem к JMenu (создание вложенного меню).

JSlider

Компонент JSlider используется в приложении для изменения числового значения. Это быстрый и простой способ позволить пользователям визуально получить ответную реакцию не только на их текущий выбор, но увидеть диапазон допустимых значений. Подумайте, вы могли бы предоставить текстовое поле и позволить пользователю ввести значение, но тогда у вас добавляется лишняя работа — вы должны гарантировать, что введенное значение является числом, которое попадает в требуемый диапазон. В качестве примера представьте, что у вас есть финансовый Web-сайт, который запрашивает желаемый процент инвестиций в акции. Вы должны проверять вводимые в текстовое поле значения, для того чтобы гарантировать, что они являются числом и находятся в диапазоне от 0 до 100. Если вы вместо этого используете JSlider, введенные значения гарантировано будут числами в требуемом диапазоне.

В Swing JSlider выглядит следующим образом:

Важными методами в JSlider являются:

  • get/setMinimum(): Получить/установить минимальное значение, которое вы можете выбрать.
  • get/setMaximum(): Получить/установить максимальное значение, которое вы можете выбрать.
  • get/setOrientation(): Получить/установить ориентацию JSlider (вверх/вниз или вправо/влево).
  • get/setValue(): Получить/установить начальное значение JSlider.

JSpinner

Во многом аналогично JSlider вы можете использовать JSpinner, для того чтобы позволить пользователям выбирать целочисленное значение. Одним важным преимуществом является его компактный размер по сравнению с JSlider. Хотя недостатком является то, что вы не можете легко установить его границы.

Однако сравнение этих двух компонентов здесь заканчивается. JSpinner намного более гибок и может быть использован для выбора из любой группы значений. Кроме выбора чисел он может быть использован для выбора дат, имен, цветов, чего-угодно. Это делает JSpinner чрезвычайно мощным, позволяя вам предоставлять компонент, содержащий только предопределенные варианты. В этом он аналогичен JComboBox, хотя их использование не должно быть взаимозаменяемо. Вы должны использовать JSpinner только для логически последовательных вариантов — числа и даты идеально подходят для этого. JComboBox, с другой стороны, более хороший выбор для представления кажущихся случайными вариантов, которые не имеют взаимосвязи между одним вариантом и следующим.

JSpinner выглядит так:

Важными методами являются:

  • get/setValue(): Получить/установить начальное значение JSpinner, которое в базовом случае должно быть целым числом.
  • getNextValue(): Получить следующее значение, которое будет выбрано после нажатия клавиши управления курсором «стрелка вверх».
  • getPreviousValue(): Получить предыдущее значение, которое будет выбрано после нажатия клавиши управления курсором «стрелка вниз».

JToolBar

Компонент JToolBar выступает как палитра для других компонентов (JButtons, JComboBoxes и т.д.), которые совместно образуют панели инструментов, встречающиеся в большинстве приложений. Панели инструментов позволяют программе размещать часто используемые команды в удобном месте и группировать их. Часто, но не всегда, кнопки панели инструментов соответствуют командам в меню. Хотя это и не обязательно, но применение панелей стало общепринятой практикой, и вы тоже должны обращать на них внимание.

JToolBar также предлагает еще одну функцию, которую вы встречали в других панелях инструментов: способность «перемещаться» (то есть, становится отдельным фреймом над главным фреймом).

Ниже показан не перемещающийся JToolBar:

Важным методом JToolBar является is/setFloatable(), который получает/устанавливает свойство: может ли JToolBar перемещаться.

JToolTip

Вы, возможно, встречали JToolTip повсюду, но никогда не знали, как они называются. Они похожи на пластиковые концы ваших шнурков — они повсюду, но вы не знаете правильного названия (они называются aglet (пистон), если кому интересно). JToolTip — это небольшие «пузырьки», которые всплывают тогда, когда вы наводите и держите курсор мышки над чем-нибудь. Они могут быть очень полезны в приложениях, предоставляя подсказки для элементов, детализируя информацию или даже показывая полный текст элемента в сжатых UI. Они активизируются в Swing, если оставить курсор мышки над компонентом на определенное количество времени; обычно они появляются примерно через секунду после остановки мышки и остаются видимыми до тех пор, пока курсор остается над компонентом.

Компоненты JToolTip легко использовать. Метод setToolTip() является методом класса JComponent, то есть, каждый Swing-компонент может иметь всплывающую подсказку, связанную с ним. Хотя JToolTip и сам является Swing-классом, он, фактически, не предоставляет в настоящее время дополнительной функциональности и не должен создаваться. Вы можете обратиться к нему и использовать, вызывая функцию setToolTip() в JComponent.

Вот как выглядит JToolTip:

JOptionPane

Компонент JOptionPane — это своего рода «ускоряющий» класс в Swing. Часто, как UI-разработчик, вы хотели бы предоставить быстрое сообщение для ваших пользователей, говоря им об ошибке или выдавая какую-нибудь другую информацию. Возможно, вы хотели бы быстро получить некоторые данные, например, имя или номер. В Swing классJOptionPane предоставляет возможность выполнить эти рутинные задачи. Вместо того, чтобы каждый раз изобретать колесо, Swing предоставляет базовый, но полезный класс для предоставления UI-разработчикам способа выдачи и получения простых сообщений.

Вот внешний вид JOptionPane:

Определенного рода хитростью при работе с JOptionPane являются все его возможные варианты, которые вы можете использовать. Будучи простым, он все же предоставляет очень много вариантов, которые могут вызвать замешательство. Одним из лучших способов изучить JOptionPane — это поработать с ним, закодировать и посмотреть, что получилось. Компонент позволяет менять практически каждый свой аспект: заголовок фрейма, само сообщение, отображаемую пиктограмму, варианты кнопок и необходимость текстового ответа. Существует очень много возможностей, для того чтобы их все перечислять в данном руководстве. Перейдите лучше на страницу API JOptionPane документации для их просмотра.

JTextArea

Компонент JTextArea развивает JTextField. В то время как JTextField ограничен одной строкой текста, JTextArea позволяет иметь несколько строк. Думайте о нем, как о пустой странице, позволяющей ввести все что угодно. Как вы уже наверно догадались, JTextArea имеет многие из функций, присутствующих в JTextField; в конце концов, это практически одинаковые компоненты. Однако JTextArea предлагает несколько дополнительных функций. К ним относятся способность переносить слова (то есть, переносить длинное слово в следующую строку вместо обрезания по середине слова) и способность переносить текст (то есть, помещать длинные строки текста на следующую строку вместо создания очень длинной строки, требующей наличия горизонтальной полосы прокрутки).

JTextArea в Swing выглядит так:

Важными методами для разрешения переноса строки и переноса слова являются:

  • is/setLineWrap(): Устанавливает, должна ли переноситься строка, если она становится слишком длинной.
  • is/setWrapStyleWord(): Устанавливает, должно ли переноситься слово на следующую строку, если оно слишком длинное.

JScrollPane

Рассматривая приведенный выше пример, предположим, что JTextArea содержит слишком много текста для данного пространства. Что тогда? Если вы думаете, что полосы прокрутки появятся автоматически, то, к сожалению, ошибаетесь. JScrollPane устраняет этот пробел, предоставляя Swing-компонент для обработки всех действий по прокрутке. То есть (хотя потребуются некоторые усилия, чтобы обеспечить панель прокрутки для каждого компонента, который в ней нуждается), если вы добавите ее, она будет управлять всем автоматически, включая скрытие/показ полос прокрутки при необходимости.

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

   JScrollPane scroll = new JScrollPane(getTextArea());
   add(scroll);

Этот обновленный пример выглядит следующим образом:


Компонент JScrollPane предоставляет также два компонента JScrollBar, которые он создаст. Эти компоненты JScrollBar содержат методы, которые вы можете использовать для изменения их поведения (их рассмотрение выходят за рамки данного руководства).

Методами, которые вы должны использовать с JScrollPane, являются:

  • getHorizontalScrollBar(): Возвращает горизонтальный компонент JScrollBar.
  • getVerticalScrollBar(): Возвращает вертикальный компонент JScrollBar.
  • get/setHorizontalScrollBarPolicy(): Эта «политика» может принимать одно из следующих значений: Always (всегда), Never (никогда), или As Needed (по необходимости).
  • get/setVerticalScrollBarPolicy(): Аналогично горизонтальной функции.

JList

Компонент JList является полезным компонентом для предоставления пользователю многих вариантов для выбора. Вы можете представлять его как расширение JComboBox. JList предоставляет больше вариантов и добавляет возможность выбора нескольких вариантов. Выбор между JList и JComboBox часто заключается в следующем: если вам требуется возможность множественного выбора или имеется более 15 вариантов (хотя это число не является общим правилом), вы должны всегда выбирать JList.

Вы должны использовать JList совместно с JScrollPane, как было показано выше, поскольку он может предоставлять больше вариантов, чем помещается в видимой области.

JList имеет также модель выбора (также имеющуюся в JTable), которую вы можете установить в различные типы выбора вариантов. Такими типами являются: одиночный выбор (вы можете выбрать только один вариант), одиночный интервал (вы можете выбрать смежные варианты, но в любом их количестве) и множественный интервал (вы можете выбрать любое число вариантов в любых комбинациях).

JList является первым из компонентов, которые я называю «сложными компонентами», и к которым относятся также компоненты JTable и JTree, допускающие большое количество пользовательских настроек, в том числе внешнего вида и способа обработки данных. Поскольку данное руководство предназначено только для рассмотрения основ Swing, я не буду углубляться в эти расширенные функции, но при работе с этими компонентами вы должны помнить — они более сложны, чем рассмотренные до сих пор компоненты.

JList в Swing выглядит так:


Существует много функций в JList для работы с данными, и, как я уже сказал, это только немногие из тех, которые потребуются для вашей работы с JList. Вот основные методы:

  • get/setSelectedIndex(): Получить/установить выбранную строку списка; в случае со списками с множественным выбором возвращается int[].
  • get/setSelectionMode(): Как объяснялось ранее, получить/установить режим выбора в одиночный выбор, одиночный интервал или множественный интервал.
  • setListData(): Установить данные для использования в JList.
  • get/setSelectedValue(): Получить выбранный объект (как противоположность выбранному номеру строки).

JTable

Представляйте себе электронную таблицу Excel при рассмотрении компонента JTable, и вы получите ясную картину того, что делает в Swing компонент JTable. Он имеет много таких же характеристик: ячейки, строки, столбцы, перемещение столбцов и скрытие столбцов. JTable развивает идею JList. Вместо отображения данных в одном столбце он отображает их в нескольких. Давайте возьмем для примера человека. JList смог бы отобразить только одно его свойство, например, имя. Однако JTable смог бы отобразить несколько свойств — имя, возраст, адрес и т.д. JTable является Swing-компонентом, позволяющим предоставлять больше информации о ваших данных.

К сожалению, имеется и недостаток. Он известен также как наиболее сложный Swing-компонент для работы. Многие UI-разработчики мучались при изучении каждой подробности JTable. Я избавлю вас от этого.

Многие концепции JList присутствуют также и в JTable, в том числе, например, множественный выбор интервалов. Но однострочные концепции JList меняются на ячеистую структуру JTable. Это означает, что есть различные способы делать этот выбор в JTable: в виде столбцов, строк или индивидуальных ячеек.

В Swing JTable выглядит так:


Конечно, рассмотрение функциональности JTable выходит за рамки данного руководства.

JTree

JTree — это еще один сложный компонент, который не так трудно использовать, как JTable, но не так просто, как JList. Хитрость при работе с JTree состоит в требуемых моделях данных.

JTree наследует свою функциональность из концепции дерева с ветвями и листьями. Вы, возможно, знакомы с этой концепцией по работе с Internet Explorer в Windows — вы можете расширить и свернуть ветвь для отображения различных листьев, которые вы можете отметить, или с которых можете снять отметку.

Скорее всего, вы обнаружите, что древо не настолько полезно в приложении, как таблица или список, поэтому в Интернете нет большого количества полезных примеров. Фактически, аналогично JTable, компонент JTree не имеет каких-либо функций для новичков. Если вы решите использовать JTree, то тут же перейдете на средний уровень и должны будете изучить концепции, лежащие в основе его работы. В примере приложения не используется JTree, поэтому, к сожалению, ни руководство для новичков, ни руководство для среднего уровня квалификации не рассматривает детально этот менее популярный компонент.

Однако бывают ситуации, когда дерево логично использовать в качестве UI-компонента. Система файлов и каталогов — это один из примеров, как в Internet Explorer, и JTree является наилучшим компонентом для данных с иерархической структурой, иными словами, когда данные имеют форму дерева.

В Swing компонент JTree выглядит так:

Концепции Swing

Схемы, модели и события

Теперь, когда вы знакомы с большинством (но определенно не со всеми) компонентов, которые можно использовать для создания UI, необходимо что-то с ними делать реальное. Вы не можете просто случайно разместить их на экране и ожидать от них немедленной работы. Вы должны разместить их в определенные места, реагировать на взаимодействие с ними, обновлять их на основе этого взаимодействия и заполнять данными. Необходимо дальнейшее изучение других важных частей UI для заполнения пробелов в ваших знаниях.

Поэтому, давайте рассмотрим:

  • Схемы (layout): Swing содержит множество схем, которые представляют собой классы, управляющие размещением компонентов в приложении и тем, что должно произойти с ними при изменении размеров окна приложения или при удалении или добавлении компонентов.
  • События (event): Вы должны реагировать на нажатия клавиши, нажатия кнопки мыши и на все остальное, что пользователь может сделать в UI. Подумайте о том, что было бы, если бы вы этого не делали — пользователи нажимают на кнопки, а ничего не происходит.
  • Модели (model): Для более продвинутых компонентов (списки, таблицы, деревья) и даже для некоторых более простых, например, JComboBox, модели — это самый эффективный способ работы с данными. Они удаляют большую часть работы по обработке данных из самого компонента (вспомните обсуждение MVC) и предоставляют оболочку для общих объектных классов данных (например, Vector и ArrayList).

Простые схемы

Как уже говорилось в предыдущем разделе, схема управляет расположением компонентов в приложении. Вашим первым вопросом мог бы быть вопрос «почему я просто не могу указать месторасположение, используя пиксели»? Да, вы можете, но тогда сразу же возникнет проблема при изменении размеров окна, или еще хуже: если пользователь изменит разрешение экрана, или кто-то попробует запустить приложение в другой OS. Менеджеры схем берут все это на себя. Никто не использует одни и те же настройки, поэтому менеджеры схем создают «относительные» схемы, позволяя вам указывать, как должно происходить изменение размеров относительно других размещенных компонентов. Хорошая новость: это легче сделать, чем сказать. Вы просто вызываете setLayout(yourLayout) для настройки менеджера схемы. Последующие вызовы add() добавляют компонент в контейнер и позволяют менеджеру схемы позаботиться об их размещении.

В Swing включено много схем; такое впечатление, что с каждой версией появляется новая, которая служит еще одной цели. Однако некоторые проверенные временем схемы существовали всегда; под словом всегда я понимаю время с появления первой версии языка Java в 1995 году. Такими схемами являются FlowLayout, GridLayout и BorderLayout.

В схеме FlowLayout компоненты располагаются слева направо. Когда пространство заканчивается, происходит переход на новую строку. Это самая простая в использовании схема и наименее мощная:

   setLayout(new FlowLayout());
   add(new JButton("Button1"));
   add(new JButton("Button2"));
   add(new JButton("Button3"));

Схема FlowLayout в работе

Схема GridLayout делает точно то, что вы подумали: она позволяет указать количество строк и столбцов, а затем располагает компоненты в этих ячейках по мере их добавления:

   setLayout(new GridLayout(1,2));
   add(new JButton("Button1"));
   add(new JButton("Button2"));
   add(new JButton("Button3"));

Схема GridLayout в работе

Схема BorderLayout все еще является очень полезной, несмотря на добавление в Swing новых схем. Даже опытные UI-разработчики часто используют BorderLayout. В ней используются понятия North (север), South (юг), East (восток), West (запад) и Center (центр) для размещения компонентов на экране:

   setLayout(new BorderLayout());
   add(new JButton("Button1"), "North");
   add(new JButton("Button2"), "Center");
   add(new JButton("Button3"), "West");

Схема BorderLayout в работе

GridBagLayout

В то время как приведенные выше примеры хороши для простых схем, более продвинутые UI требуют использования более сложного менеджера схемы. Вот здесь нужен GridBagLayout. К сожалению, этот менеджер схем очень запутанный и тяжелый, и каждый, кто работал с ним, согласится с этим. Я тоже не могу не согласиться, но, несмотря на его сложность, он, вероятно, является наилучшим (из всех менеджеров схем, встроенных в Swing) способом создания прекрасно выглядящего UI.

Вот мой первый совет: В новейших версиях Eclipse есть встроенный визуальный построитель, автоматически генерирующий требуемый для GridBagLayout код для каждого экрана. Используйте его! Он сбережет бесчисленные часы бесполезных игр с числами для нормального отображения. Поэтому, имея возможность пуститься в объяснения работы GridBagLayout и настройки его для наилучшего выполнения работы, я, тем не менее, просто посоветую: найдите визуальный редактор и сгенерируйте код. Это сэкономит вам часы.

События

Наконец, мы перешли к наиболее важным элементам Swing — работе с событиями и реакции на взаимодействия с UI. Swing обрабатывает события, используя модель событие/прослушиватель. Эта модель работает путем разрешения некоторым классам регистрироваться на события от компонента. Такой класс называется прослушивателем (listener), поскольку он ждет возникновения событий в компоненте и выполняет действия при их возникновении. Компонент сам знает, как «активировать» событие (то есть, он знает типы взаимодействий, которые может генерировать, и как дать прослушивателям знать, когда происходят эти взаимодействия). Он передает это взаимодействие при помощи событий (event), классов, содержащих информацию о взаимодействии.

Убрав техническую болтовню, рассмотрим некоторые примеры работы событий в Swing. Я начну с простейшего примера — кнопки JButton и вывода сообщений «Hello» на консоль при ее нажатии.

JButton знает, когда на нее нажимают; это обрабатывается внутри компонента, код для обработки этого не нужен. Однако прослушиватель должен зарегистрироваться для получения этого события от JButton, для того чтобы вы могли вывести сообщение «Hello». Класс listener делает это, реализуя интерфейс listener и вызывая методaddActionListener() компонента JButton:

   // Создать JButton
   JButton b = new JButton("Button");
   // Зарегистрировать прослушиватель
   b.addActionListener(new HelloListener());

   class HelloListener implements ActionListener
   {
      // Метод интерфейса для получения нажатий кнопки
      public void actionPerformed(ActionEvent e)
      {
         System.out.println("Hello");
      }
   }

Компонент JList работает аналогично. Когда кто-то выбирает что-то в JList, вы выводите на консоли сообщение о выбранном объекте:

   //  myList - это JList, заполненный данными
   myList.addListSelectionListener(new ListSelectionListener()
   {
      public void valueChanged(ListSelectionEvent e)
      {
         Object o = myList.getSelectedItem();
         System.out.println(o.toString());
      }
   }
   );

Из тих двух примеров вы должны понять, как работает в Swing модель событие/прослушиватель. Фактически, каждое взаимодействие в Swing обрабатывается таким способом, поэтому, поняв эту модель, вы тотчас же поймете, как обрабатывается любое событие в Swing и как реагировать на любое возможное взаимодействие пользователя с системой.

Модели

Вы должны знать о Java Collections, наборе Java-классов, обрабатывающих данные. К этим классам относятсяArrayListHashMap и Set. Большинство приложений использует эти классы повсюду при перемещении данных взад и вперед. Однако при необходимости использования этих данных в UI возникает одно ограничение. UI не знает, как их отображать. Подумайте об этом минутку. Если у вас есть JList и ArrayList некоторых объектов данных (например, объектов Person), как JList узнает, что отображать? Должен ли он отображать только фамилию, или фамилию и имя вместе?

Вот где нужна концепция модели. Хотя понятие модели относится к более широкой области, в примерах данного руководства я использую понятие UI-модель для описания классов, которые компоненты используют для отображения данных.

Каждый компонент, работающий с коллекцией данных в Swing, использует концепцию модели, и это предпочтительный способ использования и управления данными. Он четко отделяет работу UI от используемых данных (вспомните пример MVC). Модель описывает компоненту, как отображать коллекцию данных. Что я имею в виду под словом «описывает»? Каждый компонент требует немного разного описания:

  • JComboBox требует от своей модели описания текста для отображения в виде вариантов выбора и количества существующих вариантов.
  • JSpinner требует от своей модели описания текста для отображения, а также описания предыдущего и следующего вариантов.
  • JList тоже требует от своей модели описания текста для отображения как вариантов выбора и количества существующих вариантов.
  • JTable нужно намного больше — он требует от своей модели описания количества существующих строк и столбцов, названий столбцов, класса каждого столбца и текста для отображения в каждой ячейке.
  • JTree требует от своей модели описания корневого узла, предков и дочерних элементов для всего дерева.

Вы можете спросить, зачем все это делать? Зачем понадобилось разделять всю эту функциональность? Представьте себе сценарий: у вас есть законченная таблица со многими столбцами данных, и вы используете эту таблицу на различных экранах. Если вы неожиданно решите удалить один из столбцов, что было бы легче, изменить код в каждом единичном экземпляре JTable, использованном вами, или изменить его в одном классе модели, который вы создали для использования с каждым экземпляром JTable? Очевидно, что лучше изменить классы.

Примеры модели

Давайте рассмотрим, как работает модель, на простом примере JComboBox. В предыдущем разделе о JComboBox я показал вам, как добавлять элементы к данным, вызывая setItem(). Хотя это и приемлемо для простых демонстраций, в реальных приложениях так делают не часто. В конце концов, когда имеется 25 вариантов, и они постоянно меняются, неужели вы захотите проходить по ним в цикле, вызывая addItem() 25 раз? Конечно же, нет.

JComboBox имеет метод setModel(), который принимает экземпляр класса ComboBoxModel. Вы должны использовать этот метод вместо метода addItem() для создания данных в JComboBox.

Предположим, что имеется ArrayList с алфавитом в качестве данных («A», «B», «C» и т.д.):

   MyComboModel model = new MyComboModel(alphaList);
   myComboBox.setModel(model);

   public class MyComboModel implements ComboBoxModel
   {
      private List data = new ArrayList();
      private int selected = 0;

      public MyComboModel(List list)
      {
         data = list;
      }

      public void setSelectedItem(Object o)
      {
         selected = data.indexOf(o);
      }

      public Object getSelectedItem()
      {
         return data.get(selected);
      }

      public int getSize()
      {
         return data.size();
      }

      public Object getElementAt(int i)
      {
         return data.get(i);
      }
   }

Хорошей стороной использования модели является то, что вы можете повторно использовать ее снова и снова. Например, предположим, что данные в JComboBox должны быть изменены с букв алфавита на числа от 1 до 27. Вы можете выполнить это изменение одной простой строкой, которая использует новый List данных для заполнения JComboBox без использования дополнительного кода:

   myComboBox.setModel(new MyComboModel(numberList));

Модели являются полезными возможностями Swing, поскольку они предоставляют возможность повторного использования кода и намного более легко работают с данными. Как это часто случается в крупномасштабных приложениях, разработчики серверной части создают и извлекают данные и передают их UI-разработчикам. Обязанностью UI-разработчика является работать с данными и отображать их правильно, а модели — инструменты, которые делают это.

Объединение всего этого вместе

Пример приложения

После примеров вы, возможно, захотите увидеть все эти вещи в действии. Хватит красивых картинок. Перейдем к конкретному примеру, который объединяет все, что вы изучили в данном руководстве.

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

Это простое по внешнему виду приложение, которое позволит вам увидеть в действии все элементы Swing. Данный пример должен ответить на любые вопросы, которые, возможно, остались у вас от предыдущих разделов. Перед началом работы посмотрим на законченный продукт:

Пример приложения

Шаг 1: Размещение компонентов

Как я уже говорил, нет большой необходимости изучать сложные схемы, поскольку вы можете использовать визуальный редактор.

Шаг 2: Инициализация данных

Приложение не может работать без данных. Давайте подумаем о том, какого рода данные нам нужны для данного приложения. Во-первых, нам необходим список городов для выбора города отправления и прибытия. Затем, нам необходим список рейсов для поиска.

Для этого примера я использую фиктивные данные, потому что главное в приложении — это Swing, а не данные. Все данные создаются в классе DataHandler. Этот класс управляет городами отправления и прибытия, а также обрабатывает операции поиска рейсов и извлечения записей.

Города сохраняются в простых объектах String. Однако рейсы хранятся в объектах данных, называемых Flights, содержащих поля для города отправления, города прибытия, номера рейса и количества доступных билетов.

Теперь, со всеми этими красными ленточками, указывающими путь, давайте вернемся к приложению.

Шаг 3: Управление событиями

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

Начнем с кнопки Search. Как говорилось выше, вы должны вызвать метод addActionListener() кнопки для регистрации на события, возникающие при ее нажатии. Для простоты я использовал класс FlightReservation для прослушивания всех возможных событий. Вот код обработки нажатия кнопки Search:

   String dest = getComboDest().getSelectedItem().toString();
   String depart = getComboDepart().getSelectedItem().toString();
   List l = DataHandler.searchRecords(depart, dest);
   flightModel.updateData(l);

Два города выбираются из комбинированных списков и используются для поиска записей соответствующих рейсов. Как только рейсы найдены, записи передаются в табличную модель таблицы; более подробно о работе табличных моделей рассказано ниже. Но знайте, что как только табличная модель обновила данные, она отобразит результаты.

Затем исследуем, что происходит при нажатии пользователем кнопки Purchase:

   Object o = flightModel.getData().get(getTblFlights().getSelectedRow());
   int tixx = Integer.parseInt(getTxtNumTixx().getText());
   DataHandler.updateRecords(o, tixx);

Теперь наоборот, когда пользователь нажимает кнопку Purchase, табличная модель вычисляет, какой рейс выбрал пользователь, и передает эту запись и количество билетов для покупки в обработчик данных.

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

   boolean selected = getTblFlights().getSelectedRow() > -1;
   getLblNumTixx().setEnabled(selected);
   getTxtNumTixx().setEnabled(selected);
   getBtnPurchase().setEnabled(selected);

Шаг 4: Модели

Далее рассмотрим модели, используемые вами для обработки всех данных, передаваемых в обе стороны в этом приложении. Анализируя приложения и изучив этот демонстрационный пример, вы должны четко увидеть, что вам нужны две модели: модель для JComboBoxes и модель для JTable.

Начнем с простейшей модели JComboBox. Я не буду приводить здесь исходный код, поскольку он аналогичен рассмотренному несколькими разделами ранее (и, фактически, может быть использован для любого из ваших JComboBox). Хотя есть некоторые важные моменты, помните о преимуществах использования моделей. И хотя у вас имеется только один класс модели, вы повторно используете его, создавая два экземпляра класса и предоставляя их каждому из JComboBox. Таким образом, оба экземпляра могут обрабатывать свои собственные данные, хотя вы пишете только один класс для этого. Вот как это делается:

   comboModel1 = new CityComboModel(DataHandler.getCities());
   comboModel2 = new CityComboModel(DataHandler.getCities());

Перейдем к модели для JTable. Эта модель сложнее, чем для JComboBox, и требует более пристального рассмотрения. Начнем с ваших знаний ComboBoxModel и посмотрим, что нужно добавить для JTable. Поскольку JTable содержит те же данные, что и ComboBox, но в нескольких столбцах, вам нужна более полная информация из модели. То есть, кроме знания количества строк данных вы должны знать количество столбцов, названия столбцов и значение конкретной ячейки, а не только самого объекта. Это позволяет вам не только отобразить объект данных, но также отобразить поля объекта данных. В данном примере мы не отображаем объект Flight; вместо этого мы отображаем поля: город отправления, город прибытия, номер рейса и количество доступных билетов. Ниже приведен код, используемый для создания TableModel и для установки этой модели для JTable:

   flightModel = new FlightTableModel();
   getTblFlights().setModel(flightModel);

Из за большого объема кода, необходимого для создания TableModel, я не буду приводить его здесь, а перенаправлю вас к исходному коду примера приложения (ссылка для загрузки приведена в разделе «Ресурсы«) для более пристального исследования его работы. Кроме того, это только поверхностно затрагивает TableModel. Как я уже говорил ранее, JTable является самым сложным и трудным компонентом для работы со Swing, и его элементы, в том числе TableModel, не намного более просты.

Шаг 5: Звонки и свистки

В любом приложении пользователи ожидают определенное количество звонков и свистков как дополнительную функциональность, а также как способ предотвратить возникновение ошибок. В данном примере, хотя основная функциональность по поиску рейса и покупке билетов работает, вы не рассматривали возможные ошибки, которые могут возникать. Для защиты от ошибок вы должны добавить сообщение об ошибке при попытках пользователя заказать больше билетов, чем доступно. Как отобразить ошибки? Если вы вспомните раздел о компоненте JOptionPane, Swing имеет готовый компонент для такого рода немедленной реакции.

Рассмотрим условие ошибки, а также то, что активизирует сообщение об ошибке:

   try
   {
      DataHandler.updateRecords(o, tixx);
   }
   catch (Exception ex) {
      // отобразить здесь сообщение об ошибке
   }

Теперь позаботимся о сообщении об ошибке. Вспомните JOptionPane и его обильное количество настроек. Выделим настройки, которые хотим иметь в сообщении об ошибке, перед тем, как решим, какой тип JOptionPane создавать. Это должно быть сообщение об ошибке, а не информационное сообщение. Используйте простой заголовок, например «Error». Детальное сообщение состоит из того, что выдала исключительная ситуация. И, наконец, ошибка возникла по вине пользователей, поэтому должно быть достаточно наличия кнопок OK и Cancel.

Вот код для создания такой JOptionPane:

   JOptionPane.showConfirmDialog(this, ex.getMessage(), "Error",
              JOptionPane.OK_CANCEL_OPTION, JOptionPane.ERROR_MESSAGE);

И вот, как это все выглядит:

Пример сообщения об ошибке

И напоследок…

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

Особенно обратите внимание на то, как я использовал модели для облегчения процесса работы с данными. Используя довольно простой код, вы можете обрабатывать любой тип данных, которые могут получать как JTable, так и JComboBox. Кроме того, обратите внимание на взаимосвязи событие/прослушиватель — как взаимодействующие компоненты принимают участие во взаимоотношении событие/прослушиватель, т.е. на то, как заинтересованные стороны (особенно другие компоненты) должны регистрироваться на эти события.

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

Резюме

Резюме

Данное руководство познакомило вас с библиотекой Swing и с концепциями, которые вы должны знать для создания базового, функционального пользовательского интерфейса. Во время изучения данного руководства:

  • Вы познакомились со Swing-компонентами. Вы должны уметь распознавать компоненты, когда видите их на экране, и применять их в ваших собственных приложениях, используя основные функции этих компонентов. Кроме того, вы должны иметь общее представление о том, когда нужно использовать компоненты (например, когда использовать JCheckBox, а когда JRadioButton).
  • Вы узнали, как разместить эти компоненты на экране при помощи менеджеров схем. Менеджеры схем существуют со времен появления Java, и, к сожалению, наиболее мощный менеджер схем наиболее тяжело использовать. Однако визуальные редакторы (например, редактор в Eclipse) делают процесс размещения чрезвычайно легким, создавая весь код схемы автоматически.
  • Вы узнали о модели событий, о том, как Swing использует модель событие/прослушиватель во всех своих компонентах для разрешения одному компоненту принимать пользовательское взаимодействие с приложением и передавать это взаимодействие в другие классы. Эти другие классы регистрируются на события от компонента и выполняют соответствующее действие при получении события. Модель событие/прослушиватель используется во всей библиотеке Swing, и вы должны глубоко изучить ее для лучшей работы с каждым Swing-компонентом.
  • Вы узнали о моделях данных и о том, как они соответствуют архитектуре MVC в Swing. Модели позволяют компонентам отображать данные без каких-либо знаний о самих данных. Они также позволяют вам повторно использовать модели, разрешая нескольким компонентам, отображающим похожие данные, использовать одну и ту же модель, устраняя необходимость создания оригинальной модели для каждого экземпляра компонента. В крупномасштабных приложениях модели служат «транслятором» между серверными объектами данных и клиентскими Swing-компонентами.

Очень важно понимать, что данное руководство не является всеобъемлющим. Оно даже не предназначено для обзора всех основ Swing. Существует еще очень много вещей, которые нужно было бы внести в это руководство, для того чтобы предоставить новичкам исчерпывающее введение в Swing. Надеюсь, что я выделил ключевые элементы, в том числе наиболее широко используемые компоненты и их наиболее широко используемые функции. Вы должны знать, что, возможно, в Swing существует в два раза больше компонентов (я призываю вас прочитать документацию по Swing для изучения тех компонентов, которые я пропустил). Более того, я только слегка затронул функции большинства компонентов; каждый Swing-компонент имеет десятки, даже до сотни, функций, которые вы потенциально можете использовать.

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

Дальнейшие действия

Вот некоторые темы, которые могут быть вами изучены для дальнейшего развития в области Swing:

  • Более продвинутые функциональные возможности JTable, включая свойства таблицы, более тонкое управление TableModel, TableRenderer для изменения внешнего вида JTable и сортировку столбцов таблицы.
  • Потоки и их соответствие Swing. Поскольку пользователи не принимают интерфейс, который блокируется при выборке из базы данных, Swing должен использовать отдельные потоки для длительных операций.
  • Создание пользовательских компонентов. Если вы чувствуете ограниченность того, что может предложить вам Swing, я расскажу, как создавать компоненты, которые могут выглядеть и вести себя не так, как встроенные Swing-компоненты.
  • Пользовательское поведение и внешний вид. Я расскажу о том, как полностью изменить внешний вид приложения при помощи двух методов: первый создает новое пользовательское поведение и внешний вид UI, а второй использует библиотеку Synth, поддерживающую интерфейс с легко измененяемым внешним видом (skinnable).

Ресурсы

Научиться

  • Оригинал руководства «Introduction to Swing«.
  • Загрузите swing1.jar, который содержит исходный код приложений «Hello World» и системы резервирования билетов.
  • Не пропустите продолжение данного руководства «Swing для среднего уровня«, основанное на материале данного руководства.
  • Руководство Sun по Swing — хорошее дополнение к данному руководству, охватывающее не рассмотренные здесь компоненты.
  • В Swing Javadoc приводится описание всех возможных функций Swing-компонентов.
  • Web-страница JavaDesktop предлагает новые приемы работы в Swing.
  • Дискуссионный форум по программированию клиентских Java-приложений является еще одним хорошим местом для получения справочной информации по Swing.
  • На сайте developerWorks опубликовано множество статей по Swing:
    • В серии статей Джона Жуковски (John Zukowski) «Магия с Merlin» и «Укрощение тигра» постоянно упоминается Swing и смежные темы.
    • Майкл Абернети (Michael Abernethy) написал более продвинутые статьи по Swing, в том числе «Легкая разработка в Swing при помощи свободно распространяемой среды TableModel» (developerWorks, октябрь 2004), «Станьте мастером с IFrame» (developerWorks, март 2004) и «Продвинутый Synth» (developerWorks, февраль 2005).
  • Зона developerWorks открытого исходного кода имеет целый раздел, посвященный Eclipse-разработке.
  • В зоне developerWorks по Java-технологии вы найдете статьи по любому аспекту Java-программирования, в том числе и по рассмотренной в данном руководстве теме.

Получить продукты и технологии

  • eclipse.org — это официальный ресурс платформы разработки Eclipse. Здесь вы найдете файлы для загрузки, статьи и дискуссионные форумы, которые помогут вам профессионально использовать Eclipse.

Об авторе

Photo of Michael Abernethy

Майкл Абернети (Michael Abernethy) в настоящее время работает руководителем группы тестировщиков в IBM WebSphere System Management, расположенной в Austin, TX. До этого он работал разработчиком Swing UI.

Источник: Введение в Swing

Глава 15

Введение в Swing

Основные навыки и понятия

  • Происхождение и принципы конструирования Swing
  • Представление о компонентах и контейнерах Swing
  • Основные положения о диспетчерах компоновки
  • Написание, компиляция и запуск на выполнение простого Swing-приложения
  • Применение компонента JButton
  • Работа с компонентом JTextField
  • Создание флажков с помощью компонента JCheckBox
  • Работа с компонентом JList
  • Применение анонимных внутренних классов для обработки событий
  • Создание апплета средствами Swing

Все программы, приведенные в предыдущих главах в качестве примера, за исключением апплетов, рассмотренных в главе 14, были консольными. Это означает, что в них не применялись средства графического пользовательского интерфейса. Консольные программы очень удобны для изучения основ Java, но в реальных программах без графического пользовательского интерфейса просто не обойтись. Поэтому в последней главе этой книги будет рассмотрена библиотека Swing современных инструментальных средств Java, предназначенных для создания пользовательских интерфейсов в программах на Java.

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

Тема Swing весьма обширна, и для подробного ее рассмотрения потребовалась бы отдельная книга, поэтому в одной главе удастся коснуться лишь самых основных вопросов применения Swing. Но и представленного здесь материала окажется достаточно, чтобы составить общее представление о Swing. В этой главе будут рассмотрены пять наиболее употребительных компонентов пользовательского интерфейса, создаваемых средствами Swing: метки, кнопки, поля ввода текста, флажки и списки. А в конце главы будет создан апплет на основе Swing. И хотя в данной главе описана лишь небольшая часть инструментальных средств Swing, изучив их, вы сможете писать простые программы с графическим пользовательским интерфейсом. А полученные знания послужат вам прочным основанием для дальнейшего изучения Swing.

На заметку
Более подробные сведения о Swing можно найти в книге Swing: Руководство для начинающих, ИД «Вильямс», 2007 г.

Происхождение и принципы конструирования Swing

В ранних версиях Java средства Swing отсутствовали. Можно считать, что они появились в результате попытки преодолеть трудности, связанные с использованием в Java первой оконной подсистемы — библиотеки Abstract Window Toolkit (AWT). В этой библиотеке определен базовый набор компонентов для создания вполне работоспособных пользовательских интерфейсов, но с ограниченными возможностями. Одним из ограничений AWT стало превращение ее визуальных компонентов в платформенно-зависимые эквиваленты, или равноправные компоненты. Это означает, что внешний вид компонентов AWT определялся не средствами Java, а используемой платформой. А поскольку в компонентах AWT применялся в качестве ресурса собственный код, то они назывались тяжеловесными.

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

Потребовалось не слишком много времени, чтобы осознать, что ограничения AWT слишком серьезны и нужен новый подход. В результате в 1997 году появилась библиотека компонентов Swing, включенная в состав библиотеки классов JFC (Java Foundation Classes). Первоначально Swing использовалась в версии Java 1.1 как отдельная библиотека. Но начиная с версии Java 1.2 средства,Swing (как, впрочем, и остальные элементы JFC) были полностью интегрированы в Java.

Swing устраняет ограничения, присущие компонентам AWT, благодаря использованию двух основных средств: легковесных компонентов и подключаемых стилей оформления. И хотя эти средства практически незаметны для программирующего на Java, именно они составляют основные принципы конструирования Swing и в значительной мере обусловливают возможности и удобство применения этой библиотеки. Рассмотрим каждое из них в отдельности.

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

Благодаря тому что каждый компонент Swing воспроизводится в коде Java, а не опирается на платформенно-зависимые равноправные компоненты, стиль оформления
компонента можно отделить от логики его функционирования, что и делается средствами Swing. Такое разделение позволяет изменить внешний вид компонента, не затрагивая других его свойств. Иными словами, появляется возможность менять стиль оформления любого компонента без побочных эффектов, оказывающих влияние на его применение.

Каждому пользователю Swing изначально доступны различные стили оформления, к числу которых относятся металлический (Metal) и Motif (стандартный для ОС UNIX). Металлический стиль называется также стилем оформления Java. Это платформеннонезависимый стиль оформления, доступный во всех средах выполнения программ на Java. Он же принимается по умолчанию, поэтому именно он и будет использоваться в примерах, представленных в этой главе.

Реализация подключаемых стилей оформления в Swing стала возможной благодаря тому, что при создании Swing был использован видоизмененный вариант классической архитектуры модель—представление—контроллер (MVC). В терминологии MVC модель определяет состояние компонента. Например, для флажка модель содержит поле, обозначающее состояние установки или сброса флажка. Представление определяет порядок отображения компонента на экране, не исключая ничего, на что может оказывать влияние текущее состояние модели. А контроллер определяет порядок реагирования компонента на действия пользователя. Так, если пользователь щелкнет кнопкой мыши на флажке, контроллер отреагирует, изменив модель таким образом, чтобы отразить выбор пользователя (установку или сброс флажка). В ответ на действия пользователя обновляется и представление. Разделяя компонент на модель, представление и контроллер, можно добиться того, что реализация одной из этих составляющих компонента не будет оказывать влияние на две другие. Например, в некоторых реализациях представления один и тот же компонент может отображаться разными способами, а модель и контроллер — оставаться без изменения.

Несмотря на то что принципы построения архитектуры MVC выглядят очень привлекательно, для компонентов Swing разделение функций между представлением и контроллером не дает ощутимых преимуществ. Поэтому в Swing применяется видоизмененный вариант MVC, в котором представление и контроллер объединены в единую логическую сущность, называемую представителем пользовательского интерфейса. В связи с этим принятый в Swing подход называется архитектурой модель—представитель, а иначе — архитектурой с разделенной моделью. Таким образом, компоненты Swing нельзя рассматривать как классическую реализацию архитектуры MVC, хотя их архитектура и опирается на нее. В процессе разработки вам не придется иметь дело непосредственно с моделями или представителями пользовательского интерфейса, но они будут незримо присутствовать в создаваемых вами программах.

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

Компоненты и контейнеры

В состав графического пользовательского интерфейса, создаваемого средствами Swing, входят две основные разновидности элементов: компоненты и контейнеры. Такое разделение во многом условно, поскольку контейнеры являются в то же время компонентами, а отличаются друг от друга своим назначением. Компонент — это независимый элемент (например, кнопка или поле ввода текста), а контейнер может содержать в себе несколько компонентов. Следовательно, контейнер — это особая разновидность компонента. Для того чтобы отобразить компонент на экране, его следует поместить в контейнер. Поэтому в графическом пользовательском интерфейсе должен присутствовать хотя бы один контейнер. А так как контейнеры являются в то же время компонентами, то один контейнер может содержать в себе другой. Это дает возможность сформировать так называемую иерархию контейнеров, на вершине которой должен находиться контейнер верхнего уровня.

Компоненты

Подавляющее большинство компонентов Swing создаются с помощью классов, производных от класса JComponent. (Единственным исключением из этого правила являются четыре контейнера верхнего уровня, которые будут описаны в следующем разделе.) В классе JComponent реализуются функциональные возможности, общие для всех компонентов. Например, в классе JComponent поддерживаются подключаемые стили оформления. Этот класс наследует свойства классов Container и Component из библиотеки AWT. Таким образом, компоненты Swing создаются на основе компонентов AWT и совместимы с ними.

Классы, представляющие все компоненты Swing, входят в пакет javax.swing. В приведенной ниже таблице перечислены имена классов всех компонентов Swing (включая компоненты, используемые как контейнеры).

JApplet JButton JcheckBox JcheckBoxMenuItem
JColorChooser JComboBox Jcomponent JDesktopPane
JDialog JEditorPane JfileChooser JformattedTextField
JFrame JInternalFrame Jlabel JLayeredPane
JList JMenu JmenuBar JMenuItem
JOptionPane JPanel JpasswordField JPopupMenu
JProgressBar JRadioButton JradioButtonMenuItem JRootPane
JScrollBar JScrollPane Jseparator JSlider
JSpinner JSplitPane JtabbedPane JTable
JTextArea JTextField JtextPane JTogglebutton
JToolBar JToolTip Jtree JViewport
JWindow

Как видите, имена всех классов начинаются с буквы “J”. Например, метке соответствует класс JLabel, кнопке — класс JButton, а флажку — класс JCheckBox.

Как пояснялось в начале этой главы, в рамках данной книги нет возможности рассмотреть все компоненты Swing: для этого потребовалась бы отдельная книга. Но в этой главе будут представлены пять наиболее употребительных компонентов: JLabel, JButton, JTextField, JCheckBox и JList. Если вы уясните их принцип действия, вам будет легче овладеть другими компонентами.

Контейнеры

В Swing определены две разновидности контейнеров. К первой из них относятся следующие контейнеры верхнего уровня: JFrame, JApplet, JWindow и JDialog. Они не наследуют переменные и методы от класса JComponent, тем не менее являются производными от классов Component и Container. В отличие от других, легковесных компонентов Swing, контейнеры верхнего уровня являются тяжеловесными. Именно поэтому контейнеры верхнего уровня составляют отдельную группу в библиотеке Swing.

Как следует из названия контейнеров верхнего уровня, они должны находиться на вершине иерархии контейнеров и не могут содержаться в других контейнерах. Более того, любая иерархия должна начинаться именно с контейнера верхнего уровня. В прикладных программах чаще всего используется контейнер типа JFrame, а в апплетах — контейнер типа JApplet.

Контейнеры второго рода являются легковесными и производными от класса JComponent. В качестве примера легковесных контейнеров можно привести классы JPanel, JScrollPane и JRootPane. Легковесные контейнеры могут содержаться в других контейнерах, и поэтому они нередко используются для объединения группы взаимосвязанных компонентов.

Панели контейнеров верхнего уровня

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

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

Многослойная панель позволяет отображать компоненты в глубину, определяя порядок перекрытия одних компонентов другими. (Это означает, что панель слоя позволяет задавать расположение компонентов в глубину, хотя такая возможность используется сравнительно редко.) В состав многослойной панели входит панель содержимого и может также включаться строка меню.

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

Прикладная программа взаимодействует в основном с панелью содержимого, поскольку именно в нее включаются визуальные компоненты. Иными словами, добавляя
компонент, например кнопку, в контейнер верхнего уровня, вы на самом деле вводите
его на панели содержимого.

Диспетчеры компоновки

Прежде чем приступать к написанию программ средствами Swing, необходимо составить себе хотя бы общее представление о диспетчерах компоновки. Диспетчер компоновки управляет размещением компонентов в контейнере. В Java определено несколько таких диспетчеров. Большинство из них входит в состав AWT (т.е. в пакет j ava. awt), но в Swing также предоставляется ряд дополнительных диспетчеров компоновки. Все диспетчеры компоновки являются экземплярами классов, реализующих интерфейс LayoutManager. (Некоторые из диспетчеров компоновки реализуют интерфейс LayoutManager2.) Ниже перечислен ряд диспетчеров компоновки, доступных для разработчиков прикладных программ, пользующихся средствами Swing.

FlowLayout Простой диспетчер компоновки, размещающий компоненты слева направои сверху вниз. (Для некоторых региональных настроек компоненты располагаются справа налево.)
BorderLayout Располагает компоненты по центру и по краям контейнера. Этот диспетчер компоновки принимается по умолчанию для панели содержимого
GridLayout Располагает компоненты сеткой, как в таблице
GridBagLayout Располагает компоненты разных размеров настраиваемой сеткой, как в таблице
BoxLayout Располагает компоненты в блоке по вертикали или по горизонтали
SpringLayout Располагает компоненты с учетом ряда ограничений

Диспетчеры компоновки, как и многие другие компоненты Swing, невозможно обсудить во всех подробностях в одной главе. Поэтому ограничимся рассмотрением только двух из них: BorderLayout и FlowLayout.

Для панели содержимого по умолчанию принимается диспетчер компоновки BorderLayout. Этот диспетчер компоновки определяет в составе контейнера пять областей, в которые могут помещаться компоненты. Первая область располагается посредине и называется центральной. Остальные четыре размещаются по сторонам света и соответственно называются северной, южной, восточной и западной. По умолчанию компонент, добавляемый на панели содержимого, располагается в центральной области. Для того чтобы расположить компонент в другой области, следует указать ее имя.

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

Первая простая Swing-программа

Программы, создаваемые средствами Swing, называемые в дальнейшем для краткости Swing-программами, отличаются от консольных программ, примеры которых были рассмотрены ранее в этой книге. Они отличаются и от апплетов, создаваемых на основе AWT и обсуждавшихся в главе 14. Swing-программы не просто настраивают применение компонентов Swing, обеспечивающих взаимодействие с пользователем, но и должны отвечать особым требованиям, связанным с организацией поточной обработки. Для того чтобы стала понятнее структура Swing-программы, лучше всего обратиться к конкретному примеру. Программы, в которых применяются компоненты Swing, можно условно разделить на две категории. К первой категории относятся обычные программы для настольных систем, которые зачастую называются настольными приложениями, а ко второй категории —апплеты. В этом разделе будет показано, каким образом создается Swing-приложение. А создание Swing-апплета будет рассмотрено далее в главе.

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

// Простая Swing-программа.

// Для каждой Swing-программы импортируется пакет javax.swing.
import javax.swing.*;

class SwingDemo {

    SwingDemo() {

        // Создание нового контейнера JFrame.
        JFrame jfrm = new JFrame("A Simple Swing Application");

        // Установка начальных размеров рамки окна.
        jfrm.setSize(275, 100);

        // При закрытии окна программа должна завершиться.
        jfrm.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        // Создание текстовой метки с помощью компонента Swing.
        JLabel jlab = new JLabel(" Swing defines the modern Java GUI.");

        // Добавление метки на панели содержимого.
        jfrm.add(jlab);

        // Отображение рамки окна.
        jfrm.setVisible(true);
    }

    public static void main(String args[]) {
        // Объект SwingDemo должен быть создан в потоке
        // диспетчеризации событий.
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                new SwingDemo();
            }
        }) ;
    }
}

Эта программа компилируется и запускается таким же образом, как и любое другое Java-приложение. Для ее компиляции в командной строке нужно ввести следующее:

А для запуска программы на выполнение в командной строке нужно ввести следующее:

При выполнении данной программы отображается окно, приведенное на рис. 15.1.

Рис. 15.1. Окно, отображаемое при выполнении программы SwingDemo

Построчный анализ первой Swing-программы

Пример программы SwingDemo демонстрирует основные понятия Swing, поэтому имеет смысл проанализировать ее подробно, уделив внимание каждой строке кода. В начале программы осуществляется импорт пакета, как показано ниже.

Этот пакет содержит компоненты и модели Swing. В частности, в нем определены классы, реализующие метки, кнопки, поля ввода текста и меню. Этот пакет должен быть непременно включен в каждую программу, использующую библиотеку Swing.

Далее в программе объявляется класс SwingDemo и его конструктор. Именно в конструкторе выполняется большая часть действий этой программы. Код конструктора начинается с создания объекта типа JFrame. Для этой цели служит следующая строка кода:

JFrame jfrm = new JFrame("A Simple Swing Application.");

Подобным образом создается контейнер jfrm, который определяет прямоугольное окно, содержащее строку заголовка, кнопки, предназначенные для закрытия, сворачивания, разворачивания и восстановления нормальных размеров окна, а также системное меню. Иными словами, в приведенной выше строке кода создается стандартное окно верхнего уровня, а строка заголовка передается конструктору в качестве параметра.

Далее задаются размеры окна в пикселях, как показано ниже,

Для этой цели вызывается метод setSize(). Ниже приведена общая форма объявления этого метода.

void setSize(int ширина, int высота)

В рассматриваемом здесь примере задается ширина окна — 215 пикселей и высота — 100 пикселей.

По умолчанию, когда окно верхнего уровня закрывается (например, по щелчку на кнопке в его верхнем правом углу), оно удаляется с экрана, но приложение не завершает на этом свою работу. И хотя такое поведение подходит для некоторых ситуаций, в большинстве случаев оно неприемлемо. Ведь, как правило, желательно, чтобы при закрытии окна верхнего уровня приложение завершалось. И добиться этого можно несколькими способами. Самый простой из них состоит в том, чтобы вызвать метод setDefaultCloseOperation(). Именно такой способ и применен в анализируемой здесь программе, как показано ниже.

jfrm.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

После выполнения данного метода закрытие окна приведет к завершению работы всего приложения. Ниже приведена общая форма объявления метода

setDefaultCloseOperation().
void setDefaultCloseOperation(int what)

Значение, передаваемое в качестве параметра what, определяет, что именно должно произойти при закрытии окна. Помимо константы JFrame.EXIT_0NJ3L0SE, данному методу можно передавать и другие, приведенные ниже константы.

JFrame.DISPOSE_ON_CLOSE
JFrame.HIDE_ON_CLOSE
JFrame.DO_NOTHING_ON_CLOSE

Имена констант отражают их назначение: освободить, скрыть, ничего не делать после закрытия окна соответственно. Все они определены в интерфейсе WindowConstants, входящем в пакет javax. swing. Этот интерфейс реализуется в классе JFrame.

В следующей строке кода создается компонент JLabel из библиотеки Swing:

JLabel jlab = new JLabel(" Swing defines the modern Java GUI.");

Компонент JLabel — самый простой в использовании среди всех компонентов Swing, поскольку он не предполагает обработку событий, связанных с действиями пользователя, а только отображает информацию: текст, изображение или и то и другое. Метка, созданная в данной программе, содержит только текст. А строка, содержащая этот текст, передается конструктору класса JLabel.

В следующей строке кода метка вводится на панели содержимого рамки окна:

Как пояснялось ранее, каждый контейнер верхнего уровня включает в себя панель содержимого, на которой располагаются компоненты. Поэтому для размещения компонента внутри рамки окна его следует добавить на панели содержимого. С этой целью вызывается метод add() из класса JFrame (в данном случае ссылка на объект типа JFrame содержится в переменной j frm). Существует несколько вариантов метода add(). Чаще других используется такой вариант:

Component add(Component компонент)

По умолчанию на панели содержимого, связанной с контейнером JFrame, используется диспетчер компоновки BorderLayout. В приведенном выше варианте метода add() компонент (в данном случае — метка) располагается в центральной области. Имеются также варианты add(), позволяющие располагать компоненты в других областях. При размещении в центральной области размеры компонента автоматически приводятся к размерам этой области.

И последний оператор в конструкторе класса SwingDemo обеспечивает отображение окна, как показано ниже.

Общая форма объявления метода setVisible() выглядит следующим образом:

void setVisible(boolean флаг)

Если параметр флаг принимает логическое значение true, окно отображается на экране, в противном случае оно остается скрытым. По умолчанию рамку окна (объект типа JFrame) не видно, поэтому для ее отображения требуется вызов

В методе main() создается объект класса SwingDemo, в результате чего окно и метка отображаются на экране. Конструктор класса SwingDemo вызывается в следующем фрагменте кода:

SwingUtilities.invokeLater(new Runnable()   {
    public void run() {
        new SwingDemo();
    }
});

В этом фрагменте кода объект типа SwingDemo создается не в основном потоке приложения, а в потоке диспетчеризации событий. Такое решение принимается по ряду причин. Прежде всего, Swing-программы, как правило, управляются событиями. Так, если пользователь активизирует компонент пользовательского интерфейса, формируется соответствующее событие. Оно передается прикладной программе путем вызова обработчика событий, определенного в этой программе. Но этот обработчик выполняется в специальном потоке диспетчеризации событий, формируемом средствами Swing, а не в главном потоке прикладной программы. Таким образом, поток, в котором выполняется этот обработчик событий, создается другими средствами, хотя все обработчики событий определяются в самой программе. Во избежание осложнений, связанных, например, с попытками двух потоков одновременно обновить один и тот же компонент, все компоненты пользовательского интерфейса из библиотеки Swing должны создаваться и обновляться не в основном потоке приложения, а в потоке диспетчеризации событий. Но ведь метод main() выполняется в основном потоке, и поэтому в нем нельзя непосредственно создавать объект класса SwingDemo. Сначала следует построить объект типа Runnable, выполняемый в потоке диспетчеризации событий, а затем предоставить ему возможность построить графический пользовательский интерфейс.

Для того чтобы активизировать код построения графического пользовательского интерфейса в потоке диспетчеризации событий, следует воспользоваться одним из двух методов, определенных в классе SwingUtilities: invokeLater() или invokeAndWait(). Эти методы объявляются следующим образом:

static void invokeLater(Runnable obj)

static void invokeAndWait(Runnable obj)
    throws InterruptedException, InvocationTargetException

Параметр obj обозначает объект типа Runnable, метод run() которого вызывается в потоке диспетчеризации событий. Оба упомянутых выше метода отличаются тем, что метод invokeLater() сразу же возвращает управление вызывающему методу, тогда как метод invokeAndWait() ожидает завершения метода obj . run(). Их можно использовать для вызова метода, выполняющего построение пользовательского интерфейса Swing-приложения, а также в тех случаях, когда требуется изменить состояние этого интерфейса из метода, выполняющегося за пределами потока диспетчеризации событий. Для этой цели обычно используется метод invokeLater(), что и было сделано в рассматриваемом здесь примере. Но при создании пользовательского интерфейса для апплета лучше воспользоваться методом invokeAndWait(). (О создании Swing-апплета речь пойдет далее в этой главе.)

Следует отметить еще одну особенность анализируемой здесь программы: она не реагирует на действия пользователя, поскольку в компоненте JLabel не предусмотрены соответствующие средства. Иными словами, в этой программе не были предусмотрены обработчики событий, потому что компонент JLabel вообще не формирует события. А все остальные компоненты Swing формируют события, на которые программа должна каким-то образом реагировать, что и будет продемонстрировано на конкретных примерах далее в главе.

Применение компонента JButton

Кнопка является одним из наиболее употребительных компонентов Swing. Нажимаемая кнопка представлена в Swing экземпляром класса JButton. Этот класс является производным от абстрактного класса AbstractButton, в котором определены функции, общие для всех кнопок. На кнопке может отображаться текст надписи, изображение или и то и другое, но здесь и далее будут рассматриваться только кнопки с текстовыми надписями.

Класс JButton содержит три конструктора. Один из них имеет следующий вид:

JButton(String сообщение)

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

После щелчка на кнопке формируется событие ActionEvent. Класс ActionEvent определен в библиотеке AWT, но используется также и в библиотеке Swing. В классе JButton предоставляются методы, позволяющие зарегистрировать приемник событий или отменить его регистрацию:

void addActionListener(ActionListener al)
void removeActionListener(ActionListener al)

где параметр al задает объект, который будет уведомляться о наступлении событий. Объект должен представлять собой экземпляр класса, реализующего интерфейс ActionListener.

В интерфейсе ActionListener определен только один метод: actionPerformed(). Ниже приведена общая форма объявления этого метода.

void actionPerformed(ActionEvent ae)

Данный метод вызывается при щелчке на кнопке. Следовательно, в этом методе осуществляется обработка событий, связанных с действиями пользователя над кнопкой. Реализуя метод actionPerf ormed(), необходимо позаботиться о том, чтобы он быстро выполнял свои функции и возвращал управление. Как пояснялось ранее, обработчики событий не должны выполнять длительные операции, поскольку это может привести к замедлению работы приложения в целом. Если же обработка события предполагает действия, требующие времени, их следует выполнять в отдельном потоке, специально создаваемом для этой цели.

С помощью объекта типа ActionEvent, передаваемого методу actionPerf ormed(), можно получить важные сведения о событии, связанном с щелчком на кнопке. В этой главе для этой цели будет использоваться символьная строка команды действия, связанная с кнопкой. По умолчанию именно эта символьная строка отображается внутри кнопки. Получить команду действия можно, вызвав метод getActionCommand() для объекта события, который объявляется следующим образом:

String getActionCommand()

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

Ниже приведен пример программы, демонстрирующий применение кнопки, реагирующей на действия пользователя. А на рис. 15.2 показано окно, отображаемое данной программой на экране.

// Демонстрация нажатия кнопки и обработки событий действия.

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

class ButtonDemo  implements ActionListener {
    JLabel jlab;

    ButtonDemo() {

        // создать новый контейнер JFrame
        JFrame jfrm = new JFrame("A Button Example");

        // установить диспетчер компоновки FlowLayout
        jfrm.setLayout(new FlowLayout());

        // задать исходные размеры рамки окна   <
        jfrm.setSize(220, 90);

        // завершить программу после закрытия окна
        jfrm.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        // Создание двух кнопок.
        JButton jbtnUp = new JButton("Up");
        JButton jbtnDown = new JButton("Down");

        // Добавление приемников событий от кнопки.
        jbtnUp.addActionListener(this);
        jbtnDown.addActionListener(this) ;

        // Добавление кнопок на панели содержимого.
        jfrm.add(jbtnUp);
        jfrm.add(jbtnDown);

        // создать метку
        jlab = new JLabel("Press a button.");

        // добавить метку в рамке окна
        jfrm.add(jlab);

        // отобразить рамку окна
        jfrm.setVisible(true);
    }

    // Обработка событий от кнопки.
    public void actionPerformed(ActionEvent ae) {
        // Для определения нажатой кнопки используется команда действия.
        if(ae.getActionCommand().equals("Up"))
            jlab.setText("You pressed Up.");
        else
            jlab.setText("You pressed down. ");
    }

    public static void main(String args[]) {
        // создать рамку окна в потоке диспетчеризации событий
        SwingUtilities.invokeLater(new Runnable() {
            public 'void run() {
                new ButtonDemoO;
            }
        }) ;
    }
}

Рис. 15.2. Окно, отображаемое при выполнении программы ButtonDemo

Проанализируем приведенную выше программу, обратив внимание на новые и рассматривавшиеся до сих пор компоненты. Сначала в этой программе импортируются пакеты j ava. awt и j ava. awt. event. Пакет j ava. awt необходим, поскольку он содержит класс диспетчера компоновки FlowLayout, а пакет j ava. awt. event — потому, что в нем определены интерфейс ActionListener и класс ActionEvent.

Далее в программе объявляется класс ButtonDemo, который реализует интерфейс ActionListener. Это означает, что объекты типа ButtonDemo могут быть использованы для приема и обработки событий действия. Затем объявляется ссылка на объект типа JLabel. Она будет использована в методе actionPerf ormed() для отображения сведений о том, какая именно кнопка была нажата.

Конструктор класса ButtonDemo начинается с создания контейнера jfrm типа JFrame. Затем в качестве диспетчера компоновки для панели содержимого контейнера jfrm устанавливается FlowLayout, как показано ниже,

j frm.setLayout(new FlowLayout());

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

После установки размеров рамки окна и определения операции, завершающей программу, в конструкторе ButtonDemo() создаются две кнопки:

JButton jbtnUp = new JButton("Up");
JButton jbtnDown = new JButton("Down");

На первой кнопке отображается надпись Up (Нажато), а на второй — Down (Отпущено).

Далее с кнопками связывается приемник событий действия, в роли которого выступает экземпляр класса ButtonDemo, а ссылка на него передается с помощью ключевого слова this. Соответствующие строки кода приведены ниже.

jbtnUp.addActionListener(this) ;
jbtnDown.addActionListener(this) ;

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

Всякий раз, когда кнопка нажимается, формируется событие, о котором зарегистрированные приемники уведомляются в результате вызова метода actionPerformed(). Объект типа ActionEvent, представляющий событие от кнопки, передается этому методу в качестве параметра. В программе ButtonDemo это событие передается следующей реализации метода actionPerformed():

// Обработка событий от кнопки.
public void actionPerformed(ActionEvent ae) {
    if(ae.getActionCommand().equals("Up"))
        jlab.setText("You pressed Up.");
    else
        jlab.setText("You pressed down. ");
}

Для передачи события служит параметр ае. В теле метода извлекается команда действия, которая соответствует кнопке, сформировавшей событие. Для получения команды действия вызывается метод getActionCommand(). (Напомним: по умолчанию команда действия совпадает с текстом, отображаемым на кнопке.) В зависимости от содержания символьной строки, представляющей команду действия, устанавливается текст надписи на кнопке, указывающий на то, какая именно кнопка была нажата.

Следует также иметь в виду, что метод actionPerformed() вызывается в потоке диспетчеризации событий. Он должен завершаться как можно быстрее, чтобы не замедлять работу приложения.

Работа с компонентом JTextField

К числу широко используемых компонентов Swing относится также компонент JTextField, который дает пользователю возможность вводить и редактировать текстовую строку. Компонент JTextField является подклассом, производным от абстрактного класса JTextComponent, который выступает в роли суперкласса не только для компонента JTextField, но и для всех текстовых компонентов вообще. В классе JTextField определен ряд конструкторов. Здесь и далее будет использоваться следующий конструктор:

где cols обозначает ширину текстового поля, выраженную в столбцах. Однако длина вводимой строки не ограничивается шириной поля, отображаемого на экране. Поэтому, если в конструкторе присутствует параметр cols, он задает лишь упомянутый выше физический размер компонента.

Для завершения ввода текста в поле пользователь нажимает клавишу , в результате чего формируется событие ActionEvent. В классе JTextField предоставляются методы addActionListener() и removeActionListener(). Для обработки событий действия необходимо реализовать метод actionPerformed(), объявленный в интерфейсе ActionListener. Обработка событий от поля ввода текста осуществляется таким же образом, как и обработка событий от кнопки, о которых шла речь ранее.

Как и с компонентом JButton, с компонентом JTextField связывается конкретная команда действия в виде символьной строки. По умолчанию она соответствует текущему содержимому поля ввода текста, хотя в таком значении используется редко. Чаще всего устанавливается фиксированное значение команды действия с помощью метода setActionCommand(), который объявляется следующим образом:

void setActionCommand(String cmd)

Символьная трока, передаваемая в качестве параметра cmd, становится новой командой действия, а текст в поле ввода текста не меняется. Установленная символьная строка команды действия остается постоянной, независимо от того, какой именно текст вводится в поле ввода текста. Как правило, к явной установке команды действия прибегают для того, чтобы обеспечить распознавание поля ввода текста как источника, сформировавшего событие действия. Поступать подобным образом приходится в том случае, если в рамке окна находится несколько элементов управления, для которых определен общий обработчик событий. Установив команду действия, вы получаете в свое распоряжение удобное средство для различения компонентов. Если же вы не установите явно команду действия для поля ввода текста, то можете испытать затруднения при распознавании источника события, так как пользователь может ввести в поле произвольный текст, совпадающий с командой действия другого компонента.

Для того чтобы получить символьную строку, отображаемую в поле ввода текста, следует обратиться к экземпляру класса JTextField и вызвать метод getText(). Объявление этого метода приведено ниже.

Задать текст для компонента JTextField можно с помощью метода setText(), объявляемого следующим образом:

void setText(String текст)

где текст — это символьная строка, размещаемая в поле ввода текста.

Ниже приведен пример программы, демонстрирующий применение компонента JTextField. В окне этой программы содержатся поле ввода текста, кнопка и две метки. Одна из меток подсказывает пользователю ввести текст в поле. Когда пользователь нажмет клавишу (при условии, что фокус ввода находится в поле ввода текста), введенные данные будут извлечены и отображены на второй метке. На кнопке отображается надпись Reverse (Обратить). При нажатии этой кнопки содержимое поля ввода текста преобразуется и заменяется на обратное. Окно, отображаемое на экране при выполнении данной программы, приведено на рис. 15.3.

// Применение поля ввода текста.

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

class TFDemo implements ActionListener {

    JTextField jtf;
    JButton jbtnRev;
    JLabel jlabPrompt, jlabContents;

    TFDemo() {

        // создать новый контейнер JFrame
        JFrame jfrm = new JFrame("Use a Text Field");

        // установить диспетчер компоновки FlowLayout
        jfrm.setLayout(new FlowLayout());

        // задать исходные размеры рамки окна
        jfrm.setSize(240, 120);

        // завершить программу после закрытия окна
        jfrm.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        // Создание поля ввода текста шириной 10 символов.
        jtf = new JTextField(10);

        // Установка команды действия для поля ввода текста.
        jtf.setActionCommand("myTF");

        // создать кнопку Reverse
        JButton jbtnRev = new JButton("Reverse") ;

        // Добавление приемников событий от поля ввода и кнопки.
        jtf.addActionListener(this);
        jbtnRev.addActionListener(this) ;

        // создать метки
        jlabPrompt = new JLabel("Enter text: ");
        jlabContents = new JLabel("");

        // добавить компоненты на панели содержимого
        jfrm.add(jlabPrompt);
        jfrm.add(jtf);
        jfrm.add(jbtnRev);
        jfrm.add(jlabContents) ;

        // отобразить рамку окна
        jfrm.setVisible(true);
    }

    // Обработка событий от кнопки и поля ввода текста.
    public void actionPerformed(ActionEvent ae) {
        // Для определения компонента, сформировавшего событие,
        // используется команда действия.
        if(ae.getActionCommand().equals("Reverse")) {
            // Нажатие кнопки Reverse.
            String orgStr = jtf.getText();
            String resStr = "";

            // обратить символьную строку в поле ввода текста
            for(int i=orgStr.length()-1; i >=0; i—)
                resStr += orgStr.charAt(i);

            // сохранить обращенную строку в поле ввода текста
            jtf.setText(resStr);
        } else
            // Нажатие клавиши <Enter> в тот момент, когда фокус
            // ввода находится в поле ввода текста,
            jlabContents.setText("You pressed ENTER. Text is: " +
                                 jtf.getText());
    }

    public static void main(String args[])  {
        // создать рамку окна в потоке диспетчеризации событий
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                new TFDemo();
            }
        }) ;
    }
}

Рис. 15.3. Окно, отображаемое при выполнении программы TFDemo

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

jtf.setActionCommand("myTF");

После выполнения этой строки кода символьная строка команды действия всегда принимает значение «myTF» независимо от того, какой именно текст введен в поле. Благодаря этому исключается ситуация, при которой команда действия, связанная с полем ввода текста, будет вступать в конфликт с командой действия, связанной с кнопкой Reverse. В методе actionPerformed() установленная команда действия используется для распознавания того компонента, который стал источником события. Если символьная строка команды действия принимает значение «Reverse», это может означать только одно: событие наступило в результате щелчка на кнопке Reverse. Иначе следует сделать вывод, что событие наступило в результате нажатия пользователем клавиши в тот момент, когда фокус ввода находился в поле ввода текста.

И наконец, обратите внимание на следующую строку кода в теле метода

actionPerformed():
jlabContents.setText("You pressed ENTER. Text is: " +
                        jtf.getText());

Как пояснялось выше, при нажатии клавиши в тот момент, когда фокус ввода находился в поле ввода текста, формируется событие ActionEvent, которое пересылается всем зарегистрированным приемникам событий действия с помощью метода actionPerf ormed(). В программе TFDemo этот метод лишь вызывает метод getText(), извлекая текст, содержащийся в компоненте jtf (поле ввода текста). После этого текст отображается с помощью метки, на которую ссылается переменная j labContents.

Создание флажков с помощью компонента JCheckBox

Если обычные кнопки используются чаще других элементов пользовательского интерфейса, то на втором месте по частоте употребления, безусловно, стоят флажки. В Swing эти элементы пользовательского интерфейса реализуются с помощью компонента типа JCheckBox. Класс JCheckBox является производным от классов AbstractButton и JToggleButton. Следовательно, флажок — это особая разновидность кнопки.

В классе JCheckBox определен ряд конструкторов. Один из них имеет следующий вид:

Он создает флажок с пояснительной надписью в виде символьной строки, передаваемой в качестве параметра str.

При установке или сбросе флажка формируется событие от элемента, представленное классом ItemEvent. Для обработки событий от элементов используются классы, реализующие интерфейс itemListener. В этом интерфейсе объявлен лишь один метод, itemStateChanged(), объявляемый следующим образом:

void itemStateChanged(ItemEvent ie)

Здесь событие от элемента передается в качестве параметра ie.

Для того чтобы получить ссылку на элемент, состояние которого изменилось, следует вызвать метод get Item() для объекта ItemEvent. Ниже приведена общая форма объявления этого метода.

Возвращаемая этим методом ссылка должна быть приведена к типу оперируемого компонента, а в данном случае — к классу JCheckBox.

Текст, связанный с флажком, можно получить, вызвав метод getText(), а задать текст пояснительной надписи, вызвав метод setText(). Эти методы действуют таким же образом, как и одноименные методы из рассмотренного ранее класса JButton.

Самый простой способ определить состояние флажка — вызвать метод isSelected(), который объявляется следующим образом:

Этот метод возвращает логическое значение true, если флажок установлен, иначе — логическое значение false.

Ниже приведен пример программы, демонстрирующий манипулирование флажками. В ней создаются три флажка: Alpha, Beta и Gamma. Всякий раз, когда состояние флажка изменяется, в окне программы появляются сведения о произведенном действии, а также перечисляются те флажки, которые установлены в данный момент. Окно, отображаемое на экране при выполнении данной программы, приведено на рис. 15.4.

// Демонстрация флажков.

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

class CBDemo implements ItemListener {
    JLabel jlabSelected;
    JLabel jlabChanged;
    JCheckBox jcbAlpha;
    JCheckBox jcbBeta;
    JCheckBox jcbGamma;

    CBDemo() {
        // создать новый контейнер JFrame
        JFrame jfrm = new JFrame("Demonstrate Check Boxes");

        // установить диспетчер компоновки FlowLayout
        jfrm.setLayout(new FlowLayout());

        // задать исходные размеры рамки окна
        jfrm.setSize(280, 120);

        // завершить программу после закрытия окна
        jfrm.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        // создать пустые метки
        jlabSelected = new JLabel("");
        jlabChanged = new JLabel("");

        // Создание флажков.
        jcbAlpha = new JCheckBox("Alpha");
        jcbBeta = new JCheckBox("Beta");
        jcbGamma = new JCheckBox("Gamma");

        // События, формируемые компонентами JCheckBox, обрабатываются
        // одним методом itemStateChanged(), реализованным в классе CBDemo.
        jcbAlpha.addltemListener(this);
        jcbBeta.addltemListener(this) ;
        jcbGamma.addltemListener(this);

        // добавить флажки и метки на панели содержимого
        jfrm.add(jcbAlpha);
        jfrm.add(jcbBeta);
        jfrm.add(jcbGamma);
        jfrm.add(jlabChanged);
        jfrm.add(jlabSelected);

        // отобразить рамку окна
        jfrm.setVisible(true);
    }

    // Обработчик событий от элементов (в данном случае — флажков).
    public void itemStateChanged(ItemEvent ie) {
        String str = "";

        // Получение ссылки на компонент флажка, сформировавший событие.
        JCheckBox cb = (JCheckBox) ie.getltem();

        // сообщить об изменении состояния флажка
        if(cb.isSelected()) // Определение состояния флажка.
            jlabChanged.setText(cb.getText() + " was just selected.");
        else
            jlabChanged.setText(cb.getText() + " was just cleared.");

        // сообщить о всех установленных флажках
        if(jcbAlpha.isSelected()) {
            str += "Alpha ";
        }
        if(jcbBeta.isSelected()) {
            str += "Beta ";
        }
        if (jcbGamma.isSelected() ) {
            str += "Gamma";
        }

        jlabSelected.setText("Selected check boxes: " + str);
    }

    public static void main(String args[]) {
        // создать рамку окна в потоке диспетчеризации событий
        SwingUtilities.invokeLater(new Runnable()   {
            public void run() {
                new CBDemo();
            }
        });
    }
}


Рис. 15.4. Окно, отображаемое при выполнении программы CBDemo

Наибольший интерес в рассматриваемом здесь примере представляет метод itemStateChanged(), предназначенный для обработки событий от элементов (в данном случае — флажков). Он выполняет две функции: во-первых, сообщает, установлен или сброшен флажок; а во-вторых, отображает перечень установленных флажков. В начале этого метода определяется ссылка на компонент, сформировавший событие ItemEvent. Это происходит в следующей строке кода:

JCheckBox cb = (JCheckBox) ie.getltem();

Приведение к типу JCheckBox необходимо потому, что метод getltem() возвращает ссылку на объект типа Object. Далее метод itemStateChanged() обращается к методу isSelected() по ссылке cb, чтобы определить текущее состояние флажка. Если метод isSelected() возвращает логическое значение true, значит, флажок установлен, а логическое значение false соответствует сброшенному состоянию флажка. Затем с помощью метки j labChanged отображаются сведения о выполненном действии.

И наконец, метод itemStateChanged() проверяет состояние каждого флажка
и формирует символьную строку с именами установленных флажков. Эта символьная
строка отображается в окне программы с помощью метки j labSelected.

Работа с компонентом Jlist

Последним в этой главе будет рассмотрен компонент JList. Он является основным классом Swing для работы со списками и позволяет выбирать один или несколько элементов из списка. И хотя элементы списка являются символьными строками, это не мешает создать список, включающий практически любые объекты, которые могут быть отображены на экране. Компонент JList настолько широко применяется в реальных программах на Java, что его трудно было бы не заметить в них прежде.

Раньше элементы списка были представлены в компоненте JList в виде ссылок на объекты типа Object. А после выпуска версии JDK 7 компонент JList стал обобщенным и теперь объявляется следующим образом:

где Е обозначает тип элементов списка. Таким образом, в компоненте JList теперь обеспечивается типовая безопасность.

На заметку
Далее рассматривается обобщенный вариант компонента JList и демонстрируется пример его применения. Поэтому если вы пользуетесь компилятором более ранней версии, чем JDK 7, вам придется выбрать предыдущий, необобщенный вариант компонента JList.

В классе компонента JList предоставляется ряд конструкторов. Один из них имеет следующий вид:

Этот конструктор создает компонент JList со списком элементов, хранящихся в массиве, на который указывает параметр элементы.

Компонент JList и сам позволяет решить немало задач построения списков, но чаще всего он помещается в контейнер JScrollPane, автоматически обеспечивающий прокрутку своего содержимого. Ниже приведен конструктор этого контейнера.

JScrollPane(Component компонент)

Здесь компонент обозначает конкретный компонент, передаваемый конструктору в качестве параметра для прокрутки (в данном случае это компонент JList). Если поместить компонент JList в контейнер JScrollPane, то тем самым будет обеспечена автоматическая прокрутка длинных списков. Благодаря этому упрощается построение графического пользовательского интерфейса вообще и изменение числа элементов списка в частности, не затрагивая при этом размеры самого компонента JList.

Когда пользователь делает или изменяет выбор элемента в списке, компонент JList формирует событие ListSelectionEvent. Это же событие формируется при отмене выбора. Для его обработки используется объект приемника событий из класса, реализующего интерфейс ListSelectionListener. Этот интерфейс относится к пакету javax. swing. event. В этом интерфейсе объявлен только один метод, valueChanged(), объявляемый следующим образом:

void valueChanged(ListSelectionEvent le)

где Ie обозначает ссылку на объект, сформировавший событие. И хотя в самом классе ListSelectionEvent определен ряд методов, для выяснения того, что же произошло со списком, обычно приходится опрашивать сам объект типа JList. Класс ListSelectionEvent также относится к пакету j avax. swing. event.

По умолчанию компонент JList дает пользователю возможность выбрать несколько элементов из списка. Изменить такое поведение можно, вызвав метод setSelectionMode(), определенный в классе JList. Этот метод объявляется так:

void setSelectionMode(int режим)

где параметр режим задает порядок выбора элементов из списка. Значение этого параметра должно совпадать с одной из приведенных ниже констант, определенных в интерфейсе ListSelectionModel, входящем в пакет javax. swing.

SINGLE_SELECTION
SINGLE_INTERVAL_SELECTION
MULTIPLE_INTERVAL_SELECTION

По умолчанию устанавливается режим MULTIPLE_INTERVAL_SELECTION, и пользователь может выбирать ряд элементов из списка через несколько промежутков. В режиме SINGLE_INTERVAL_SELECTI0N можно выбирать ряд элементов из списка только через один промежуток. А в режиме SINGLE SELECTION каждый раз можно выбрать только один элемент из списка. Очевидно, что единственный элемент может быть выбран из списка и в двух других режимах, где допускается также выбирать одновременно целый ряд элементов.

Индекс первого элемента, выбранного из списка, а в режиме SINGLE SELECTION — это индекс единственного выбранного элемента, можно получить, вызвав метод getSelectedlndex(). Ниже показано, каким образом он объявляется,

Индексирование начинается с нуля. Так, если выбран первый элемент в списке, этот метод возвращает значение 0. Если же ни один из элементов не выбран, возвращается значение -1.

Получить массив, содержащий все выбранные из списка элементы, можно, вызвав метод getSelectedlndices():

int[] getSelectedlndices()

В возвращаемом массиве индексы расположены по возрастающей. Если же получен массив нулевой длины, это означает, что ни один из элементов не выбран из списка.

Ниже приведен пример программы, демонстрирующий применение простого компонента JList, содержащего список имен. Всякий раз, когда пользователь выбирает имя из списка, формируется событие ListSelectionEvent, которое обрабатывается методом valueChanged(), объявленным в интерфейсе ListSelectionListener. Этот метод определяет индекс выбранного элемента и отображает соответствующее имя. Окно, отображаемое на экране при выполнении данной программы, приведено на рис. 15.5.

// Демонстрация простого компонента JList.

// Для компиляции этой программы требуется JDK 7
// или более поздняя версия данного комплекта.

import javax.swing.*;
import javax.swing.event.*;
import j ava.awt.*;
import java.awt.event.*;
class ListDemo implements ListSelectionListener {

    JList<String> jlst;
    JLabel jlab;
    JScrollPane jscrip;

    // создать массив имен
    String names[] = { "Sherry", "Jon", "Rachel", // Этот массив имен
                       "Sasha", "Josselyn", "Randy", // будет отображаться
                       "Tom", "Mary", "Ken", // списком в компоненте JList.
                      "Andrew", "Matt", "Todd" };

    ListDemo() {
        // создать новый контейнер JFrame
        JFrame jfrm = new JFrame("JList Demo");

        // установить диспетчер компоновки FlowLayout
        jfrm.setLayout(new FlowLayout());

        // задать исходные размеры рамки окна
        jfrm.setSize(200, 160);

        // завершить программу после закрытия окна
        jfrm.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        // создать компонент JList
        jlst = new JList<String>(names);    // Создание списка  имен.

        // задать режим выбора элементов    из списка
        // Переход в режим выбора элементов из списка по одному.
        j1st.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);

        // добавить список на панели прокрутки.
        // Компонент списка помещается в контейнер панели прокрутки.
        jscrlp = new JScrollPane(jlst);

        // задать предпочтительные размеры панели прокрутки
        jscrlp.setPreferredSize(new Dimension(120, 90));

        // создать метку для отображения    результатов выбора  из  списка,
        jlab = new JLabel("Please choose a name");

        // добавить обработчик событий, связанных с выбором из списка
        // Прием событий, наступающих при выборе элементов из списка.
        jlst.addListSelectionListener(this);

        // добавить список и метку на панели содержимого
        jfrm.add(jscrlp);
        jfrm.add(jlab);

        // отобразить рамку окна
        jfrm.setVisible(true);
    }

    // обработать события, связанные с выбором элементов из списка
    public void valueChanged(ListSelectionEvent le) {
    // получить индекс того элемента, выбор которого был сделан
        // или отменен в списке
        int idx = j1st.getSelectedlndex();

        // отобразить результат выбора, если элемент был выбран
        if(idx != -1)
            jlab.setText("Current selection: " + names[idx]);
        else // иначе еще раз предложить сделать выбор
            jlab.setText("Please choose a name");
    }

    public static void main(String args[]) {
        // создать рамку окна в потоке диспетчеризации событий
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                new ListDemoO;
            }
        }) ;
    }
}

Рис. 15.5. Окно, отображаемое при выполнении программы ListDemo

Рассмотрим исходный код данной программы более подробно. Обратите внимание на то, что в начале программы объявляется массив names. Он инициализируется символьными строками, содержащими разные имена. В конструкторе ListDemo() массив names используется для создания компонента JList. Конструктор, которому в качестве параметра передается массив, как это имеет место в данном случае, автоматически создает экземпляр класса JList, содержащий элементы массива. Следовательно, формируемый список будет состоять из имен, хранящихся в массиве names.

Далее устанавливается режим, допускающий выбор только одного элемента из списка. Затем компонент jlst помещается в контейнер JScrollPane, а для панели прокрутки задаются предпочтительные размеры 120 * 90. Это делается ради компактности и удобства использования данного компонента. Для задания предпочтительных размеров компонента служит метод setPreferredSize(). Как правило, предпочтительные размеры определяют фактические размеры компонента, но не следует забывать, что некоторые диспетчеры компоновки могут игнорировать подобные запросы на установку размеров компонентов.

Когда пользователь выбирает элемент из списка или изменяет свой выбор, формируется связанное с этим событие. Для получения индекса выбранного элемента в обработчике подобных событий, а в данном случае в его роли выступает метод valueChanged(), вызывается метод getSelectedlndex(). И поскольку для списка был задан режим, ограничивающий выбор только одним элементом, то индекс однозначно определяет этот элемент. Затем индекс используется для обращения к массиву names и получения имени выбранного элемента. Обратите внимание на то, что в данной программе проверяется, равен ли индекс значению -1. Как упоминалось выше, это значение возвращается при условии, что ни один из элементов не выбран из списка. Нечто подобное может произойти в том случае, если событие было сформировано в результате отмены лем своего выбора. Напомним, что событие, связанное с выбором из списка, формируется, когда пользователь выбирает элемент списка или же отменяет свой выбор.

Пример для опробования 15.1.
Утилита сравнения файлов, создаваемая на основе Swing

Несмотря на то что вы ознакомились лишь с небольшой частью компонентов Swing, это не помешает вам применить свои знания на практике и создать реальное приложение средствами этой библиотеки. В примере для опробования 10.1 была создана консольная утилита сравнения файлов. А в этом проекте предстоит снабдить ее пользовательским интерфейсом, построенным из компонентов Swing. Это позволит значительно улучшить внешний вид данной утилиты и сделать ее более удобной в употреблении. Ниже показано, как выглядит рабочее окно утилиты сравнения файлов, создаваемой на основе Swing.

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

Последовательность действий

  1. Создайте файл SwingFC.java и введите приведенные ниже комментарии и операторы import.
    /*
        Пример для опробования 15-1.
    
        Утилита сравнения файлов, создаваемая на основе Swing.
    
        Для компиляции этой утилиты требуется JDK 7
        или более поздняя версия данного комплекта.
    */
    import java.awt.*;
    import java.awt.event.*;
    import javax.swing.*;
    import java.io.*;
    
    1. Создайте класс SwingFC, начав с приведенного ниже исходного кода.
    class SwingFC implements ActionListener {
    
        JTextField jtfFirst; // Переменная для хранения имени первого файла
        JTextField jtfSecond; // Переменная для хранения имени второго файла
    
        JButton jbtnComp; // Кнопка для сравнения файлов
        JLabel jlabFirst, jlabSecond; // Подсказки для пользователя
        JLabel jlabResult; // Сведения о результатах и сообщения об ошибках
    

    Имена сравниваемых файлов указываются в полях ввода текста jtfFirst и jtfSecond. Для того чтобы начать сравнение файлов, указанных в этих полях, пользователь должен щелкнуть на кнопке jbtnComp. По ходу сравнения с помощью меток j labFirst-и j labSecond должны отображаться наводящие сообщения. А результаты сравнения или сообщения об ошибках должны отображаться с помощью метки jlabResult.
    3. Создайте конструктор класса SwingFC, как показано ниже.

    SwingFCO {
    
        // создать новый контейнер JFrame
        JFrame jfrm = new JFrame("Compare Files");
    
        // установить диспетчер компоновки FlowLayout
        j frm.setLayout(new FlowLayout());
    
        // задать исходные размеры рамки окна
        jfrm.setSize (200, 190);
    
        // завершить программу после закрытия окна
        jfrm.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    
        // создать поля для ввода имен файлов
        jtfFirst = new JTextField(14);
        jtfSecond = new JTextField(14);
    
        // установить команды действия для полей ввода текста
        j tfFirst.setActionCommand("fileA");
        jtfSecond.setActionCommand("fileB");
    
        // создать кнопку Compare
        JButton jbtnComp = new JButton("Compare");
    
        // добавить приемник событий действия от кнопки Conqpare
        jbtnComp.addActionListener(this);
    
        // создать метки
        jlabFirst = new JLabel("First file: ");
        jlabSecond = new JLabel("Second file: ");
        jlabResult = new JLabel("");
    
        // добавить компоненты на панели содержимого
        jfrm.add(jlabFirst);
        jfrm.add(jtfFirst);
        jfrm.add(jlabSecond);
        jfrm.add(jtfSecond);
        jfrm.add(jbtnComp);
        jfrm.add(jlabResult);
    
        // отобразить рамку окна
        jfrm.setVisible(true);
    }
    

    Большая часть исходного кода этого конструктора должна быть вам уже знакома. Обратите внимание лишь на следующую особенность: приемник событий действия вводится только для нажимаемой кнопки jbtnCompare, а приемники событий действия для полей ввода текста не добавляются. Дело в том, что содержимое полей ввода текста требуется только в тот момент, когда нажимается кнопка Compare (Сравнить), а в остальное время в их содержимом нет особой нужды. Поэтому и нет никакого смысла предусматривать реагирование утилиты на любые события от полей ввода текста. Когда вы напишете хотя бы несколько реальных программ с использованием библиотеки Swing, вы обнаружите, что потребность в обработке событий от полей ввода текста возникает очень редко.

  2. Начните создание обработчика событий actionPerformedO так, как показано ниже. Этот метод вызывается при нажатии кнопки Compare.
    // сравнить файлы после нажатия кнопки Compare
    public void actionPerformed(ActionEvent ae) {
        int i=0, j=0;
    
        // сначала убедиться, что введены имена обоих файлов
        if(jtfFirst.getText().equals("")) {
            jlabResult.setText("First file name missing.");
            return;
        }
        if(jtfSecond.getText().equals("")) {
            jlabResult.setText("Second file name missing.");
            return;
        }
    }
    

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

  3. Завершите создание обработчика событий, введя приведенный ниже исходный код, в котором файлы сначала открываются, а затем сравниваются.
        // сравнить файлы, используя оператор try с ресурсами
        try (FilelnputStrdam fl = new FilelnputStream(jtfFirst.getText());
             FilelnputStream f2 = new FilelnputStream(jtfSecond.getText()))
        {
            // проверить содержимое каждого файла
            do {
                i = f1.read();
                j = f2.read();
                if(i != j) break;
            }   while(i != -1 &&    j   !=  -1);
            if (i != j)
                jlabResult.setText("Files are not the same.");
            else
                jlabResult.setText("Files compare equal.");
        } catch(IOException exc) {
            jlabResult.setText("File Error");
            }
    }
    
  4. И наконец, введите в класс SwingFC метод main(), как показано ниже.
    public static void main(String args[]) {
            // создать рамку окна в потоке диспетчеризации событий
            SwingUtilities.invokeLater(new Runnable() {
                public void run() {
                    new SwingFC();
                }
            });
        }
    }
    
  5. Ниже приведен весь исходный код утилиты сравнения файлов.
    /*
        Пример для опробования 15-1.
    
        Утилита сравнения файлов, создаваемая на основе Swing.
    
        Для компиляции этой утилиты требуется JDK 7
        или более поздняя версия данного комплекта.
    */
    import java.awt.*;
    import java.awt.event.*;
    import javax.swing.*;
    import java.io.*;
    
    class SwingFC implements ActionListener {
    
        JTextField jtfFirst; // Переменная для хранения имени первого файла
        JTextField jtfSecond; // Переменная для хранения имени второго файла
    
        JButton jbtnComp; // Кнопка для сравнения файлов
    
        JLabel jlabFirst, jlabSecond; // Подсказки для пользователя
        JLabel jlabResult; // Сведения о результатах и сообщения об ошибках
    
        SwingFC() {
    
            // создать новый контейнер JFrame
            JFrame jfrm = new JFrame("Compare Files");
    
            // установить диспетчер компоновки FlowLayout
            jfrm.setLayout(new FlowLayout());
    
            // задать исходные размеры рамки окна
            jfrm.setSize(200, 190);
    
            // завершить программу после закрытия окна
            j frm.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    
            // создать поля для ввода имен файлов
            jtfFirst = new JTextField(14);
            jtfSecond = new JTextField(14);
    
            // установить команды действия для полей ввода текста
            jtfFirst.setActionCommand("fileA");
            jtfSecond.setActionCommand("fileB");
    
            // создать кнопку Compare
            JButton jbtnComp = new JButton("Compare");
    
            // добавить приемник событий действия от кнопки Compare
            jbtnComp.addActionListener(this) ;
    
            // создать метки
            jlabFirst = new JLabel("First file: ");
            jlabSecond = new JLabel("Second file: ");
            jlabResult = new JLabel("");
    
            // добавить компоненты на панели содержимого
            jfrm.add(jlabFirst);
            jfrm.add(jtfFirst);
            jfrm.add(jlabSecond);
            jfrm.add(jtfSecond);
            jfrm.add(jbtnComp);
            jfrm.add(jlabResult);
    
            // отобразить рамку окна
            jfrm.setVisible(true);
        }
    
        // сравнить файлы после нажатия кнопки Compare
        public void actionPerformed(ActionEvent ae) {
            int i=0, j=0;
    
            // сначала убедиться, что введены имена обоих файлов
            if(jtfFirst.getText().equals("")) {
                jlabResult.setText("First file name missing.");
                return;
            }
            if(jtfSecond.getText().equals("") ) {
                jlabResult.setText("Second file name missing.");
                return;
            }
    
            // сравнить файлы, используя оператор try с ресурсами
            try (FilelnputStream fl = new FilelnputStream(jtfFirst.getText())
            {
                FilelnputStream f2 = new FilelnputStream(jtfSecond.getText()))
                // проверить содержимое каждого файла
                do {
                    i   = f1.read();
                    j = f2.read();
                    if(i != j) break;
                }   whiled != -1 && j !=    -1);
                if(i != j)
                    jlabResult.setText("Files are not the same.");
                else
                    jlabResult.setText("Files compare equal.");
            } catch(IOException exc) {
                jlabResult.setText("File Error");
            }
        }
    
        public static void main(String args[]) {
            // создать рамку окна в потоке диспетчеризации событий
            SwingUtilities.invokeLater(new Runnable() {
                public void run() {
                    new SwingFC();
                }
            }) ;
        }
    }
    

Применение анонимных внутренних классов для обработки событий

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

У анонимного внутреннего класса нет имени, а экземпляр такого класса получается динамически по мере необходимости. Анонимные внутренние классы позволяют значительно упростить создание обработчиков для некоторых видов событий. Допустим, имеется компонент jbtn типа JButton. Приемник событий действия от кнопки можно реализовать следующим образом:

jbtn.addActionListener(new ActionListener() {
    public void actionPerformed(ActionEvent ae) {
        // обработать событие здесь
    }
});

В данном примере используется анонимный внутренний класс, реализующий интерфейс ActionListener. Обратите особое внимание на синтаксис, используемый при создании этого класса. Тело внутреннего класса начинается после символа {, следующего за выражением new ActionListener(). Обратите также внимание на то, что вызов метода addActionListener() завершается закрывающей скобкой и точкой с запятой, т.е. как обычно. Такой синтаксис используется при создании анонимных внутренних классов, предназначенных для обработки любых событий. Очевидно, что для разнородных событий задаются разные приемники и реализуются разные методы.

Преимущество анонимного внутреннего класса заключается, в частности, в том, что компонент, вызывающий методы этого класса, заранее известен. Так, в предыдущем примере не было никакой необходимости вызывать метод getActionCommand(), чтобы выяснить, какой именно компонент сформировал событие, поскольку метод actionPerf ormed() может быть вызван в подобной реализации только при наступлении событий, сформированных компонентом jbtn. С реальным примером применения анонимных внутренних классов вы ознакомитесь в следующем разделе при создании Swing-апплета.

Создание апплета средствами Swing

Ранее в этой главе рассматривались примеры Swing-программ. Но компоненты Swing нередко применяются и для создания апплетов. Swing-апплеты похожи на апплеты, создаваемые на основе библиотеки AWT (см. главу 14), но у них имеется существенное отличие: Swing-апплет расширяет класс JApplet, производный от класса Applet, а не сам этот класс. Таким образом, подкласс JApplet наследует все функциональные возможности своего суперкласса Applet, а кроме того, в него добавлены средства поддержки библиотеки Swing. Класс JApplet служит в качестве контейнера верхнего уровня, а следовательно, он содержит различные панели, описанные в начале этой главы. Поэтому все компоненты Swing-апплета добавляются на панели содержимого контейнера JApplet таким же образом, как это делалось ранее на панели содержимого контейнера JFrame.

Срок действия Swing-апплета определяется теми же четырьмя методами, что и срок действия AWT-апплета: init(), start(), stop() и destroy() (см. главу 14). Очевидно, что переопределять необходимо только те методы, в которых нужно реализовать функциональные возможности, требующиеся для создаваемого апплета. Следует также иметь в виду, что рисование в окне выполняется в Swing- и AWT-апплетах по-разному. Именно поэтому в Swing-апплетах метод paint() обычно не переопределяется.

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

Ниже приведен пример Swing-апплета. Он выполняет те же действия, что и Swing-программа из примера, демонстрирующего нажатие кнопок ранее в этой главе, но в данном случае программа реализована в виде апплета. Для обработки событий в этом апплете используются анонимные внутренние классы. Результат выполнения этого Swing-апплета в средстве просмотра апплетов appletviewer показан на рис. 15.6.

// Простой Swing-апплет.

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;

/*
    Этот код HTML может быть использован для загрузки апплета:
    <object code="MySwingApplet" width=200 height=80>
    </object>
*/

// Swing-апплет должен расширять класс JApplet.
public class MySwingApplet extends JApplet {
    JButton jbtnUp;
    JButton jbtnDown;

    JLabel jlab;

    // инициализировать апплет
    public void init() {
        try {
            // Для создания графического пользовательского интерфейса
            // апплета используется метод invokeAndWait().
            SwingUtilities.invokeAndWait(new Runnable() {
                public void run() {
                    makeGUIO; // инициализировать графический интерфейс
                }
            });
        } catch(Exception exc) {
            System.out.println("Can't create because of "+ exc);
        }
    }

    // В этом апплете нет нужды переопределять
    // методы start(), stop() и destroy().

    // установить и инициализировать графический интерфейс
    private void makeGUIO {
        // установить диспетчер компоновки FlowLayout для апплета
        setLayout(new FlowLayout());

        // создать две кнопки
        jbtnUp = new JButton("Up");
        jbtnDown = new JButton("Down");

        // добавить приемник событий от кнопки Up
        // Для обработки событий от кнопки Up
        // используется анонимный внутренний класс.
        jbtnUp.addActionListener(new ActionListener()   {
            public void actionPerformed(ActionEvent ae) {
                jlab.setText("You pressed Up.");
            }
        });

        // добавить приемник событий от кнопки Down
        // Для обработки событий от кнопки Down
        // используется анонимный внутренний класс.
        jbtnDown.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent ae) {
                jlab.setText("You pressed down.");
            }
        });

        // добавить кнопки на панели содержимого
        add(jbtnUp);
        add(jbtnDown);

        // создать текстовую метку
        jlab = new JLabel("Press a button.");

        // добавить метку на панели содержимого
        add(jlab);
    }
}

Рис. 15.6. Результат выполнения Swing-апплета

У исходного кода этого апплета имеются некоторые особенности, заслуживающие отдельного рассмотрения. Во-первых, класс MySwingApplet является производным от класса JApplet. Как пояснялось ранее, все Swing-апплеты расширяют класс JApplet вместо класса Applet. Во-вторых, в методе init() компоненты Swing инициализируются в отдельном потоке диспетчеризации событий, где вызывается метод make GUI(). Для организации такого потока служит метод invokeAndWait(). В апплетах рекомендуется пользоваться методом init(), а не методом invokeLater(), поскольку он не должен возвращать управление до завершения инициализирующего процесса. По существу, сам метод start() не должен вызываться до конца инициализации, т.е. до тех пор, пока графический пользовательский интерфейс не будет полностью построен.

В методе makeGUI() создаются две кнопки и метка, а кроме того, с кнопками связываются приемники событий действия. Обратите внимание на то, что обработчики событий реализуются в виде анонимных внутренних классов. Этот пример реализации обработчиков событий можно использовать в качестве образца при разработке обработчиков других событий. Одно из главных преимуществ такого подхода заключается в том, что объект, вызывающий событие, заранее известен, поскольку именно в нем создается экземпляр анонимного внутреннего класса. Поэтому нет никакой необходимости получать и анализировать команду действия, чтобы выяснить, какая именно кнопка породила событие. И наконец, компоненты, предназначенные для отображения в окне апплета, добавляются на панели содержимого. Несмотря на всю простоту данного примера, он демонстрирует общий подход к созданию любого Swing-апплета с графическим пользовательским интерфейсом.

Что делать дальше

Примите поздравления! Если вы прочитали и проработали материал всех 15 глав этой книги, то можете смело считать себя программирующим на Java. Конечно, вам предстоит узнать еще немало о самом языке Java, его библиотеках и подсистемах, но вы уже владеете базовыми знаниями, чтобы на их прочном основании приобретать новые знания и опыт программирования на Java.

В дальнейшем вам, вероятнее всего, придется самостоятельно изучить следующие темы.

  • Swing. Полученных в этой книге знаний о библиотеке Swing явно недостаточно, чтобы воспользоваться в полной мере этим важным элементом Java.
  • AWT. На этой библиотеке простроена библиотека Swing.
  • Обработка событий. Эта тема рассмотрена в данной книге лишь в самых общих чертах, поэтому многие вопросы обработки событий вам еще предстоит изучить самостоятельно.
  • Сетевые классы Java. Служат для организации взаимодействия в сети.
  • Служебные классы Java, в особенности из библиотеки коллекций Collections Framework. Упрощают решение очень многих задач программирования.
  • Прикладной интерфейс Concurrent API. Обеспечивает доскональный контроль над высокопроизводительными многопоточными приложениями.
  • JavaBeans. Инструментальное средство, предназначенное для создания программных компонентов на Java.
  • Создание собственных методов.
  • Сервлеты. Если вам придется участвовать в создании сложных веб-приложений, то вы вряд ли сможете обойтись без знаний и навыков разработки сервлетов. Сервлеты выполняют те же функции на стороне сервера, что и апплеты на стороне клиента, в роли которого чаще всего выступает браузер.

Для дальнейшего изучения Java рекомендуется книга Полный справочник по Java, 8-е издание, ИД “Вильямс”, 2012 г.

В ней вы найдете подробные сведения о языке программирования Java и его основных библиотеках, а также сотни примеров программ, демонстрирующих языковые средства Java.

Упражнения для самопроверки по материалу главы 15

  1. Компоненты AWT являются тяжеловесными, а компоненты Swing — ____________.
  2. Может ли изменяться стиль оформления компонента Swing? Если да, то какое средство позволяет это сделать?
  3. Какой контейнер верхнего уровня чаще всего используется в приложениях?
  4. Контейнер верхнего уровня содержит несколько панелей. На какой панели размещаются компоненты?
  5. Как создать ссылку, отображающую сообщение «Select an entry from the list» (Выберите элемент из списка)?
  6. В каком потоке должно происходить все взаимодействие с компонентами графиче¬ского пользовательского интерфейса?
  7. Какая команда действия связывается по умолчанию с компонентом JButton? Как изменить команду действия?
  8. Какое событие формируется при нажатии кнопки?
  9. Как создать поле ввода текста шириной до 32 символов?
  10. Можно ли установить команду действия для компонента JTextField? Если можно, то как это сделать?
  11. С помощью какого компонента Swing можно создать флажок? Какое событие формируется при установке или сбросе флажка?
  12. Компонент JList отображает список элементов, которые может выбирать пользователь. Верно или неверно?
  13. Какое событие формируется при выборе пользователем элемента из списка типа JList или отмене выбора?
  14. В каком методе задается режим выбора элементов списка типа JList? С помощью какого метода можно получить индекс первого выбранного элемента?
  15. Подкласс какого класса нужно создать при разработке Swing-апплета?
  16. Обычно при построении исходного пользовательского интерфейса в Swing-апплетах используется метод invokeAndWait(). Верно или неверно?
  17. Добавьте в утилиту сравнения файлов, созданную в примере для опробования 15.1, флажок со следующей пояснительной надписью: Show position of mismatch (Показывать место несовпадения). Если этот флажок установлен, программа должна отображать место, в котором обнаружено первое расхождение в содержимом сравниваемых файлов.
  18. Измените программу ListDemo таким образом, чтобы она допускала выбор нескольких элементов из списка.
  19. Дополнительное задание. Преобразуйте класс Help, созданный в примере для опробования 4.1, в Swing-программу с графическим пользовательским интерфейсом. Сведения о ключевых словах (for, while, switch и т.д.) должны отображаться с помощью компонента JList. При выборе пользователем элемента из списка должно выводиться описание синтаксиса выбранного ключевого слова. Для отображения многострочного текста на месте метки можно воспользоваться средствами HTML. В этом случае текст должен начинаться с дескриптора <html> и завершаться дескриптором </html>. В итоге текст будет автоматически размечен в виде HTML-документа. Помимо прочих преимуществ, такая разметка текста позволяет создавать многострочные метки. В качестве примера ниже приведена строка кода, в которой создается метка, отображающая две текстовые строки: первой выводится строка «Тор» (Верх), а под ней — вторая строка «Bottom» (Низ).
    JLabel jlabhtml = new JLabel("<html>Top<br>Bottom</html>");
    

    На этот вопрос ответа не дается. Ведь вы достигли такого уровня, который позволяет самостоятельно разрабатывать программы на Java!

  20. Продолжайте изучать Java. Желательно ознакомиться с возможностями стандартных пакетов, например java.lang, java.util и java.net. Пишите небольшие программы, демонстрирующие применение различных классов и интерфейсов. Наилучший способ достичь мастерства в программировании на Java — написать как можно больше разных программ.

Java Swing – это легкий инструментарий графического интерфейса пользователя (GUI), который включает в себя богатый набор виджетов. Он включает в себя пакет, позволяющий создавать компоненты графического интерфейса для приложений Java, и не зависит от платформы.

Библиотека Swing построена на основе Java Abstract Widget Toolkit (AWT), более старого, зависимого от платформы инструментария GUI. Вы можете использовать компоненты библиотеки Java, такие как кнопка, текстовое поле и т. д. из библиотеки, и вам не нужно создавать компоненты с нуля.

Создание графических компонентов с помощью Java Swing Tutorial

Все компоненты в Swing являются JComponent, который может быть добавлен к классам контейнера.

Что такое контейнерный класс?

Контейнерные классы – это классы, в которых могут быть другие компоненты. Поэтому для создания графического интерфейса нам нужен как минимум один контейнерный объект. Есть 3 типа контейнеров.

  1. Панель: это чистый контейнер, а не окно само по себе. Единственная цель панели состоит в том, чтобы организовать компоненты на окне.
  2. Рамка: это полностью функционирующее окно с его заголовком и значками.
  3. Диалог: его можно рассматривать как всплывающее окно, которое появляется, когда сообщение должно быть отображено. Это не полностью функционирующее окно, как Рамка.
import javax.swing.*;
class gui{
    public static void main(String args[]){
       JFrame frame = new JFrame("My First GUI");
       frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
       frame.setSize(300,300);
       JButton button = new JButton("Press");
       frame.getContentPane().add(button); // Adds Button to content pane of frame
       frame.setVisible(true);
    }
}

Пример: Чтобы научиться проектировать GUI в Java.

Шаг 1) Скопируйте следующий код в редактор

import javax.swing.*;
   class gui{
      public static void main(String args[]){
        JFrame frame = new JFrame("My First GUI");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setSize(300,300);
       JButton button1 = new JButton("Press");
       frame.getContentPane().add(button1);
       frame.setVisible(true);
     }
}

Шаг 2) Сохраните, скомпилируйте и запустите код.

Шаг 3) Теперь давайте добавим кнопку в наш фрейм. Скопируйте следующий код в редактор

import javax.swing.*;
class gui{
      public static void main(String args[]){
           JFrame frame = new JFrame("My First GUI");
           frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
           frame.setSize(300,300);
          JButton button1 = new JButton("Button 1");
          JButton button2 = new JButton("Button 2");
          frame.getContentPane().add(button1);
          frame.getContentPane().add(button2);
          frame.setVisible(true);
     }
}

Шаг 4) Выполнив код, вы получите большую кнопку.
создание кнопки

Шаг 5) Как насчет добавления двух кнопок? Скопируйте следующий код в редактор.

import javax.swing.*;
class gui{
      public static void main(String args[]){
           JFrame frame = new JFrame("My First GUI");
           frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
           frame.setSize(300,300);
          JButton button1 = new JButton("Button 1");
          JButton button2 = new JButton("Button 2");
          frame.getContentPane().add(button1);
          frame.getContentPane().add(button2);
          frame.setVisible(true);
     }
}

Шаг 6) Сохраните, скомпилируйте и запустите программу.

Шаг 7) Неожиданный вывод =? Кнопки перекрываются.

Менеджер макетов используется для компоновки (или упорядочивания) компонентов JAVA GUI внутри контейнера. Существует много менеджеров макетов, но наиболее часто используемые из них-

Java BorderLayout – BorderLayout помещает компоненты в пять областей: верхнюю, нижнюю, левую, правую и центральную. Это менеджер макетов по умолчанию для каждого Java JFrame.

BorderLayout помещает компоненты в пять областей

Java FlowLayout. FlowLayout-это менеджер макетов по умолчанию для каждой JPanel. Он просто раскладывает компоненты в один ряд один за другим.

FlowLayout-это менеджер макетов по умолчанию

Java GridBagLayout – это самый сложный из всех макетов. Он выравнивает компоненты, помещая их в сетку ячеек, позволяя компонентам охватывать более одной ячейки.

Java GridBagLayout

Шаг 8) Как насчет создания фрейма чата, как показано ниже?

фрейм чата

Попробуйте написать код, прежде чем смотреть на программу ниже.

//Usually you will require both swing and awt packages
// even if you are working with just swings.
import javax.swing.*;
import java.awt.*;
class gui {
    public static void main(String args[]) {

        //Creating the Frame
        JFrame frame = new JFrame("Chat Frame");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setSize(400, 400);

        //Creating the MenuBar and adding components
        JMenuBar mb = new JMenuBar();
        JMenu m1 = new JMenu("FILE");
        JMenu m2 = new JMenu("Help");
        mb.add(m1);
        mb.add(m2);
        JMenuItem m11 = new JMenuItem("Open");
        JMenuItem m22 = new JMenuItem("Save as");
        m1.add(m11);
        m1.add(m22);

        //Creating the panel at bottom and adding components
        JPanel panel = new JPanel(); // the panel is not visible in output
        JLabel label = new JLabel("Enter Text");
        JTextField tf = new JTextField(10); // accepts upto 10 characters
        JButton send = new JButton("Send");
        JButton reset = new JButton("Reset");
        panel.add(label); // Components Added using Flow Layout
        panel.add(tf);
        panel.add(send);
        panel.add(reset);

        // Text Area at the Center
        JTextArea ta = new JTextArea();

        //Adding Components to the frame.
        frame.getContentPane().add(BorderLayout.SOUTH, panel);
        frame.getContentPane().add(BorderLayout.NORTH, mb);
        frame.getContentPane().add(BorderLayout.CENTER, ta);
        frame.setVisible(true);
    }
}


Swing

tutorial
java


  • A Brief History
  • JFrame
  • Components

    • JButton
    • JLabel
  • JTextArea

    • Other Components
  • JPanel
  • Layout Managers
  • Nesting Layouts
  • Event Listeners
  • Custom Painting
  • Timers
  • Other Resources
  • Homework

So far, all of our Java programs have been command line programs, which we interacted with through the command prompt, if we interacted with the program at all.

Command line programs are fine for simple tasks or for programs that don’t really interact with the user, but if you want to interact with the user, then you probably want to create a graphical user interface, or GUI (pronounced “gee-you-eye” or “gooey”). GUIs are the types of programs you’re probably more accustomed to using: the types of programs that have windows and buttons and text fields instead of only using the command prompt.

Java contains a bunch of classes that help you create GUIs, and this tutorial focuses on a set of classes that make up a library called Swing.

Note: Swing is not an acronym! (Neither is Java, for that matter.)

A Brief History

Java contains three different GUI libraries, which are each just a bunch of classes in the Java API that you can use. Trying to understand how all of these classes fit together can be confusing, so here’s a brief summary of how they work:

  • The Abstract Window Toolkit, or AWT, has been a part of Java since day one, way back in 1996. AWT works by passing in “native” calls to your computer. So if you create a checkbox in AWT, you’re really telling your operating system to create a checkbox. All of the AWT classes are in the java.awt package.

  • The downside of that approach is that your program will look different on different computers: a checkbox on Linux looks different from a checkbox on Windows. This can make it harder to layout your program or have a consistent experience across different platforms. To fix this, Swing was added to Java in 1998. The idea behind Swing is that instead of telling your computer to create a checkbox, Swing draws the checkbox itself. That way, the checkbox will look the same on different operating systems. The Swing classes are in the javax.swing package. But Swing was built on top of AWT, so you’ll see Swing code using classes from the java.awt package as well.

  • JavaFX was originally developed as an external library in 2008, and it was included in Java in 2014. JavaFX focuses on modern GUI features, like more animations, CSS styling, and using a computer’s graphics card to handle the rendering. JavaFX classes are in the javafx package, which is in a .jar file that comes with Java.

Even though JavaFX is newer, I’m focusing on Swing for a couple reasons:

  • The benefit of Swing being around longer is that there are a ton of resources for learning more about it. If you’re wondering how to do something in Swing, chances are somebody has asked your question on Stack Overflow.

  • I think Swing is a great way to become more comfortable with OOP, inheritance, and general program flow. So even if your end goal with programming isn’t Swing, it’s a good idea to spend some time here because it’ll help you learn other stuff you need to be learning.

  • I know Swing better than I know JavaFX. :smile_cat:

One more thing worth noting: the above libraries, including Swing, are for creating a desktop application, which is a program that runs on your computer, not in a webpage. Think of opening up the Spotify application, not going to Spotify’s website. But like I said above, even if your end goal isn’t creating a desktop application in Java, it’s still a good idea to learn this stuff since it teaches you other stuff you need to know anyway.

Enough history, to the tutorial!

JFrame

The first step to creating a GUI is displaying a window, so you can interact with that instead of the command prompt. The JFrame class is Swing’s representation of a window, and it works like this:

import javax.swing.JFrame;

public class MyGui{

	public static void main(String[] args){
		JFrame frame = new JFrame("Happy Coding");
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		frame.setSize(300, 300);
		frame.setVisible(true);
	}
}

Let’s take it one line of code at a time:

JFrame frame = new JFrame("Happy Coding");

This line of code creates an instance of the JFrame class, passing in a parameter that sets the title of the window.

frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

This makes it so when you click the X button in the window, your program exits. If you don’t do this, your program could continue running in the background even after the window closes, which you probably don’t want.

This line of code sets the width and height of the window in pixels. Try passing in different values to change the size of the window.

Finally, this actually pops the window up and shows it to the user. Notice that the program continues running even after our main() function ends!

Usually you wouldn’t put all of your code in the main() method like that, except for very simple programs like this one. You would probably do something like this:

import javax.swing.JFrame;

public class MyGui{

	public MyGui(){
		JFrame frame = new JFrame("Happy Coding");
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		frame.setSize(300, 300);
		frame.setVisible(true);
	}
	
	public static void main(String[] args){
		new MyGui();
	}
}

This code moves the logic from the main() function into the class, and the main() function simply creates an instance of that class. This isn’t much different from putting it all in the main() method, but this will make more sense as your code gets more complicated.

You might also split your code up like this:

import javax.swing.JFrame;

public class MyGui{
	
	private JFrame frame;

	public MyGui(){
		frame = new JFrame("Happy Coding");
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		frame.setSize(300, 300);
	}
	
	public void show(){
		frame.setVisible(true);	
	}
	
	public static void main(String[] args){
		MyGui myGui = new MyGui();
		myGui.show();
	}
}

This code does the same thing, but it splits the code up: it initializes the frame variable in the constructor, and it shows it in the show() function.

In any case, you would save this to a file named MyGui.java and then you’d compile and run it from the command line, just like the other programs we’ve seen so far. (Yeah, you still have to compile and run using the command line. We’ll talk about how to create an executable file in a later tutorial!)

The code causes a window to show:

blank JFrame

Our window is blank, because we haven’t added anything to it yet.

Components

In addition to the JFrame class, Swing has a bunch of classes that represent different components you can add to a window: stuff like buttons, labels, checkboxes, and text areas. To use a component, you call frame.add(component); to add it to the window. We’ll see some examples below.

As always, your best friend is the Java API, but here are a few of the most commonly used classes:

JButton

The JButton class is Swing’s representation of a clickable button. Let’s add it to our GUI:

import javax.swing.JFrame;
import javax.swing.JButton;

public class MyGui{

	public static void main(String[] args){
		JFrame frame = new JFrame("Happy Coding");
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		
		JButton button = new JButton("Click me!");
		frame.add(button);
		
		frame.setSize(300, 300);	
		frame.setVisible(true);
	}
}

First we construct a JFrame and make it so the program ends when we close it. Then we create an instance of the JButton class, passing in an argument that sets the text of the button. Then we add the button the the window. Finally, we set the size of the window and show it.

window with button

Right now the button just takes up the whole window, and nothing happens when you click it. We’ll fix that in a second.

JLabel

The JLabel class is Swing’s representation of an undeditable text label. Let’s add it to our GUI:

import javax.swing.JFrame;
import javax.swing.JLabel;

public class MyGui{

	public static void main(String[] args){
		JFrame frame = new JFrame("Happy Coding");
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		
		JLabel label = new JLabel("Hello world!");
		frame.add(label);
		
		frame.setSize(300, 300);	
		frame.setVisible(true);
	}
}

This is almost the same as the JButton code, except now we’re creating an instance of JLabel and adding it to the JFrame.

window with label

Right now the label is left-aligned and uses the default font. We can fix that by calling functions on our JLabel instance:

import javax.swing.JFrame;
import javax.swing.JLabel;
import java.awt.Font;

public class MyGui{

	public static void main(String[] args){
		JFrame frame = new JFrame("Happy Coding");
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		
		JLabel label = new JLabel("Hello world!");
		label.setFont(new Font("Serif", Font.BOLD, 36));
		label.setHorizontalAlignment(JLabel.CENTER);
		frame.add(label);
		
		frame.setSize(300, 300);	
		frame.setVisible(true);
	}
}

Now our JLabel has a large bold serif font, which it displays using a center alignment.

window with customized label

You can find other ways to customize your components by looking them up in the Java API! Try changing the text color of the JLabel.

JTextArea

The JTextArea class is Swing’s representation of a box of text that the user can edit.

import javax.swing.JFrame;
import javax.swing.JTextArea;

public class MyGui{

	public static void main(String[] args){
		JFrame frame = new JFrame("Happy Coding");
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		
		JTextArea textArea = new JTextArea("It was the best of times, it was the worst of times...");
		frame.add(textArea);
		
		frame.setSize(300, 300);	
		frame.setVisible(true);
	}
}

This gives us a box of text that the user can type into:

window with text area

Again, you can customize this component using functions you find in the Java API. Try changing the font!

Other Components

This tutorial isn’t meant to show you every single thing you can do with Swing. It’s meant to show you the basics so you can use the Java API to figure out exactly how to do what you want. Here are just a few other components you should check out:

  • JCheckBox and JRadioButton represent checkboxes and radio buttons.
  • JTextField gives you a one-line text area.
  • JComboBox and JList allow the user to select items from lists.
  • JMenu lets you add menus to your window.
  • JProgressBar shows a progress bar.
  • JSlider and JSpinner let the user adjust a value.
  • JTable shows a table of data.
  • JScrollPane adds scroll bars to content that’s too large to fit in the window.

To learn how to use these components, look them up in the Java API and read about the constructors, functions, and variables they contain. Then put together a little example program that tests the component out before integrating it into your main project.

JPanel

So far, we’ve only added a single component to our window. That’s not very exciting, but we can use the JPanel class to add multiple components to a window. JPanel is a component that holds other components. To use a JPanel, you’d follow this basic flow:

  • Create an instance of JPanel.
  • Add components to the JPanel instance.
  • Add that JPanel to the JFrame.

It looks like this:

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JTextArea;

public class MyGui{

	public static void main(String[] args){
		JFrame frame = new JFrame("Happy Coding");
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		
		JPanel panel = new JPanel();
		
		JButton buttonOne = new JButton("I'm a JButton!");
		panel.add(buttonOne);
		
		JLabel label = new JLabel("I'm a JLabel!");
		panel.add(label);
		
		JTextArea textArea = new JTextArea("I'm a JTextArea!");
		panel.add(textArea);
		
		JButton buttonTwo = new JButton("I'm another JButton!");
		panel.add(buttonTwo);
		
		frame.add(panel);
		
		frame.setSize(300, 300);	
		frame.setVisible(true);
	}
}

This code creates a JFrame, then creates a JPanel instance. It adds four different components to the JPanel, and then it adds that JPanel to the JFrame. Finally, it sets the size of the JFrame and shows it.

window with multiple components

By default, the components are displayed one after the other, and they wrap to multiple rows if the window is not wide enough to fit them. Try changing the width of the window to see the components rearrange themselves.

Layout Managers

You probably don’t want your components to be display like that though. You can change how they’re arranged using layout managers, which are classes that tell a JPanel how to arrange components.

To use a layout manager, you first create an instance of the layout manager you want to use, and then you pass it into the setLayout() function of your JPanel. It looks like this:

JPanel panel = new JPanel();
BorderLayout borderLayoutManager = new BorderLayout();
panel.setLayout(borderLayoutManager);

Of course, you can also pass the instance directly into the function instead of storing it in a variable first:

JPanel panel = new JPanel();
panel.setLayout(new BorderLayout());

And the JPanel constructor can take a layout manager as an argument:

JPanel panel = new JPanel(new BorderLayout());

In any case, this code uses a BorderLayout layout manager, which splits the JPanel up into 5 different areas: the top of the JPanel is NORTH, the bottom is SOUTH, the left is WEST, the right is EAST, and the center is, well, CENTER. You can pass these values (which are static variables in the BorderLayout class) into the add() function along with the component to arrange them. Putting it all together, it looks like this:

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JTextArea;
import java.awt.BorderLayout;

public class MyGui{

	public static void main(String[] args){
		JFrame frame = new JFrame("Happy Coding");
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		
		JPanel panel = new JPanel();
		BorderLayout borderLayoutManager = new BorderLayout();
		panel.setLayout(borderLayoutManager);
		
		JButton buttonOne = new JButton("I'm a JButton!");
		panel.add(buttonOne, BorderLayout.NORTH);
		
		JLabel label = new JLabel("I'm a JLabel!");
		panel.add(label, BorderLayout.SOUTH);
		
		JTextArea textArea = new JTextArea("I'm a JTextArea!");
		panel.add(textArea, BorderLayout.EAST);
		
		JButton buttonTwo = new JButton("I'm another JButton!");
		panel.add(buttonTwo, BorderLayout.WEST);
				
		frame.add(panel);
		
		frame.setSize(300, 300);	
		frame.setVisible(true);
	}
}

This code uses a BorderLayout layout manager, and adds each component to a different section of the JPanel.

border layout

Notice that the components stay in their positions, even when you resize the window. Also notice that the size of each component is set by the layout.

There are a bunch of other layout managers, and this tutorial is a great place to learn more about them. Here’s an example that uses BoxLayout:

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JTextArea;
import javax.swing.BoxLayout;

public class MyGui{

	public static void main(String[] args){
		JFrame frame = new JFrame("Happy Coding");
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		
		JPanel panel = new JPanel();
		BoxLayout boxLayoutManager = new BoxLayout(panel, BoxLayout.Y_AXIS);
		panel.setLayout(boxLayoutManager);
		
		JButton buttonOne = new JButton("I'm a JButton!");
		panel.add(buttonOne);
		
		JLabel label = new JLabel("I'm a JLabel!");
		panel.add(label);
		
		JTextArea textArea = new JTextArea("I'm a JTextArea!");
		panel.add(textArea);
		
		JButton buttonTwo = new JButton("I'm another JButton!");
		panel.add(buttonTwo);
				
		frame.add(panel);
		
		frame.setSize(300, 300);	
		frame.setVisible(true);
	}
}

This code sets a BoxLayout layout manager, which arranges the components in a single line, either horizontally or vertically depending on the value you pass into the BoxLayout constructor. Try changing the parameter to BoxLayout.X_AXIS to see what happens!

box layout

Again, notice that the size of the components depends on the layout you use! This can be a little confusing, but it helps make sure your components do reasonable things when the user resizes the window.

Nesting Layouts

Remember that we can add components to JPanel instances, and JPanel is itself a component. That means we can add components to a JPanel with one layout manager, and then add that JPanel to another JPanel with a different layout manager! This is called nesting layouts, and it lets us bundle up multiple components and treat them as a single block in the overall layout.

If that sounds confusing, think about it this way: we can create a JPanel with a vertical BoxLayout that contains 5 JButton instances. Then we can create another JPanel with a BorderLayout layout manager, and we can add the first JPanel to the WEST section of the second JPanel!

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.BoxLayout;
import java.awt.BorderLayout;

public class MyGui{

	public static void main(String[] args){
		JFrame frame = new JFrame("Happy Coding");
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		
		JPanel leftPanel = new JPanel();
		BoxLayout leftBoxLayoutManager = new BoxLayout(leftPanel, BoxLayout.Y_AXIS);
		leftPanel.setLayout(leftBoxLayoutManager);
		
		leftPanel.add(new JButton("JButton One"));
		leftPanel.add(new JButton("JButton Two"));
		leftPanel.add(new JButton("JButton Three"));
		leftPanel.add(new JButton("JButton Four"));
		leftPanel.add(new JButton("JButton Five"));
		
		JPanel rightPanel = new JPanel();
		BoxLayout rightBoxLayoutManager = new BoxLayout(rightPanel, BoxLayout.Y_AXIS);
		rightPanel.setLayout(rightBoxLayoutManager);
		
		rightPanel.add(new JLabel("JLabel One"));
		rightPanel.add(new JLabel("JLabel Two"));
		rightPanel.add(new JLabel("JLabel Three"));
		rightPanel.add(new JLabel("JLabel Four"));
		rightPanel.add(new JLabel("JLabel Five"));
		

		JPanel mainPanel = new JPanel(new BorderLayout());
		mainPanel.add(leftPanel, BorderLayout.WEST);
		mainPanel.add(rightPanel, BorderLayout.EAST);
		
		frame.add(mainPanel);
		
		frame.setSize(300, 300);	
		frame.setVisible(true);
	}
}

This code creates a JPanel with a vertical BoxLayout, then adds five JButton components to it. Then it creates another JPanel with another vertical BoxLayout, and it adds five JLabel components to it. Then it creates a third JPanel with a BorderLayout layout manager, and it adds the first two JPanel components to it.

In other words, it treats each set of components as a block that it lays out in the main window layout.

nested layouts

There isn’t really a limit to how much nesting you can have!

Event Listeners

So far, our components haven’t done anything other than display. Nothing happens when we click a button, for example. We can change that by adding event listeners to our components.

Event listeners are objects that define functions that are called when a certain event happens: when the user clicks the mouse or presses a key on the keyboard, for example.

For example, let’s create an ActionListener, which lets us trigger a function when the user clicks a button. ActionListener is an interface, so the first step is to create a class that implements that interface:

import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;

public class SimpleActionListener implements ActionListener{

	public void actionPerformed(ActionEvent event){
		System.out.println("Clicked!");
	}
}

The ActionListener interface requires a single function named actionPerformed(), and our SimpleActionListener class implements the interface by defining that function.

Now that we have a class that implements ActionListener, we can create an instance of this class and add it to a JButton:

import javax.swing.JFrame;
import javax.swing.JButton;

public class MyGui{

	public static void main(String[] args){
		JFrame frame = new JFrame("Happy Coding");
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		
		JButton button = new JButton("Click me!");
		frame.add(button);
		
		SimpleActionListener listener = new SimpleActionListener();
		button.addActionListener(listener);
		
		frame.setSize(300, 300);	
		frame.setVisible(true);
	}
}

This code creates a JFrame and adds a JButton to it. Then it creates an instance of our SimpleActionListener class and passes it into the addActionListener() function of our JButton instance. Then the code sets the size of the window and displays it.

Now, when we click the button, the actionPerformed() function inside our SimpleActionListener class is called, and "Clicked!" is printed to the console.

We probably want to do something a little more involved than just printing something to the console when the user clicks the button, though. Let’s say we want to change the text on the button to show how many times the user has clicked. To do that, we need a reference to the JButton instance inside our SimpleActionListener class.

One way to get that reference is through the ActionEvent instance passed in as a parameter to the actionEvent() function. That class contains a getSource() function, which returns the object that generated the event: in our case, this is our JButton instance! But the reference is an Object reference, so you have to cast it to JButton to use functions from the JButton class.

That might sound confusing, but it looks like this:

import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
import javax.swing.JButton;

public class SimpleActionListener implements ActionListener{
	
	private int clicks = 0;

	public void actionPerformed(ActionEvent event){
		clicks++;
		
		JButton clickedButton = (JButton) event.getSource();
		clickedButton.setText("Clicks: " + clicks);
	}
}

Now our class contains a clicks variable that keeps track of how many times the user has clicked. In the actionPerformed() function, that variable is incremented. Then the code calls the event.getSource() function, casts the returned reference to JButton and stores it in the clickedButton variable. Finally, the code calls the setText() function of that JButton to display the click count. And since the clickedButton variable points to the same JButton instance that we’ve added to our JFrame, our displayed button’s text is updated.

clicking button

But what if we wanted to update a different component that wasn’t the source of the event? For example, what if we want to update a JLabel whenever we click a JButton? To do that, we have to pass a reference to the JLabel ourselves. We could use a setter function, or we could use a constructor that took the JLabel as an argument:

import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
import javax.swing.JLabel;

public class SimpleActionListener implements ActionListener{
	
	private int clicks = 0;
	private JLabel label;
	
	public SimpleActionListener(JLabel label){
		this.label = label;
	}

	public void actionPerformed(ActionEvent event){
		clicks++;
		
		label.setText("Clicks: " + clicks);
	}
}

Now our SimpleActionListener class takes a JLabel argument in its constructor, and it updates the text of that JLabel whenever the actionPerformed() function is called.

Back in our main() function, we have to create a JLabel instance, add it to the window, and pass it into the constructor of our SimpleActionListener class. Putting it all together, it looks like this:

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JButton;
import javax.swing.JLabel;

public class MyGui{

	public static void main(String[] args){
		JFrame frame = new JFrame("Happy Coding");
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		
		JPanel panel = new JPanel();
		
		JButton button = new JButton("Click me!");
		panel.add(button);
		
		JLabel label = new JLabel("Clicks: 0");
		panel.add(label);
		
		SimpleActionListener listener = new SimpleActionListener(label);
		button.addActionListener(listener);
		
		frame.add(panel);
		
		frame.setSize(300, 300);	
		frame.setVisible(true);
	}
}

This code creates a JFrame and a JPanel, and then it creates a JButton and a JLabel and adds both of them to the JPanel. The code then passes the JLabel into the SimpleActionListener constructor, and it adds that listener to the JButton. Then it adds the JPanel to the JFrame and shows the window.

Now when the user clicks the button, the text on the JLabel is updated.

clicking button

The above examples use a named, top-level class that implements the ActionListener interface, but rememeber that you can also implement an interface using inner classes and anonymous classes. This lets us include our listener code with our GUI code, like this:

ActionListener listener = new ActionListener(){
	int clicks = 0;
	public void actionPerformed(ActionEvent event){
		clicks++;

		label.setText("Clicks: " + clicks);
	}
};
button.addActionListener(listener);

Now instead of using a separate SimpleActionListener class, we’re using an anonymous class that implements the ActionListener interface. The logic is the same, but note that we’re no longer passing the JLabel into a constructor. Because this is an anonymous inner class, it has access to the variables in its enclosing scope (in this case, the main() function). That means it can reference the label variable directly.

You can also shorten that into a single statement:

button.addActionListener(new ActionListener(){
	int clicks = 0;
	public void actionPerformed(ActionEvent event){
		clicks++;

		label.setText("Clicks: " + clicks);
	}
});

Now instead of storing the instance of our anonymous class in a variable, we pass it directly into the addActionListener() function.

Anonymous classes are often used when specifying listeners, which lets us keep logic related to a single component together, and avoids creating a bunch of classes we only use once.

There are a bunch of different kinds of event listeners, and you can add multiple listeners to the same component. You can also add listeners to the overall JFrame window. Here’s an example that adds a MouseListener and a KeyListener to the JFrame:

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JButton;
import javax.swing.JLabel;
import java.awt.event.MouseListener;
import java.awt.event.MouseEvent;
import java.awt.event.KeyListener;
import java.awt.event.KeyEvent;

public class MyGui{

	public static void main(String[] args){
		JFrame frame = new JFrame("Happy Coding");
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		
		JLabel label = new JLabel();
		
		frame.addMouseListener(new MouseListener(){
			public void mouseClicked(MouseEvent me){
				label.setText("Mouse clicked. (" + me.getX() + ", " + me.getY() + ")");
			}
			public void mouseEntered(MouseEvent me){
				label.setText("Mouse entered. (" + me.getX() + ", " + me.getY() + ")");
			}
			public void mouseExited(MouseEvent me){
				label.setText("Mouse exited. (" + me.getX() + ", " + me.getY() + ")");
			}
			public void mousePressed(MouseEvent me){
				label.setText("Mouse pressed. (" + me.getX() + ", " + me.getY() + ")");
			}
			public void mouseReleased(MouseEvent me){
				label.setText("Mouse released. (" + me.getX() + ", " + me.getY() + ")");
			}
		});
		
		frame.addKeyListener(new KeyListener(){
			public void keyPressed(KeyEvent ke){
				label.setText("Key pressed. (" + ke.getKeyChar() + ")");
			}
				
			public void keyReleased(KeyEvent ke){
				label.setText("Key released. (" + ke.getKeyChar() + ")");
			}
	
			public void keyTyped(KeyEvent ke){
				label.setText("Key typed. (" + ke.getKeyChar() + ")");
			}
		});
		
		frame.add(label);
		
		frame.setSize(300, 300);	
		frame.setVisible(true);
	}
}

This might seem like a lot, but this code is only really doing a few things. First it creates a JFrame and a JLabel, and then it adds a MouseListener to that JFrame. The MouseListener interface requires five functions: mouseClicked(), mouseEntered(), mouseExited(), mousePressed(), and mouseReleased(). In each of those functions, the text of the JLabel is set based on the values of the getX() and getY() functions, which return the position of the cursor.

Next, a KeyListener is added to the JFrame. The KeyListener interface requires three functions: keyPressed(), keyReleased(), and keyTyped(). In each of those functions, the text of the JLabel is set based on the value of the getKeyChar() function, which returns the key the user is hitting.

Then the JLabel is added to the JFrame, and the JFrame is displayed.

The result is a window that displays information about the the events generated by the mouse and keyboard:

event info

There are a bunch of other event listeners, and you should check out the Java API and this tutorial to learn more about them.

Custom Painting

So far, we’ve learned how to create a GUI using the components that come with Swing. We can customize these components by specifying their layout, font size, color, border, etc. But if we want to do our own drawing, we have to create our own component that performs custom painting.

Looking at the Java API, we can see that Swing components inherit a function named paintComponent() from the JComponent class. Basically, the paintComponent() function draws the stuff inside the component.

So, to create a custom component, we can extend a component class and override the paintComponent() function. Then we can put our drawing code inside the paintComponent() function. The JPanel class gives us a reasonable starting point since it’s just a blank component, so let’s extend that.

That sounds confusing, but it looks like this:

import javax.swing.JPanel;
import java.awt.Graphics;

public class CustomComponent extends JPanel{

	@Override
	public void paintComponent(Graphics g){
		super.paintComponent(g);
		g.drawOval(10, 10, 200, 200);
	}
}

This code defines a class that extends the JPanel class and overrides the paintComponent() function, which takes a Graphics instance as an argument. The code then calls the super class’s paintComponent() function, which handles stuff like drawing the background of the component. Then the code calls the drawOval() function, which draws an oval on the component.

And because the class extends the JPanel class, we can use it just like any other component! We can add it to another JPanel or a JFrame to display it:

import javax.swing.JFrame;

public class MyGui{

	public static void main(String[] args){
		JFrame frame = new JFrame("Happy Coding");
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		
		CustomComponent customComponent = new CustomComponent();
		frame.add(customComponent);
		
		frame.setSize(300, 300);	
		frame.setVisible(true);
	}
}

This code creates an instance of the CustomComponent class and adds it to a JFrame. This displays our component in a window, and we can see the circle we’re drawing:

custom component

In Swing, drawing is done through the Grahpics instance passed into the paintComponent() function. You should check out the Java API to read about all the different functions you can call, and they should look pretty similar to Processing’s drawing functions. That’s because Processing is built on top of Java! In fact, Processing’s drawing functions actually end up calling Swing’s Graphics functions.

However, there are a few differences between Processing’s drawing and Swing’s drawing:

  • The paintComponent() function is NOT automatically called 60 times per second. In fact, you don’t really have total control over when it’s called! It can be called when the window is resized, when you move the window, when other windows are moved, or whenever Swing feels like it. You should not rely on paintComponent() for timing!

  • Similarly, you should not put any “business logic” inside your paintComponent() function. That means stuff like user input should be handled using event listeners, and animation should be handled outside of the paintComponent() function. We’ll get to that in a second.

  • The Graphics class contains very similar functions to Processing. Processing’s ellipse() function is the drawOval() function in Swing, rect() is drawRect(), etc. There are a few differences though. For example, Processing uses a stroke color to draw both the shape’s outline and a fill color to draw its inner area. Swing splits that up into two functions, so to draw a circle with an outline you would first call fillEllipse() and then drawEllipse() to draw an outline around it. Similarly, both Processing and Swing use a coordinate system where 0,0 is in the upper-left corner, but some of Swing’s individual functions are a little different: the drawOval() function takes the upper-left corner of the circle, not the center.

  • Remember that custom painting happens in a class that extends JPanel, so we have access to the functions defined by that class (and all of its super classes). For example, the getWidth() and getHeight() functions return the width and height of the component.

Here’s an example that shows some of what I’m talking about:

import javax.swing.JPanel;
import java.awt.Graphics;
import java.awt.Color;

public class CustomComponent extends JPanel{
	
	public CustomComponent(){
		setBackground(new Color(0, 255, 255));
	}

	@Override
	public void paintComponent(Graphics g){
		super.paintComponent(g);
		
		g.setColor(Color.YELLOW);
		g.fillOval(10, 10, getWidth()-20, getHeight()-20);
		
		g.setColor(Color.BLACK);
		g.drawOval(10, 10, getWidth()-20, getHeight()-20);
	}
}

Again, this code creates a subcalss of JPanel. It also defines a constructor and calls the setBackground() function (which it inherits from a superclass) and passes in a Color instance to set the background color. That line of code uses the Color constructor, which takes RGB arguments to create a color.

Then in the paintComponent() function, it first calls the super class’s paintComponent() function, which handles stuff like drawing the background color. Then the code calls the setColor() function, which changes the drawing color. This line of code uses the static variable YELLOW defined in the Color class, which is an easy way to use predefined colors instead of going through RGB every time. Then the code calls the fillOval() function to draw the inside of a circle. Then it sets the color to black and draws the outline.

Our main class code doesn’t change. It can still just treat our CustomComponent class as a component and add it to a JFrame to display it.

custom component

Timers

Like I mentioned above, you have no control over when the paintComponent() function is called, so you shouldn’t use paintComponent() for stuff like animation or timing.

Instead, you should use the Timer class in the javax.swing package. The Timer class lets you specify an interval and an ActionListener, and the Timer will call the actionPerformed() function of that ActionListener at that interval. Here’s a simple example:

ActionListener listener = new ActionListener(){
	public void actionPerformed(ActionEvent e){
		System.out.println("Timer fired!");	
	}
}
		
Timer timer = new Timer(1000, listener);
timer.start();

This code creates an implementation of ActionListener using an anonymous class, and in its actionPerformed() function it just prints a message to the console. Then the code calls the Timer constructor, passing in an interval of 1000 milliseconds as well as the ActionListener it just created. Then it calls the start() function on that Timer instance. This causes the Timer to call the actionPerformed() function every 1000 milliseconds, which results in the message being printed to the console one time per second.

Note that we could also have done it in a single statement:

new Timer(1000, new ActionListener(){
	public void actionPerformed(ActionEvent e){
		System.out.println("Timer fired!");	
	}
}).start();

Either format is fine, or you could create a separate top-level class that implements ActionListener instead of using an anonymous class. You’ll see a mix of all of these approaches in the real world.

Anyway, now that we know how to create a Timer to call code at an interval, and we know how to do custom painting, we can combine those ideas to create an animation!

The idea is the same as it was in Processing: we want to base our drawing off of variables, and we want to change those variables over time so that the drawing changes over time. Only instead of doing the drawing and changing in the same function, we want to split that up into two functions: one that contains the “business logic” of updating the variables and is triggered by the Timer, and the paintComponent() function that draws a frame based on those variables.

Putting it all together, it looks like this:

import javax.swing.JPanel;
import javax.swing.Timer;
import java.awt.Graphics;
import java.awt.Color;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;

public class CustomComponent extends JPanel{
	
	private int circleY = 0;
	
	public CustomComponent(){
		setBackground(new Color(0, 255, 255));
	
		new Timer(16, new ActionListener(){
			public void actionPerformed(ActionEvent e){
				step();
				repaint();
			}
		}).start();
	}
	
	private void step(){
		circleY++;
		if(circleY > getHeight()){
			circleY = 0;
		}
	}

	@Override
	public void paintComponent(Graphics g){
		super.paintComponent(g);
		
		g.setColor(Color.RED);
		g.fillOval(getWidth()/2 - 10, circleY, 20, 20);
	}
}

This code defines a CustomComponent class that extends JPanel and contains a circleY variable that starts out at 0. In the constructor, the background color is set, and a Timer is created. Every 16 milliseconds (60 times per second), the Timer calls the step() function, which increments the circleY variable and resets its value if it becomes greater than the height of the component. Then the Timer calls the repaint() function, which is another inherited function that tells the component to redraw itself. This (eventually) causes the overridden paintComponent() function to be called, which draws the circle to the screen.

Some stuff to notice:

  • We’re using an anonymous inner class to create our implementation of ActionListener that we’re passing into our Timer, which means that we can access functions from the outer CustomComparator class inside the actionPerformed() function. This is why we can call step() and repaint() directly.

  • We’re keeping our “business logic” isolated from our painting code. It’s a good idea to keep things separated like this.

  • You should think of the repaint() function as a suggestion for the component to redraw itself. This does not always mean the paintComponent() function will be called right away, and this isn’t the only time the paintComponent() function will be called! For example, on a busy system, you can call repaint() multiple times before the component has a chance to redraw itself. If you call repaint() 10 times before the component can redraw itself, you still only get one call to paintComponent()! That’s why it’s important to keep your logic separate, so you can more reliably call it.

Again, our main code doesn’t change, and we can just create an instance of this class and add it to a JFrame to show it:

falling ball

We can also add an event listener to add user interaction to our animation:

import javax.swing.JPanel;
import javax.swing.Timer;
import java.awt.Graphics;
import java.awt.Color;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseEvent;

public class CustomComponent extends JPanel{
	
	int circleX = 150;
	int circleY = 0;
	
	public CustomComponent(){
		setBackground(new Color(0, 255, 255));
		
		addMouseListener(new MouseListener(){
			
			public void mousePressed(MouseEvent me){
				circleX = me.getX();
				circleY = me.getY();
			}
			
			public void mouseClicked(MouseEvent me){}
			public void mouseEntered(MouseEvent me){}
			public void mouseExited(MouseEvent me){}
			public void mouseReleased(MouseEvent me){}

		});
	
		new Timer(16, new ActionListener(){
			public void actionPerformed(ActionEvent e){
				step();
				repaint();
			}
		}).start();
	}
	
	private void step(){
		circleY++;
		if(circleY > getHeight()){
			circleY = 0;
		}
	}

	@Override
	public void paintComponent(Graphics g){
		super.paintComponent(g);
		
		g.setColor(Color.RED);
		g.fillOval(circleX - 10, circleY, 20, 20);
	}
}

This is the same code as before, but now it adds a MouseListener that moves the circle to wherever the cursor is when the user presses the mouse button.

falling ball with user input

And just to show you another approach: instead of using anonymous classes for our listeners, we could have implemented the interfaces in our class, like this:

import javax.swing.JPanel;
import javax.swing.Timer;
import java.awt.Graphics;
import java.awt.Color;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseEvent;

public class CustomComponent extends JPanel implements ActionListener, MouseListener{
	
	int circleX = 150;
	int circleY = 0;
	
	public CustomComponent(){
		setBackground(new Color(0, 255, 255));
		
		addMouseListener(this);
		new Timer(16, this).start();
	}
	
	private void step(){
		circleY++;
		if(circleY > getHeight()){
			circleY = 0;
		}
	}

	@Override
	public void paintComponent(Graphics g){
		super.paintComponent(g);
		
		g.setColor(Color.RED);
		g.fillOval(circleX - 10, circleY, 20, 20);
	}
	
	@Override
	public void actionPerformed(ActionEvent e){
		step();
		repaint();
	}
	
	@Override
	public void mousePressed(MouseEvent me){
		circleX = me.getX();
		circleY = me.getY();
	}
			
	public void mouseClicked(MouseEvent me){}
	public void mouseEntered(MouseEvent me){}
	public void mouseExited(MouseEvent me){}		
	public void mouseReleased(MouseEvent me){}
}

This code does the exact same thing as before. But now instead of using anonymous inner classes to implement the ActionListener and MouseListener interfaces, our class implements them by defining the body of their respective functions. Then we use the this keyword to pass a self-reference into the Timer constructor and addMouseListener() function. Since this class implements those interfaces, it can be treated as its own ActionListener or MouseListener.

Either approach is fine, and neither is more or less correct than the other. You could also split each listener into its own separate top-level class in its own file. Which approach you use depends on what makes more sense to you and how this stuff fits into your brain.

Other Resources

This tutorial isn’t meant to show you every detail about every single thing you can do in Swing. It’s meant to introduce you to the basic concepts Swing is built on, so you can then consult other resources to accomplish your goals. Here are a few places to get you started:

  • As always, the Java API is your best friend.
  • The official Swing tutorial is a good place to start and links to a bunch of other resources.
  • Using Swing Components introduces a ton of components and how to use them.
  • Laying out Components introduces different layout managers. The visual guide gives you a quick preview of what each one looks like.
  • Writing Event Listeners talks more about different types of event listeners.
  • Performing Custom Painting goes into more detail about doing your own drawing.

If you’re still confused after reading through the documentation, you can always Google stuff like “Java Swing set JButton border” for example. The Swing tag on Stack Overflow is also very active, but make sure you do a search before asking a question.

And of course, you can always ask questions on the Happy Coding forum!

Homework

  • Create a GUI that does something useful, or something that’s not useful! Get creative!
  • Take some of your old Processing sketches and rewrite them using Swing.

Improve Article

Save Article

Like Article

  • Read
  • Discuss
  • Improve Article

    Save Article

    Like Article

    Swing is a Java Foundation Classes [JFC] library and an extension of the Abstract Window Toolkit [AWT]. Swing offers much-improved functionality over AWT, new components, expanded components features, and excellent event handling with drag-and-drop support.

    Introduction of Java Swing

    Swing has about four times the number of User Interface [UI] components as AWT and is part of the standard Java distribution. By today’s application GUI requirements, AWT is a limited implementation, not quite capable of providing the components required for developing complex GUI’s required in modern commercial applications. The AWT component set has quite a few bugs and really does take up a lot of system resources when compared to equivalent Swing resources. Netscape introduced its Internet Foundation Classes [IFC] library for use with Java. Its Classes became very popular with programmers creating GUI’s for commercial applications.

    • Swing is a Set Of API ( API- Set Of Classes and Interfaces )
    • Swing is Provided to Design Graphical User Interfaces
    • Swing is an Extension library to the AWT (Abstract Window Toolkit)
    • Includes New and improved Components that have been enhancing the looks and Functionality of GUIs’
    • Swing can be used to build(Develop) The Standalone swing GUI Apps Also as Servlets And Applets
    • It Employs model/view design architecture
    • Swing is more portable and more flexible than AWT, The Swing is built on top of the AWT
    • Swing is Entirely written in Java
    • Java Swing Components are Platform-independent And The Swing Components are lightweight
    • Swing Supports a Pluggable look and feels And Swing provides more powerful components
    • such as tables, lists, Scrollpanes, Colourchooser, tabbedpane, etc
    • Further Swing Follows MVC.

    Many programmers think that JFC and Swing are one and the same thing, but that is not so.

    JFC contains Swing [A UI component package] and quite a number of other items:

    • Cut and paste: Clipboard support
    • Accessibility features: Aimed at developing GUI’s for users with disabilities
    • The Desktop Colors Features Has been Firstly introduced in Java 1.1
    • Java 2D: it has Improved colors, images, and also texts support

    Features Of Swing Class  

    • Pluggable look and feel
    • Uses MVC architecture
    • Lightweight Components
    • Platform Independent
    • Advanced features such as JTable, JTabbedPane, JScollPane, etc.
    • Java is a platform-independent language and runs on any client machine, the GUI look and feel, owned and delivered by a platform-specific O/S, simply does not affect an application’s GUI constructed using Swing components
    • Lightweight Components: Starting with the JDK 1.1, its AWT-supported lightweight component development. For a component to qualify as lightweight, it must not depend on any non-Java [O/s based) system classes. Swing components have their own view supported by Java’s look and feel classes.
    • Pluggable Look and Feel: This feature enables the user to switch the look and feel of Swing components without restarting an application. The Swing library supports components’ look and feels that remain the same across all platforms wherever the program runs. The Swing library provides an API that gives real flexibility in determining the look and feel of the GUI of an application
    • Highly customizable – Swing controls can be customized in a very easy way as visual appearance is independent of internal representation.
    • Rich controls– Swing provides a rich set of advanced controls like Tree TabbedPane, slider, colorpicker, and table controls.

    Swing Classes Hierarchy

    The MVC Connection

    • In general, a visual component is a composite of three distinct aspects: 
      1. The way that the component looks when rendered on the screen
      2. The way such that the component reacts to the user
      3. The state information associated With the component
    • Over the years, one component architecture has proven itself to be exceptionally effective:- Model-View-Controller or MVC for short.
    • In MVC terminology, the model corresponds to the state information associated with the Component
    • The view determines how the component is displayed on the screen, including any aspects of the view that are affected by the current state of the model.
    • The controller determines how the component reacts to the user

    The simplest Swing components have capabilities far beyond AWT components as follows: 

    • Swing buttons and labels can be displaying images instead of or in addition to text
    • The borders around most Swing components can be changed easily. For example, it is easy to put a 1 pixel border around the outside of a Swing label
    • Swing components do not have to be rectangular. Buttons, for example, can be round
    • Now The Latest Assertive technologies such as screen readers can easily get information from Swing components. For example: A screen reader tool can easily capture the text that is displayed on a Swing button or label

    Example 1: Develop a program using label (swing)  to display message “GFG WEB Site  Click”;

    Java

    import java.io.*;

    import javax.swing.*;

    class GFG {

        public static void main(String[] args)

        {

            JFrame frame

                = new JFrame();

            JButton button = new JButton(

                " GFG WebSite Click");

            button.setBounds(

                150, 200, 220,

                50);

            frame.add(button);

            frame.setSize(500, 600);

            frame.setLayout(null);

            frame.setVisible(true);

        }

    }

    Output: 

    Example 2: Write a program to create three buttons with caption OK , SUBMIT, CANCLE.

    Java

    import java.awt.*;

    class button  {

        button() {

            Frame f = new Frame();

            Button b1 = new Button("OK");

            b1.setBounds(100, 50, 50, 50);

            f.add(b1);

            Button b2 = new Button("SUBMIT");

            b2.setBounds(100, 101, 50, 50);

            f.add(b2);

            Button b3 = new Button("CANCLE");

            b3.setBounds(100, 150, 80, 50);

            f.add(b3);

            f.setSize(500, 500);

            f.setLayout(null);

            f.setVisible(true);

        }

        public static void main(String a[]) {

            new button();

        }

    }

    OUTPUT: 

    Example 3 : 

    Java

    import java.awt.*;

    class Lan {

        Lan() {

            Frame f = new Frame();

            Label l1 = new Label("Select known Languages");

            l1.setBounds(100, 50, 120, 80);

            f.add(l1);

            Checkbox c2 = new Checkbox("Hindi");

            c2.setBounds(100, 150, 50, 50);

            f.add(c2);

            Checkbox c3 = new Checkbox("English");

            c3.setBounds(100, 200, 80, 50);

            f.add(c3);

            Checkbox c4 = new Checkbox("marathi");

            c4.setBounds(100, 250, 80, 50);

            f.add(c4);

            f.setSize(500, 500);

            f.setLayout(null);

            f.setVisible(true);

        }

        public static void main(String ar[]) {

            new Lan();

        }

    }

    output: 

    Components of Swing Classthe task’s percentage

    Class Description
    Component A Component is the Abstract base class for about the non menu user-interface controls of SWING. Components are represents an object with a graphical representation
    Container A Container is a component that can container SWING Components
    JComponent A JComponent is a base class for all swing UI Components In order to use a swing component that inherits from JComponent, component must be in a containment hierarchy whose root is a top-level Swing container
    JLabel A JLabel is an object component for placing text in a container
    JButton This class creates a labeled button
    JColorChooser A JColorChooser provides a pane of controls designed to allow the user to manipulate and select a color
    JCheckBox A JCheckBox is a graphical(GUI) component that can be in either an on-(true) or off-(false) state
    JRadioButton The JRadioButton class is a graphical(GUI) component that can be in either an on-(true) or off-(false) state. in the group
    JList A JList component represents the user with the scrolling list of text items
    JComboBox A JComboBox component is Presents the User with a show up Menu of choices
    JTextField A JTextField object is a text component that will allow for the editing of a single line of text 
    JPasswordField                   A JPasswordField object it is a text component specialized for password entry
    JTextArea A JTextArea object is a text component that allows for the editing of multiple lines of text
    Imagelcon A ImageIcon control is an implementation of the Icon interface that paints Icons from Images
    JScrollbar A JScrollbar control represents a scroll bar component in order to enable users to Select from range values
    JOptionPane     JOptionPane provides set of standard dialog boxes that prompt users for a value or Something
    JFileChooser   A JFileChooser it Controls represents a dialog window from which the user can select a file.
    JProgressBar                As the task progresses towards completion, the progress bar displays the tasks percentage on its completion
    JSlider  A JSlider this class is lets the user graphically(GUI) select by using a value by sliding a knob within a bounded interval.
    JSpinner  A JSpinner this class is a single line input where the field that lets the user select by using a number or an object value from an ordered sequence

    Last Updated :
    29 Mar, 2023

    Like Article

    Save Article

    Понравилась статья? Поделить с друзьями:

    А вот и еще наши интересные статьи:

  • Стиральная машина вятка автомат аленка инструкция
  • Амоксициллин клавулановая кислота для инъекций инструкция по применению
  • Проектор автомобильный geyiren a900 инструкция по эксплуатации
  • Детский спортивный комплекс чемпион 150 инструкция
  • Инструкция по применению препарата милдронат в таблетках

  • 0 0 голоса
    Рейтинг статьи
    Подписаться
    Уведомить о
    guest

    0 комментариев
    Старые
    Новые Популярные
    Межтекстовые Отзывы
    Посмотреть все комментарии