Краткий обзор GUI-фреймворков для Java и мое первое простенькое GUI-приложение на Swing. Оконный пользовательский интерфейс в Java-программе

14.07.2019

Так исторически сложилось, что с UI мне приходилось работать очень мало. Видимо, поэтому мне так интересные всякие там Qt и wxWidgets — все кажется новым, интересным, необычным. Впрочем, коль скоро я взялся за изучение Java , речь сегодня пойдет не о Qt и не о wxWidgets, а о Swing. Сегодня совместными усилиями мы напишем простенькое GUI-приложение на Java, с кнопочками, списками и даже умеющее менять шкурки!

Ситуация с GUI фреймворками в мире Java несколько запутанная. Насколько я смог разобраться, дела обстоят следующим образом.

  • AWT (Abstract Window Toolkit) был первым GUI фреймворком. Идея была правильная — AWT использует нативные контролы, то есть, они выглядят и физически являются родными, независимо от того, где вы запускаете свое приложение. К сожалению, оказалось, что (1) общих для различных окружений контролов мало и (2) писать кроссплатформенные нативные интерфейсы так, чтобы ничего не поползло и не разъехалось, очень сложно;
  • Поэтому на смену AWT пришел Swing . Swing использует формочки, создаваемые AWT, на которых он своими силами рисует контролы. Работает это хозяйство, понятно дело, медленнее, но зато UI становится намного более портабельным. Swing предлагает на выбор программисту множество Look&Feel, благодаря которым можно сделать либо так, чтобы приложение выглядело и вело себя одинаково как под Windows, так и под Linux, либо чтобы приложение было очень похоже на нативное независимо от того, где его запускают. В первом случае приложение проще отлаживать, во втором — становятся счастливее пользователи. Кстати, изначально Swing был сделан парнями из Netscape;
  • SWT (Standard Widget Toolkit) — фреймворк, написанный в IBM и используемый в Eclipse. Как и в AWT, используются нативные контролы. SWT не входит в JDK и использует JNI, поэтому не очень соответствует идеологии Java «написано однажды, работает везде». Вроде как при очень сильном желании можно запаковать в пакет реализацию SWT для всех-всех-всех платформ, и тогда приложение вроде как даже станет портабельным, но только до тех пор, пока не появится какая-нибудь новая операционная система или архитектура процессора;
  • JavaFX активно пилится в Oracle и позиционируется, как скорая замена Swing. Идеологически JavaFX похож на Swing, то есть, контролы не нативные. Среди интересных особенностей JavaFX следует отметить хардверное ускорение, создание GUI при помощи CSS и XML (FXML), возможность использовать контролы JavaFX’а в Swing’е, а также кучу новых красивых контролов, в том числе для рисования диаграмм и 3D. Видео с более детальным обзором JavaFX можно . Начиная с Java 7, JavaFX является частью JRE/JDK ;
  • NetBeans Platform (не путать с NetBeans IDE!) — это такая штука, которая, как я понял, работает поверх Swing и JavaFX, предоставляет как бы более удобный интерфейс для работы с ними, а также всякие дополнительные контролы. В одном приложении, использующем NetBeans Platform, я видел возможность перетаскивать вкладки drug&drop’ом, располагая панели в окне подобно тому, как это делают тайловые оконные менеджеры . По всей видимости, сам Swing так не умеет. Почитать про NetBeans Platform поподробнее ;

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

Выше что-то говорилось про какие-то там Look&Feel. Чтобы лучше понять, о чем идет речь, давайте напишем программу, которая выводит список этих самых Look&Feel и позволит переключаться между ними прямо в процессе работы программы.

Наше приложение будет выглядеть следующим образом под Ubuntu:

А так оно будет выглядеть при запуске под Windows:

Как видите, JRE под Windows и Linux включают в себя разный набор L&F. Кроме того, вы можете подключить сторонний Look&Feel или даже написать свой. По умолчанию используется L&F Metal, который во всех ОС и оконных менеджерах выглядит более-менее одинаково. Если вам больше нравятся круглые кнопочки, то вместо Metal можно использовать Look&Feel Nimbus. Если вам хочется, чтобы приложение было похоже на нативное, то под Linux следует выбрать L&F GTK+ (интересно, а если пользователь сидит под KDE?), а под Windows — L&F Windows. Неплохой идеей, видимо, будет предусмотреть в вашей программе возможность переключаться между различными L&F. С другой стороны, при этом придется тестировать работу приложения со всеми этими L&F.

Давайте посмотрим на исходный код приложения. Коллеги UI-щики заверили меня, что никаких WYSIWYG редакторов они не используют, а если используют, то разве что для быстрого прототипирования. Из неплохих WYSIWYG редакторов назывался JFormDesigner . Говорят, генерируемый им код даже похож на код, написанный человеком, а не адовую().последовательность().вызовов().методов(). В общем, весь код писался лапками в IntelliJ IDEA .

public static void main(String args) {

@Override
public void run() {
createGUI() ;
}
} ) ;
}

В Swing и AWT, если мы хотим что-то поменять в UI, мы должны делать это из event dispatching thread. Статический метод invokeLater принимает класс, реализующий интерфейс Runnable, и вызывает его метод run() внутри event dispatching thread. Если вам не знаком приведенный выше синтаксис, то это такой способ в Java объявить класс, не присваивая ему имени. Классы без имени называются анонимными. Часто анонимные классы в Java выполняют ту же роль, что играют лямда-фукнции в функциональных языках программирования. Помимо прочего, поддерживаются и замыкания. Интересно, что, в отличие от лямбд, анонимные классы в Java позволяют передать сразу пачку методов. Притом, при помощи наследования и абстрактных классов, для всех или части методов можно взять их реализацию по умолчанию.

Аннотация @Override проверяет, что метод run() действительно переопределит метод интерфейса Runnable. Без нее при переопределении метода мы можем случайно сделать опечатку и определить новый метод вместо того, чтобы переопределить существующий. Впрочем, в данном конкретном случае аннотация, видимо, не очень полезна, и, наверное, даже является лишней.

В итоге event dispatching thread вызовет метод createGUI(), полный код которого следующий:

private static void createGUI() {
JList< String> list = new JList<> () ;
list.setSelectionMode (ListSelectionModel .SINGLE_SELECTION ) ;

JScrollPane listScrollPane = new JScrollPane (list) ;

JPanel topPanel = new JPanel () ;
topPanel.setLayout (new BorderLayout () ) ;
topPanel.add (listScrollPane, BorderLayout .CENTER ) ;

ActionListener updateButtonListener = new UpdateListAction(list) ;
updateButtonListener.actionPerformed (
new ActionEvent (list, ActionEvent .ACTION_PERFORMED , null )
) ;

JButton updateListButton = new JButton ("Update list" ) ;
JButton updateLookAndFeelButton = new JButton ("Update Look&Feel" ) ;

JPanel btnPannel = new JPanel () ;
btnPannel.setLayout (new BoxLayout (btnPannel, BoxLayout .LINE_AXIS ) ) ;
btnPannel.add (updateListButton) ;
btnPannel.add (Box .createHorizontalStrut (5 ) ) ;
btnPannel.add (updateLookAndFeelButton) ;

JPanel bottomPanel = new JPanel () ;
bottomPanel.add (btnPannel) ;

JPanel panel = new JPanel () ;
panel.setBorder (BorderFactory .createEmptyBorder (5 ,5 ,5 ,5 ) ) ;
panel.setLayout (new BorderLayout () ) ;
panel.add (topPanel, BorderLayout .CENTER ) ;
panel.add (bottomPanel, BorderLayout .SOUTH ) ;

JFrame frame = new JFrame ("Look&Feel Switcher" ) ;
frame.setMinimumSize (new Dimension (300 , 200 ) ) ;
frame.setDefaultCloseOperation (WindowConstants .EXIT_ON_CLOSE ) ;
frame.add (panel) ;
frame.pack () ;
frame.setVisible (true ) ;

UpdateListButton.addActionListener (updateButtonListener) ;
updateLookAndFeelButton.addActionListener (
new UpdateLookAndFeelAction(frame, list)
) ;
}

Тут, в общем-то, нет ничего супер сложного. Создаются кнопки, список, список заворачивается в JScrollPane, чтобы у списка была прокрутка. Элементы управления располагаются во фрейме при помощи панелей. Панели могут иметь различные лайоуты, здесь мы использовали BorderLayout и BoxLayout . Принцип аналогичен тому, что используется в wxWidgets .

Для реакции на различные события, например, нажатия кнопок, используются классы, реализующие интерфейс ActionListener. В приведенном выше коде используется два таких класса — UpdateListAction и UpdateLookAndFeelAction. Как нетрудно догадаться по названию, первый класс отвечает за обработку нажатий на левую кнопку «Update list», второй — на правую кнопку «Update Look&Feel». ActionListener’ы привязываются к кнопкам при помощи метода addActionListener. Поскольку сразу после запуска приложения нам хочется увидеть список доступных Look&Feel, мы эмулируем нажатие на кнопку «Update list». Для этого мы создаем экземпляр класса ActionEvent и передаем его в качестве аргумента методу actionPerformed класса UpdateListAction.

Реализация класса UpdateListAction следующая:

static class UpdateListAction implements ActionListener {
private JList< String> list;

public UpdateListAction(JList< String> list) {
this .list = list;
}

@Override
public void actionPerformed(ActionEvent event) {
ArrayList< String> lookAndFeelList = new ArrayList<> () ;
UIManager.LookAndFeelInfo infoArray =

int lookAndFeelIndex = 0 ;
int currentLookAndFeelIndex = 0 ;
String currentLookAndFeelClassName =
UIManager .getLookAndFeel () .getClass () .getName () ;

for (UIManager.LookAndFeelInfo info : infoArray) {
if (info.getClassName () .equals (currentLookAndFeelClassName) ) {
currentLookAndFeelIndex = lookAndFeelIndex;
}
lookAndFeelList.add (info.getName () ) ;
lookAndFeelIndex++;
}

String listDataArray = new String [ lookAndFeelList.size () ] ;
final String newListData =
lookAndFeelList.toArray (listDataArray) ;
final int newSelectedIndex = currentLookAndFeelIndex;

SwingUtilities .invokeLater (new Runnable () {
@Override
public void run() {
list.setListData (newListData) ;
list.setSelectedIndex (newSelectedIndex) ;
}
} ) ;
}
}

В конструкторе передается указатель на список, в котором мы будет отображать доступные Look&Feel. На самом деле, поскольку UpdateListAction является вложенным классом нашего основного класса LookAndFeelSwitcher, у него есть возможность обращаться напрямую к полям создавшего его экземпляра LookAndFeelSwitcher. Но функциональщик внутри меня сопротивляется такому подходу, поэтому я решил передать ссылку на список явно через конструктор.

Метод actionPerformed будет вызываться при нажатии на кнопку. Код этого метода довольно тривиален — мы просто используем статические методы класса UIManager для получения списка доступных Look&Feel, а также определения текущего Look&Feel. Затем обновляется содержимое списка и выбранный в нем элемент. Тут нужно обратить внимание на два момента. Во-первых, каждый Look&Feel имеет имя и имя класса , это разные вещи. Пользователю мы должны показывать имена, а при переключении Look&Feel использовать имя класса. Во-вторых, обратите внимание на то, как создаются final переменные newListData и newSelectedIndex, которые затем используются в анонимном классе. Это и есть тот самый аналог замыканий, речь о котором шла ранее. Очевидно, использование не final переменных в замыканиях привело бы к печальным последствиям.

Наконец, рассмотрим класс UpdateLookAndFeelAction:

static class UpdateLookAndFeelAction implements ActionListener {
private JList< String> list;
private JFrame rootFrame;

public UpdateLookAndFeelAction(JFrame frame, JList< String> list) {
this .rootFrame = frame;
this .list = list;
}

@Override
public void actionPerformed(ActionEvent e) {
String lookAndFeelName = list.getSelectedValue () ;
UIManager.LookAndFeelInfo infoArray =
UIManager .getInstalledLookAndFeels () ;

for (UIManager.LookAndFeelInfo info : infoArray) {
if (info.getName () .equals (lookAndFeelName) ) {
String message = "Look&feel was changed to " + lookAndFeelName;
try {
UIManager .setLookAndFeel (info.getClassName () ) ;
SwingUtilities .updateComponentTreeUI (rootFrame) ;
} catch (ClassNotFoundException e1) {
message = "Error: " + info.getClassName () + " not found" ;
} catch (InstantiationException e1) {
message = "Error: instantiation exception" ;
} catch (IllegalAccessException e1) {
message = "Error: illegal access" ;
} catch (UnsupportedLookAndFeelException e1) {
message = "Error: unsupported look and feel" ;
}
JOptionPane .showMessageDialog (null , message) ;
break ;
}
}
}
}

Здесь мы просто (1) находим L&F с именем, равным имени, выбранному в списке, (2) меняем L&F при помощи static метода setLookAndFeel класса UIManager и (3) перерисовываем главный фрейм нашего UI, а также, рекурсивно, расположенные на нем элементы, при помощи static метода updateComponentTreeUI класса SwingUtilities. Наконец, мы уведомляем пользователя при помощи сообщения, все ли прошло успешно.

Также хотелось бы сказать пару слов об отладке GUI-приложений на Java, и не только GUI. Во-первых, в Swing есть такое волшебное сочетание клавиш Ctr + Shift + F1, которое выводит в stdout информацию о том, как расположены контролы. Очень полезно, если хочется слизать UI у конкурентов. Во-вторых, есть такой интересный хоткей Ctr + \. Если нажать его в консоли работающего приложения на Java, будут выведены все нитки и их стектрейсы. Удобно, если вы словили дэдлок. Наконец, в-третьих, во время разработки GUI бывает полезно разукрасить панели в разные цвета. Сделать это можно так:

buttonsPanel.setBackground (Color .BLUE ) ;

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

Abstract Window Toolkit

AWT была первой попыткой Sun создать графический интерфейс для Java. Они пошли легким путем и просто сделали прослойку на Java, которая вызывает методы из библиотек, написанных на С. Библиотечные методы создают и используют графические компоненты операционной среды. С одной стороны, это хорошо, так как программа на Java похожа на остальные программы в рамках данной ОС. Но с другой стороны, нет никакой гарантии, что различия в размерах компонентов и шрифтах не испортят внешний вид программы при запуске ее на другой платформе. Кроме того, чтобы обеспечить мультиплатформенность, пришлось унифицировать интерфейсы вызовов компонентов, из-за чего их функциональность получилась немного урезанной. Да и набор компонентов получился довольно небольшой. К примеру, в AWT нет таблиц, а в кнопках не поддерживается отображение иконок.

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

Достоинства:

  • часть JDK;
  • скорость работы;
  • графические компоненты похожи на стандартные.

Недостатки:

  • использование нативных компонентов налагает ограничения на использование их свойств. Некоторые компоненты могут вообще не работать на «неродных» платформах;
  • некоторые свойства, такие как иконки и всплывающие подсказки, в AWT вообще отсутствуют;
  • стандартных компонентов AWT очень немного, программисту приходится реализовывать много кастомных;
  • программа выглядит по-разному на разных платформах (может быть кривоватой).

заключение:

В настоящее время AWT используется крайне редко - в основном в старых проектах и апплетах. Oracle припрятал обучалки и всячески поощряет переход на Swing. Оно и понятно, прямой доступ к компонентам оси может стать серьезной дырой в безопасности.

Swing


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

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

Достоинства:

  • часть JDK, не нужно ставить дополнительных библиотек;
  • по Swing гораздо больше книжек и ответов на форумах. Все проблемы, особенно у начинающих, гуглу досконально известны;
  • встроенный редактор форм почти во всех средах разработки;
  • на базе свинга есть много расширений типа SwingX;
  • поддержка различных стилей (Look and feel).

Недостатки:

  • окно с множеством компонентов начинает подтормаживать;
  • работа с менеджерами компоновки может стать настоящим кошмаром в сложных интерфейсах.

Заключение:

Swing жил, Swing жив, Swing будет жить. Хотя Oracle и старается продвигать JavaFX, на сегодняшний день Swing остается самым популярным фреймворком для создания пользовательских интерфейсов на Java.

Standard Widget Toolkit


Как
выглядит
SWT

SWT был разработан в компании IBM в те времена, когда Swing еще был медленным, и сделано это было в основном для продвижения среды программирования Eclipse. SWT, как и AWT, использует компоненты операционной системы, но для каждой платформы у него созданы свои интерфейсы взаимодействия. Так что для каждой новой системы тебе придется поставлять отдельную JAR-библиотеку с подходящей версией SWT. Это позволило более полно использовать существующие функции компонентов на каждой оси. Недостающие функции и компоненты были реализованы с помощью 2D, как в Swing. У SWT есть много приверженцев, но, положа руку на сердце, нельзя не согласиться, что получилось не так все просто, как хотелось бы. Новичку придется затратить на изучение SWT намного больше времени, чем на знакомство с тем же Swing. Кроме того, SWT возлагает задачу освобождения ресурсов на программиста, в связи с чем ему нужно быть особенно внимательным при написании кода, чтобы случайное исключение не привело к утечкам памяти.

Достоинства:

  • использует компоненты операционной системы - скорость выше;
  • Eclipse предоставляет визуальный редактор форм;
  • обширная документация и множество примеров;
  • возможно использование AWT- и Swing-компонентов.

Недостатки:

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

Заключение:

Видно, что в IBM старались. Но получилось уж очень на любителя…

JavaFX


JavaFX можно без преувеличения назвать прорывом. Для отрисовки используется графический конвейер, что значительно ускоряет работу приложения. Набор встроенных компонентов обширен, есть даже отдельные компоненты для отрисовки графиков. Реализована поддержка мультимедийного контента, множества эффектов отображения, анимации и даже мультитач. Внешний вид всех компонентов можно легко изменить с помощью CSS-стилей. И самое прекрасное - в JavaFX входит набор утилит, которые позволяют сделать родной инсталлятор для самых популярных платформ: exe или msi для Windows, deb или rpm для Linux, dmg для Mac. На сайте Oracle можно найти подробную документацию и огромное количество готовых примеров. Это превращает программирование с JavaFX в легкое и приятное занятие.

Достоинства:

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

Недостатки:

  • фреймворк еще разрабатывается, поэтому случаются и падения и некоторые глюки;
  • JavaFX пока не получил широкого распространения.

Заключение:

Хорошая работа, Oracle. Фреймворк оставляет только позитивные впечатления. Разобраться несложно, методы и интерфейсы выглядят логичными. Хочется пользоваться снова и снова!

Визуальные библиотеки на практике

SWT: погодный виджет

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

И начнем, пожалуй, с самого популярного виджета - отображения текущей погоды, для реализации которого выберем SWT.

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

Display display = new Display(); shell = new Shell(display, SWT.NO_TRIM);

Так как мы создаем виджет, нам не нужно отображать стандартное обрамление окна и кнопки управления, для этого мы указали флаг NO_TRIM. Для фона мы будем использовать картинку - прямоугольник с закругленными углами. В принципе, окно SWT может принимать любые формы. Чтобы добиться такого эффекта, используем класс Region. Все, что нужно, - добавить в этот класс все видимые точки из картинки фона, пропуская прозрачные.

Загружаем картинку:

Image image = new Image(display, "images/bg.png#26759185");

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

Region region = new Region(); ImageData imageData = image.getImageData(); if (imageData.alphaData != null) { Rectangle pixel = new Rectangle(0, 0, 1, 1); for (int y = 0; y < imageData.height; y++) { for (int x = 0; x < imageData.width; x++) { if (imageData.getAlpha(x, y) == 255) { pixel.x = imageData.x + x; pixel.y = imageData.y + y; region.add(pixel); } } } } else { ImageData mask = imageData.getTransparencyMask(); Rectangle pixel = new Rectangle(0, 0, 1, 1); for (int y = 0; y < mask.height; y++) { for (int x = 0; x < mask.width; x++) { if (mask.getPixel(x, y) != 0) { pixel.x = imageData.x + x; pixel.y = imageData.y + y; region.add(pixel); } } } }

Устанавливаем форму окна:

Shell.setRegion(region);

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

Listener listener = new Listener() { int startX, startY; public void handleEvent(Event e) { if (e.type == SWT.KeyDown && e.character == SWT.ESC) { shell.dispose(); } if (e.type == SWT.MouseDown && e.button == 1) { startX = e.x; startY = e.y; } if (e.type == SWT.MouseMove && (e.stateMask & SWT.BUTTON1) != 0) { Point p = shell.toDisplay(e.x, e.y); p.x -= startX; p.y -= startY; shell.setLocation(p); } if (e.type == SWT.Paint) { e.gc.drawImage(image, imageData.x, imageData.y); } } };

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

Назначим слушатель соответствующим событиям окна:

Shell.addListener(SWT.KeyDown, listener); shell.addListener(SWT.MouseDown, listener); shell.addListener(SWT.MouseMove, listener); shell.addListener(SWT.Paint, listener);

Устанавливаем размер окна равным размеру изображения:

Shell.setSize(imageData.x + imageData.width, imageData.y + imageData.height);

Открываем окно и запускаем цикл событий:

Shell.open(); while (!shell.isDisposed ()) { if (!display.readAndDispatch ()) display.sleep (); }

Не забываем в конце освободить использованные ресурсы:

Region.dispose(); image.dispose(); display.dispose();

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

Настало время добавить содержания. Будем отображать текущую погоду в виде иконки состояния (солнечно, дождь, снег…), показаний температуры и времени последнего обновления.

Для расположения графических компонентов в окне в нужном виде используются менеджеры компоновки. Менеджер компоновки занимается не только расположением компонентов, но и изменением их размеров при изменении размеров окна. Для нашего виджета будем использовать GridLayout. Этот менеджер располагает компоненты в ячейках воображаемой таблицы. Создаем GridBagLayout на две колонки с различной шириной колонок (флаг false в конструкторе), устанавливаем его в качестве менеджера компоновки окна:

GridLayout layout = new GridLayout(2, false); shell.setLayout(layout);

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

//draw status image Label imageLabel = new Label(shell, SWT.NONE); imageLabel.setLayoutData(new GridData(SWT.LEFT, SWT.TOP, true, true, 1, 1));

Флаги в классе GridData означают, что метка будет располагаться слева вверху, будет растягиваться горизонтально и вертикально (флаги, установленные в true) при наличии свободного места и занимает одну строку и один столбец таблицы компоновки.

В SWT нет прозрачного фона компонентов, и позади картинки статуса будет красоваться белый фон, чего, конечно, не хотелось бы. Поэтому создадим объект Color с цветом фона окна:

Color bgColor = new Color(display, 0x2b, 0x2b, 0x2b);

В конце программы этот объект также необходимо очистить, вызвав метод dispose. Устанавливаем цвет фона и картинку статуса, которую можно загрузить из файла точно так же, как мы загрузили картинку фона вначале:

ImageLabel.setBackground(bgColor); Image statusImage = new Image(display, "images/1.png#26759185"); imageLabel.setImage(statusImage);

Теперь добавим Label с текущей температурой и расположим его в правой верхней части окна:

Label temperatureLabel = new Label(shell, SWT.NONE); temperatureLabel.setLayoutData(new GridData(SWT.RIGHT, SWT.TOP, false, false, 1, 1));

Установим какую-нибудь температуру:

TemperatureLabel.setText("+1 \u2103");

Для записи температуры по Цельсию используется юникодный номер соответствующего символа со служебными символами \u.

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

FontData fD = temperatureLabel.getFont().getFontData(); fD.setHeight(30); fD.setStyle(SWT.BOLD); Font newFont = new Font(display, fD); temperatureLabel.setFont(newFont); Шрифт, как и другие ресурсные объекты, нужно освобождать. Для этого воспользуемся слушателем события разрушения метки:

TemperatureLabel.addDisposeListener(new DisposeListener() { public void widgetDisposed(DisposeEvent e) { newFont.dispose(); } });

Наконец, добавим метку с описанием погодных условий:

Label descriptionLabel = new Label(shell, SWT.WRAP); descriptionLabel.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, true, 2, 1)); descriptionLabel.setText("Облачно с прояснениями, небольшой дождь"); descriptionLabel.setBackground(bgColor); descriptionLabel.setForeground(display.getSystemColor(SWT.COLOR_WHITE));

Текст может быть довольно длинным, так что при создании метки указываем флаг WRAP, чтобы текст автоматически разбивался на несколько строк при нехватке места. Расположим компонент по центру и разрешим ему заполнить все горизонтальное пространство. Также укажем, что компонент занимает два столбца таблицы компоновки. Запускаем и получаем окошко с картинки «Виджет погоды».

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

Swing: всегда свежие новости

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

JFrame frame = new JFrame(); frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);

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

Для хранения информации о доступных новостях заведем класс FeedMessage c полями для названия статьи и даты выхода:

Public class FeedMessage { public String title; public Date publicationDate; }

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

Public class RssFeedTableModel extends AbstractTableModel { private List entries = new ArrayList<>(); public void updateData(List entries) { this.entries = entries; fireTableDataChanged(); } public int getRowCount() { return entries.size(); } public int getColumnCount() { return 2; } public Object getValueAt(int rowIndex, int columnIndex) { switch (columnIndex) { case 0: return entries.get(rowIndex).title; case 1: return entries.get(rowIndex).publicationDate; } return null; } }

Метод fireTableDataChanged сообщает представлению, что модель данных изменилась и необходима перерисовка.

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

JTable table = new JTable(new RssFeedTableModel()); table.setShowGrid(false); table.setIntercellSpacing(new Dimension(0, 0)); table.setRowHeight(30); table.setTableHeader(null);

Теперь займемся внешним видом ячеек. Swing позволяет назначать отдельные классы представления для разных типов данных. За отрисовку отдельных ячеек таблицы отвечает класс, наследующий интерфейс TableCellRenderer. По умолчанию используется DefaultTableCellRenderer, который представляет собой текстовую метку.

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

Table.setDefaultRenderer(String.class, new DefaultTableCellRenderer() { Color oddColor = new Color(0x25, 0x25, 0x25); Color evenColor = new Color(0x1a, 0x1a, 0x1a); Color titleColor = new Color(0x3a, 0xa2, 0xd7); public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); setBackground(row % 2 == 0 ? oddColor: evenColor); setForeground(titleColor); setFont(font); return this; } });

Чтобы таблица начала использовать наш отрисовщик, необходимо добавить метод, который возвращает тип данных для каждой ячейки, в модель данных:

Public Class getColumnClass(int columnIndex) { switch (columnIndex) { case 0: return String.class; case 1: return Date.class; } return Object.class; }

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

JScrollPane scrollPane = new JScrollPane(table); table.setFillsViewportHeight(true); scrollPane.getVerticalScrollBar().setPreferredSize (new Dimension(0,0));

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

Frame.getContentPane().add(scrollPane, BorderLayout.CENTER);

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

JLabel titleLabel = new JLabel("Xakep RSS"); Font titleFont = new Font("Arial", Font.BOLD, 20); titleLabel.setFont(titleFont); titleLabel.setHorizontalAlignment(SwingConstants.CENTER); titleLabel.setForeground(Color.WHITE); titleLabel.setPreferredSize(new Dimension(0, 40)); frame.getContentPane().add(titleLabel, BorderLayout.NORTH);

В отличие от SWT, объекты «цвет» и «шрифт» освобождаются автоматически, так что можно больше не переживать за утечки памяти.

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

MouseAdapter listener = new MouseAdapter() { int startX; int startY; public void mousePressed(MouseEvent e) { if (e.getButton() == MouseEvent.BUTTON1) { startX = e.getX(); startY = e.getY(); } } public void mouseDragged(MouseEvent e) { Point currCoords = e.getLocationOnScreen(); frame.setLocation(currCoords.x - startX, currCoords.y - startY); } }; titleLabel.addMouseListener(listener); titleLabel.addMouseMotionListener(listener);

Теперь поменяем форму окна на прямоугольник с закругленными углами. Лучше всего это делать в слушателе компонента, так как, если размер окна изменится, форма окна будет правильно пересчитана:

Frame.addComponentListener(new ComponentAdapter() { public void componentResized(ComponentEvent e) { frame.setShape(new RoundRectangle2D.Double(0, 0, frame.getWidth(), frame.getHeight(), 20, 20)); } });

Устанавливаем размер окна, убираем обрамление и делаем окно полупрозрачным.

Frame.setSize(520, 300); frame.setUndecorated(true); frame.setOpacity(0.85f);

Наконец, открываем окно в графическом потоке. SwingUtilities.invokeLater(new Runnable() { public void run() { frame.setVisible(true); } });

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

JavaFX: послушаем музычку

И наконец, гвоздь сезона - JavaFX. Воспользуемся его мультимедийными возможностями и компонентом для построения графиков и сделаем простенький эквалайзер.

Для начала наследуем класс виджета от Application. Это основной класс приложения в JavaFX. Application содержит основные методы жизненного цикла приложения. Компоненты формы создаются в методе start, аргументом которому служит класс Stage. Stage представляет собой окно программы. Изменим стиль окна на TRANSPARENT, чтобы убрать обрамление и кнопки. В Stage помещается класс Scene, в котором задаются размеры окна и цвет фона. В Scene, в свою очередь, передаем класс Group, в который будем помещать дочерние компоненты:

Public void start(Stage primaryStage) { primaryStage.initStyle(StageStyle.TRANSPARENT); Group root = new Group(); Scene scene = new Scene(root, 400, 200, Color.TRANSPARENT); primaryStage.setScene(scene);

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

CategoryAxis xAxis = new CategoryAxis(); NumberAxis yAxis = new NumberAxis(0,50,10); BarChart bc = new BarChart(xAxis,yAxis); bc.setPrefSize(400, 200); bc.setLegendVisible(false); bc.setAnimated(false); bc.setBarGap(0); bc.setCategoryGap(1); bc.setVerticalGridLinesVisible(false); bc.setHorizontalGridLinesVisible(false); xAxis.setLabel("Частота"); yAxis.setLabel("Мощность"); yAxis.setTickLabelFormatter(new NumberAxis.DefaultFormatter(yAxis, null, "dB"));

Заполняем диаграмму начальными данными:

XYChart.Series series1 = new XYChart.Series(); series1Data = new XYChart.Data; String categories = new String; for (int i=0; i(categories[i], 50); series1.getData().add(series1Data[i]); } bc.getData().add(series1);

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

Rectangle rectangle = new Rectangle(0, 0, 400, 200); Stop stops = new Stop { new Stop(0, new Color(0, 0, 0, 0.8)), null}; LinearGradient lg2 = new LinearGradient(0, 0, 0, 0, false, CycleMethod.NO_CYCLE, stops); rectangle.setFill(lg2); rectangle.setArcHeight(20); rectangle.setArcWidth(20);

Добавляем оба компонента к группе:

Root.getChildren().addAll(rectangle, bc);

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

Root.setOnMousePressed(new EventHandler() { public void handle(MouseEvent me) { initX = me.getScreenX() - primaryStage.getX(); initY = me.getScreenY() - primaryStage.getY(); } }); root.setOnMouseDragged(new EventHandler() { public void handle(MouseEvent me) { primaryStage.setX(me.getScreenX() - initX); primaryStage.setY(me.getScreenY() - initY); } });

Загружаем песню в плеер:

File file = new File("выпусти меня отсюда.mp3"); Media audioMedia = null; audioMedia = new Media(file.toURI().toURL().toString()); audioMediaPlayer = new MediaPlayer(audioMedia);

Добавляем слушатель, который будет обновлять столбиковую диаграмму:

AudioMediaPlayer.setAudioSpectrumListener(new AudioSpectrumListener() { public void spectrumDataUpdate(double timestamp, double duration, float magnitudes, float phases) { for (int i = 0; i < series1Data.length; i++) { series1Data[i].setYValue(magnitudes[i] + 60); } } });

Делаем сцену видимой и запускаем песню:

PrimaryStage.show(); audioMediaPlayer.play();

Запускаем приложение:

Public static void main(String args) { launch(args); }

И наслаждаемся такой вот красотой.

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


Чтобы размещать кнопки, текстовые надписи и другие компоненты в окне программы, вам следует понять, как работает JPanel . Это что-то вроде контейнера для компонентов, который занимает прямоугольную область экрана и показывает компоненты, выровненные по какому-то простому принципу. Как именно выровнены компоненты, зависит от типа схемы размещения, которую вы установили для панели. Для простых задач программирования вам следует знать, как минимум, BorderLayout , который располагает компоненты по краям и один большой компонент ставит в середину, затем FlowLayout , который обычно располагает компоненты в ряд по горизонтали, и, наконец, GridLayout , который располагает компоненты в произвольной таблице n * m. Есть другие типы, но они слишком сложны для новичков. Ключевая идея в том, что "компонентой" может быть не только кнопка или флажок, но и другая JPanel. Вы можете получить достаточно сложный пользовательский интерфейс, просто расположив панели одна на другой и выбрав для них планировку.


Если у вас есть экземпляр объекта JPanel, вызовите метод .setLayout , чтобы установить тип планировки, и затем метод.add, чтобы добавить на панель компоненты. В случае BorderLayout в качестве второго параметра вам нужно будет передать положение. Например, чтобы расположить кнопку в верхней части, вызовите myPanel.add(myButton, BorderLayout.North) .


Контейнер самого высокого уровня, который появляется на экране, представляющем приложение Java, является экземпляром JFrame , а не JPanel . Чтобы добавить вашу основную панель в экземпляр JFrame просто вызовите myJFrame.getContentPane().add(myJPanel, BorderLayout.Center) .


Чтобы заставить ваше приложение делать что-то большее, чем просто появляться, вам нужно будет понять интерфейс ActionListener . У любого неабстрактного ActionListener есть только один метод actionPerformed, который вызывается, когда пользователь выполняет "действие" над компонентой, в которой зарегистрирован слушатель (например, действие над кнопкой - это, очевидно, ее нажатие). Чтобы зарегистрировать слушателя действий для кнопки или любого другого компонента, вызовите метод .addActionListener. .

Шаги

Создание общего фрейма

    Создайте класс, который расширяет класс JFrame . Этот класс будет содержать все ваши компоненты графического интерфейса (GUI), такие как кнопки и текстовые поля.

    Продумайте общий внешний вид вашего первого приложения. Неплохо было бы начать с центральной панели типа BorderLayout с другой панелью в нижней ее части (BorderLayout.South ). Эта вторая панель будет иметь тип FlowLayout и содержать несколько кнопок, флажков и других контрольных элементов. И наконец, расположите большой компонент JTextArea посередине центрального компонента. Чтобы взаимодействовать с пользователем посредством текста, вы сможете использовать методы getText() и setText() .

    Напишите конструктор для вашего класса. Этот конструктор должен создать все панели и компоненты, которые вы запланировали, расположить их правильно и добавить последнюю панель, которая "прикрепляет все" к вашему фрейму (myFrame.getContentPane().add(myLargePanel, BorderLayout.Center).

    Напишите метод main, который будет точкой входа программы. В этом методе создайте экземпляр фрейма, установите его начальные размер и положение (используйте.setSize(x,y) и .setLocation(width, height) ), и заставьте его появиться на экране, вызвав .setVisible(true).

    Программирование ответов на действия пользователя

    1. Сделайте ваш фрейм реализующим интерфейс ActionListener . Это позволит вашему классу "слушать" действия компонентов.

      Для каждой кнопки, флажка или другого контрольного элемента, которых вы создали, вызовите метод. addActionListener, передав ваш фрейм (this ) в качестве параметра.

      Переопределите абстрактный метод класса ActionListener, который называется actionPerformed(ActionEvent event). В этом методе вам следует добавить условные выражения "if", чтобы проверить, откуда пришло событие. В этом условном операторе "if" должно быть условие вроде такого: "if (event.getSource() == button1 )". Здесь проверяется, откуда пришло событие, и пришло ли оно от кнопки. Внутри выражения "if" выполняйте любые действия, которые вам необходимы при нажатии на кнопку.

      У JTextArea есть метод .setText("myText") , который является неплохим способом запрограммировать какой-то видимый ответ на ваше действие.

    • Совсем не намного сложнее реализовать интерфейс MouseListener и использовать .addMouseListener , чтобы зарегистрировать его для любой компоненты.
    • Если вам нужно запросить от пользователя ввести какую-то строку, вызовите статический метод JOptionPane.showInputDialog(this, "My message"). первым параметром должен быть фрейм вашего приложения или какая-нибудь панель (поле для ввода появится посередине родительского элемента). Метод возвращает значение, которое пользователь ввел в диалоговом окне.
    • Вполне возможно разместить все компоненты на одной панели, использующей тип GridBagLayout, но этим классом труднее управлять.
    • Если вы хотите нарисовать собственные графические объекты (например, шахматную доску), почитайте о компоненте Canvas . Он может быть размещен в вашем приложении, как любой другой компонент, но вам нужно будет написать метод.paint, который полностью отвечает за его отрисовку.
    • Во многих реальных приложениях наиболее полезным компонентом Swing является JTable . После изучения основ, продолжайте работать с ним.

    Предупреждения

    • Некоторые средства разработки предлагают возможность составить графический интерфейс Swing способом, "удобным для пользователя". Однако, зачастую они не могут должным образом сделать панель с продвинутыми возможностями. Эти возможности включают деревья, таблицы, списки и комбинированные списки, которые меняют свое содержимое по мере работы программы, а также компоненты с моделями данных пользователя и т.д. Код, написанный при таком "дружеском для пользователя" способе, станет настоящим кошмаром, если вам потом потребуется дописать его вручную. Поэтому слишком не увлекайтесь подобными "дизайнерами графического интерфейса, дружественными для пользователя", потому что это ограничит ваши возможности из-за их ограниченных возможностей.
    • Swing - это однопоточное приложение. Если обработка действия у вас занимает слишком много времени, оно "зависнет", пока не произойдет выход из метода .actionPerformed . Изучайте и используйте многопоточность java, чтобы Swing оставался "живым", пока работает какой-то трудоемкий процесс.
    • Большинство методов компонентов Swing можно безопасно вызвать только из потока диспетчеризации событий (метод .actionPerformed и другие похожие методы слушателя). Если вам нужно вызвать их из какого-то другого потока (например, чтобы обновить индикатор прогресса или показать результаты какого-то длительного процесса), почитайте о SwingUtils.invokeLater .

    Источники

    Исходный код

    Import java.awt.BorderLayout ; import java.awt.FlowLayout ; import java.awt.event.ActionEvent ; import java.awt.event.ActionListener ; import javax.swing.JButton ; import javax.swing.JCheckBox ; import javax.swing.JFrame ; import javax.swing.JPanel ; import javax.swing.JTextArea ; /** * Очень простое приложение java swing. * Содержит кнопку и флажок. Отвечает * на изменения этих контрольных элементов * изменением текста в главном текстовом поле. * * @author audriusa */ public class WikiHow extends JFrame implements ActionListener { /** * Кнопка. */ JButton myButton = new JButton ("Button" ) ; /** * Флажок. */ JCheckBox myCheckBox = new JCheckBox ("Check" ) ; /** * Текстовое поле. */ JTextArea myText = new JTextArea ("My text" ) ; /** * Нижняя панель, содержащая кнопку. */ JPanel bottomPanel = new JPanel () ; /** * Родительская панель, содержащая все. */ JPanel holdAll = new JPanel () ; /** * Конструктор. */ public WikiHow() { bottomPanel.setLayout (new FlowLayout () ) ; bottomPanel.add (myCheckBox) ; bottomPanel.add (myButton) ; holdAll.setLayout (new BorderLayout () ) ; holdAll.add (bottomPanel, BorderLayout .SOUTH ) ; holdAll.add (myText, BorderLayout .CENTER ) ; getContentPane() .add (holdAll, BorderLayout .CENTER ) ; myButton.addActionListener (this ) ; myCheckBox.addActionListener (this ) ; setDefaultCloseOperation(DISPOSE_ON_CLOSE) ; } /** * Программа * @param args Параметры старта программы, не используются. */ public static void main(String args) { WikiHow myApplication = new WikiHow() ; // Указываем, где оно должно появиться: myApplication.setLocation (10 , 10 ) ; myApplication.setSize (300 , 300 ) ; // Показать! myApplication.setVisible (true ) ; } /** * Любой неабстрактный класс, который реализует ActionListener * должен иметь этот метод. * * @param e Событие. */ public void actionPerformed(ActionEvent e) { if (e.getSource () == myButton) myText.setText ("A button click" ) ; else if (e.getSource () == myCheckBox) myText.setText ("The checkbox state changed to " + myCheckBox.isSelected () ) ; else myText.setText ("E ...?" ) ; } }

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

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

Дело в том, что каждая оконная среда - это сложный мир, со своими законами, набором строительных блоков и приемов программирования. Motif не похож на MS-Windows и оконную систему Macintosh. По-разному представляются примитивные элементы интерфейса, по-разному обрабатываются внешние события, по-разному происжодит рисование на экране и т.д.

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

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

Данная особенность оконных сред проявилась, в частности, в появлении довольно большого количества различных классовых библиотек, “обертывающих” оригинальные оконные системы. В качестве примеров можно привести MFC, OWL, Zink и многие другие.

Вот и среди стандартных Java-библиотек присутствует AWT или Abstract Windowing Toolkit - абстрактный оконный инструментарий.

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

Основы построения графического пользовательского интерфейса Компоненты и контейнеры

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

К числу примитивных компонент относятся:

Основные контейнеры:

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

Экранной формой называется область, которая видна на экране в виде окна с различными элементами - кнопками, текстом, выпадающими списками и т.п. А сами эти элементы называются компонентами.

Среды, позволяющие в процессе разработки приложения в интерактивном режиме размещать на формы компоненты и задавать их параметры, называются RAD-средами. RAD расшифровывается как Rapid Application Development - быстрая разработка приложений.

В NetBeans и других современных средах разработки такой процесс основан на объектной модели компонентов, поэтому он называется Объектно-Ориентированным Дизайном (OOD – Object-Oriented Design).

NetBeans является RAD-средой и позволяет быстро и удобно создавать приложения с развитым графическим пользовательским интерфейсом (GUI). Хотя языковые конструкции Java, позволяющие это делать, не очень просты, на начальном этапе работы с экранными формами и их элементами нет необходимости вникать в эти тонкости. Достаточно знать основные принципы работы с такими проектами.

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

Во-вторых, такой интерфейс при решении какой-либо задачи позволяет лучше сформулировать, какие параметры надо вводить, какие действия и в какой последовательности выполнять, и что в конце концов получается. И отобразить всё это на экране: вводимым параметрам будут соответствовать пункты ввода текста, действиям – кнопки и пункты меню, результатам – пункты вывода текста.

Пример открытия проекта с существующим исходным кодом.

В NetBeans 5.0 имелся хороший пример GUI-приложения, однако в NetBeans 5.5 он отсутствует. Поэтому для дальнейшей работы следует скопировать аналогичный пример с сайта автора или сайта, на котором выложен данный учебный курс. Пример называется JavaApplicationGUI_example.

Сначала следует распаковать zip-архив, и извлечь находящуюся в нём папку с файлами проекта в папку с вашими проектами (например, C:\Documents and Settings\User). Затем запустить среду NetBeans, если она не была запущена, и закрыть имеющиеся открытые проекты, чтобы они не мешали. После чего выбрать в меню File/Open Project, либо или на панели инструментов иконку с открывающейся фиолетовой папочкой, либо нажать комбинацию клавиш ++O. В открывшемся диалоге выбрать папку JavaApplicationGUI_example (лучше в неё не заходить, а просто установить выделение на эту папку), после чего нажать кнопку Open Project Folder.

При этом, если не снимать галочку “Open as Main Project”, проект автоматически становится главным.

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

* GUI_application.java

package java_gui_example;

* @author Вадим Монахов

public class GUI_application extends javax.swing.JFrame {

* Creates new form GUI_application

public GUI_application() {

initComponents();

/** This method is called from within the constructor to

* initialize the form.

* WARNING: Do NOT modify this code. The content of this method is

* always regenerated by the Form Editor.

private void exitMenuItemActionPerformed(java.awt.event.ActionEvent evt)

* @param args the command line arguments

public static void main(String args) {

java.awt.EventQueue.invokeLater(new Runnable() {

public void run() {

new GUI_application().setVisible(true);

// Variables declaration - do not modify

private javax.swing.JMenuItem aboutMenuItem;

private javax.swing.JMenuItem contentsMenuItem;

private javax.swing.JMenuItem copyMenuItem;

private javax.swing.JMenuItem cutMenuItem;

private javax.swing.JMenuItem deleteMenuItem;

private javax.swing.JMenu editMenu;

private javax.swing.JMenuItem exitMenuItem;

private javax.swing.JMenu fileMenu;

private javax.swing.JMenu helpMenu;

private javax.swing.JMenuBar menuBar;

private javax.swing.JMenuItem openMenuItem;

private javax.swing.JMenuItem pasteMenuItem;

private javax.swing.JMenuItem saveAsMenuItem;

private javax.swing.JMenuItem saveMenuItem;

// End of variables declaration

Поясним некоторые его части. Указание пакета java_gui_example, в котором будет располагаться код класса приложения, нам уже знакомо. Декларация самого класса GUI_application в данном случае несколько сложнее, чем раньше:

public class GUI_application extends javax.swing.JFrame

Она означает, что задаётся общедоступный класс GUI_application, который является наследником класса JFrame, заданного в пакете swing, вложенном в пакет javax. Слово extends переводится как “расширяет” (класс-наследник всегда расширяет возможности класса-прародителя).

Общедоступный конструктор GUI_application()создаёт объект приложения и инициализирует все его компоненты, методом initComponents(), автоматически генерируемом средой разработки и скрываемом в исходном коде узлом +Generated Code.

Развернув узел, можно увидеть реализацию этого метода, но изменить код нельзя. Мы не будем останавливаться на том, что в нём делается.

private void exitMenuItemActionPerformed

Он будет обсуждаться чуть позже. Метод

public static void main(String args)

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

Запущенное приложение. Приложение с раскрытым меню.

При запуске приложения экранная форма выглядит так, как показано на рисунке. В ней уже имеется заготовка меню, которое способно разворачиваться и сворачиваться, и даже работает пункт Exit – “Выход”. При нажатии на него происходит выход из приложения.

Именно за нажатие на этот пункт меню несёт ответственность оператор exitMenuItemActionPerformed. При проектировании экранной формы он назначен в качестве обработчика события – подпрограммы, которая выполняется при наступлении события. В нашем случае событием является выбор пункта меню Exit, и при этом вызывается обработчик exitMenuItemActionPerformed. Внутри него имеется всего одна строчка

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

Редактор экранных форм

Нажмём закладку Design (“дизайн”) в левой верхней части редактора исходного кода. При этом мы переключимся из режима редактирования исходного кода (активна закладка Source – “исходный код”) в режим редактирования экранной формы, как это показано на рисунке.

Редактирование экранной формы.

Вместо исходного кода показывается внешний вид экранной формы и находящиеся на ней компоненты. Справа от окна, в котором показывается экранная форма в режиме редактирования, расположены окна Palette (“палитра”) палитры компонентов и окно Properties (“свойства”) показа и редактирования свойств текущего компонента.

Свойство – это поле данных, которое после изменения значения может проделать какое-либо действие. Например, при изменении значения ширины компонента отрисовать на экране компонент с новой шириной. “Обычное” поле данных на такое не способно. Таким образом, свойство – это “умное поле данных”.

Палитра компонентов предназначена для выбора типа компонента, который нужен программисту для размещения на экранной форме. Например, добавим на нашу форму компонент типа JButton (сокращение от Java Button – “кнопка Java”). Для этого щёлкнем мышью по пункту JButton на палитре и передвинем мышь в нужное место экранной формы. При попадании мыши в область экранной формы на ней появляется кнопка стандартного размера, которая передвигается вместе с мышью. Щелчок в нужном месте формы приводит к тому, что кнопка остаётся в этом месте. Вокруг неё показываются рамка и маленькие квадратики, обозначающие, что наш компонент является выделенным. Для него осуществляется показ и редактирование свойств в окне Properties.

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

По умолчанию надписи на компонентах задаются как имя типа, после которого идёт номер компонента. Но вместо заглавной буквы, в отличие от имени типа, используется строчная. Поэтому первая кнопка будет иметь надпись jButton1, вторая – jButton2, и так далее. Такие же имена будут приобретать автоматически создаваемые в исходном коде переменные, соответствующие кнопкам.

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

Введём на кнопке надпись “OK” – используем эту кнопку для выхода из программы.

Редактирование свойств компонента

Размер компонента задаётся мышью путём хватания за рамку и расширения или сужения по соответствующим направлениям. Установка на новое место – перетаскиванием компонента мышью.

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

В правой части каждой ячейки имеется кнопка с надписью “…” – в современных операционных системах принято добавлять три точки в названии пунктов меню и кнопок, после нажатия на которые открывается диалоговое окно. В данном случае раскрывается окно специализированного редактора соответствующего свойства, если он существует.

Если требуется просматривать и редактировать большое количество свойств компонента, бывает удобнее щёлкнуть правой кнопкой мыши по нужному компоненту и в появившемся всплывающем меню выбрать пункт “Properties”. В этом случае откроется отдельное окно редактирования свойств компонента. Можно держать открытыми одновременно произвольное количество таких окон.

Булевские свойства в колонке значений свойств показываются в виде кнопок выбора checkbox – квадратиков с возможностью установки галочки внутри. Если галочки нет, значение свойства false, если есть – true.

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

Название свойства Что оно задаёт
background Цвет фона
componentPopupMenu Позволяет назначать всплывающее меню, появляющееся по нажатию правой кнопкой мыши в области компонента.
font Фонт, которым делается надпись на компоненте.
foreground Цвет фонта, которым делается надпись на компоненте.
icon Картинка, которая рисуется на компоненте рядом с текстом.
text Текст (надпись) на компоненте.
toolTipText Всплывающая подсказка, появляющаяся через некоторое время при наведении курсора мыши на компонент.
border Тип рамки вокруг компонента.
borderPainted Рисуется ли рамка вокруг компонента.
contentAreaFilled Имеется ли заполнение цветом внутренней области компонента (для кнопок оно создаёт эффект трёхмерности, без заполнения кнопка выглядит плоской).
defaultCapable Способна ли кнопка быть “кнопкой по умолчанию”: при нажатии автоматически происходит нажатие “кнопки по умолчанию” (такая кнопка на экранной форме должна быть одна).
enabled Доступен ли компонент. По умолчанию все создаваемые на форме компоненты доступны. Недоступные компоненты рисуются более блеклыми красками.

В качестве примера добавим всплывающую подсказку для нашей кнопки: введём текст “Эта кнопка предназначена для выхода из программы” в поле, соответствующее свойству toolTipText. К сожалению, подсказка может быть только однострочной – символы перевода на новую строку при выводе подсказки игнорируются, даже если они заданы в строке программным путём.

Наконец, зададим действие, которое будет выполняться при нажатии на кнопку – обработчик события (event handler) нажатия на кнопку. Для этого сначала выделим кнопку, после чего щёлкнем по ней правой кнопкой мыши, и в появившемся всплывающем меню выберем пункт Events/Action/actionPerformed.

Назначение обработчика события

Events означает “События”, Action – “Действие”, actionPerformed – “выполненное действие”.

После этого произойдёт автоматический переход в редактор исходного кода, и там появится заготовка обработчика события:

// TODO add your handling code here:

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

Рядом с обработчиком jButton1ActionPerformed будет расположен уже имеющийся обработчик события, срабатывающий при нажатии на пункт меню “Выход”:

private void exitMenuItemActionPerformed(java.awt.event.ActionEvent evt) {

Заменим в нашем обработчике события строку с комментарием на код, вызывающий выход из программы:

private void jButton1ActionPerformed(java.awt.event.ActionEvent evt) {

Теперь после запуска нашего приложения подведение курсора мыши к кнопке приведёт к появлению всплывающей подсказки, а нажатие на кнопку – к выходу из программы.

Часто встречающийся случай – показ сообщения при наступлении какого-либо события, например – нажатия на кнопку. Этом случае вызывают панель с сообщением:

javax.swing.JOptionPane.showMessageDialog(null,"Меня нажали");

Если классы пакета javax.swing импортированы, префикс javax.swing при вызове не нужен.

Внешний вид приложения

На этапе редактирования приложения внешний вид его компонентов соответствует платформе. Однако после запуска он становится совсем другим, поскольку по умолчанию все приложения Java показываются в платформо-независимом виде.:

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

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

Для того, чтобы показать приложение в платформо-ориентированном виде (то есть в том виде, который использует компоненты и настройки операционной системы), требуется изменить код конструктора приложения, вставив перед вызовом метода initComponents задание типа пользовательского интерфейса (User’s Interface, сокращённо UI):

import javax.swing.*;

import java.awt.*;

public GUI_application() {

UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());

}catch(Exception e){};

initComponents();

Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();

Dimension frameSize = getSize();

setLocation(new Point((screenSize.width-frameSize.width)/2,

(screenSize.height-frameSize.width)/2)

Внешний вид запущенного приложения с платформо-ориентированным пользовательским интерфейсом в операционной системе Windows ® XP

Код, следующий после вызова initComponents(), предназначен для установки окна приложения в центр экрана.

Имеется возможность задания ещё одного платформо-независимого вида приложения – в стиле Motiff, используемого в операционной системе Solaris ® . Для установки такого вида вместо вызова

UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()

Следует написать

UIManager.setLookAndFeel("com.sun.java.swing.plaf.motif.MotifLookAndFeel");

Внешний вид запущенного приложения с платформо-независимым пользовательским интерфейсом в стиле Motiff

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

Ведение проектов

Для того, чтобы не запутаться в разных проектах и их версиях, особенно с учётом того, что учебные проекты бывает необходимо часто переносить с одного компьютера на другой, следует серьёзно отнестись к ведению проектов. Автором в результате многолетней практики работы с разными языками и средами программирования выработана следующая система (откорректированная в применении к среде NetBeans):

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

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

· Если берётся проект с существующим исходным кодом, его папка копируется в папку нашего архива либо вручную, либо выбором соответствующей последовательности действий в мастере создания проектов NetBeans.

· При получении сколько-нибудь работоспособной версии проекта следует делать его архивную копию. Для этого в открытом проекте в окне “Projects” достаточно щелкнуть правой кнопкой мыши по имени проекта, и в появившемся всплывающем меню выбрать пункт “Copy Project”. Откроется диалоговая форма, в которой предлагается автоматически образованное имя копии – к первоначальному имени проекта добавляется подчёркивание и номер копии. Для первой копии это _1, для второй _2, и так далее. Причём головная папка архива по умолчанию остаётся той же, что и у первоначального проекта. Что очень удобно, поскольку даёт возможность создавать копию всего тремя щелчками мышки без набора чего-либо с клавиатуры.

Создание рабочей копии проекта

Скопированный проект автоматически возникает в окне “Projects”, но не становится главным. То есть вы продолжаете работать с прежним проектом, и все его открытые окна сохраняются. Можно сразу закрыть новый проект – правой кнопкой мыши щёлкнуть по его имени, и в появившемся всплывающем меню выбрать пункт “Close Project”.

Для чего нужна такая система ведения проектов? Дело в том, что у начинающих программистов имеется обыкновение разрушать результаты собственного труда. Они развивают проект, не сохраняя архивов. Доводят его до почти работающего состояния, после чего ещё немного усовершенствуют, затем ещё – и всё перестаёт работать. А так как они вконец запутываются, восстановить работающую версию уже нет возможности. И им нечего предъявить преподавателю или начальнику!

Поэтому следует приучиться копировать в архив все промежуточные версии проекта, более работоспособные, чем уже сохранённые в архив. В реальных проектах трудно запомнить все изменения, сделанные в конкретной версии, и, что важнее, все взаимосвязи, вызвавшие эти изменения. Поэтому даже опытным программистам время от времени приходится констатировать: “Ничего не получается!” И восстанавливать версию, в которой ещё не было тех нововведений, которые привели к путанице. Кроме того, часто бывает, что новая версия в каких-то ситуациях работает неправильно. И приходится возвращаться на десятки версий назад в поисках той, где не было таких “глюков”. А затем внимательно сравнивать работу двух версий, выясняя причину неправильной работы более поздней версии. Или убеждаться, что все предыдущие версии также работали неправильно, просто ошибку не замечали.

  • Внешняя политика СССР в годы войны. Ленд-лиз. Тегеранская конференция. Ялтинская и Потсдамская конференции 1945 г. Создание ООН.
  • Внешняя политика СССР в годы войны.Ленд-лиз. Тегеранская конференция. Ялтинская и Потсдамская конференция 1945г.Создание ООН.
  • Возобновелние деятельности БСГ. Создание белнац партий и организаций

  • Похожие статьи