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

Так исторически сложилось, что с 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 ) ;

Предуведомление

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

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

На предыдущем занятии () мы сформировали и вывели на экран монитора графическое окно и попутно решили некоторые задачи, касающиеся его внешнего вида и расположения. Теперь мы обсудим то, что осталось «за кадром».

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

import java.awt.*;

import javax.swing.*;

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

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

Пакеты включают в себя необходимые классы и интерфейсы (об интерфейсах мы расскажем в свое время), обеспечивающие ту или иную функциональность будущего приложения. Наличие звездочки («*») указывает на то, что программист импортирует все содержимое пакета, без точного указания входящих в него классов или интерфейсов. Может показаться, что в случае больших пакетов, итоговый объектный код может оказаться чересчур большим, но беспокоиться не стоит: компилятор Java достаточно «умен», чтобы использовать только то, что действительно необходимо вашей программе; все, что программе не нужно, компилятор включать в объектный код попросту не станет. Если хотите, вы можете использовать несколько иную форму подключения пакетов, например,

import java.awt.Window;

import javax.swing.JFrame;

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

Импортирование (подключение) пакета к разрабатываемой программе производится ключевым словом import, после которого идет имя пакета. Каждый пакет должен импортироваться отдельно (т.е. нельзя написать import java.awt.*, javax.swing.*;). Конечно, объем исходного кода при этом несколько увеличивается, но очень незначительно. Все классы и интерфейсы, составляющие вашу программу, должны располагаться строго после конструкций import, иначе компилятор сгенерирует сообщение об ошибках компиляции.

Первый пакет (начинающийся с java.awt) обеспечивает прежде всего взаимодействие программ на Java с графической подсистемой операционной системы. Вспомните, что Java является кроссплатформенным языком программирования и генерирует единый объектный код вне зависимости от операционной системы, на которой этот код будет выполняться. Поэтому Java «вынуждена» обращаться к ресурсам, предоставляемым той операционной системой, которая установлена на компьютере пользователя. Один из таких ресурсов - графика (кроме графики Java обращается к «услугам» операционной системы для доступа к файловой системе и другим ресурсам). Так вот, пакет java.awt.* позволяет графическим интерфейсам, написанным на Java задействовать графические возможности операционной системы и отображаться графическим объектам, созданным в программах Java, на экране монитора. Здесь мы немного задержимся.

Все графические компоненты в Java подразделяются на две категории: легковесные (lightweight) и тяжеловесные (heavyweight). Подавляющее большинство графических компонентов (кнопки, списки, деревья, метки, таблицы и т.д.) являются легковесными. Это означает, что операционная система абсолютно ничего о них не знает и даже не «подозревает» об их существовании. Легковесные компоненты принадлежат окнам (подобных тому, что мы выводили на предыдущем занятии) и отображаются в этих окнах.

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

JButton buttonPressMe = new JButton («Нажми меня»);

и разместить ее в указанном месте окна (соответствующие детали будут описаны позже). Кнопка – легковесный объект и операционная система не имеет о кнопке никаких сведений (для операционной системы кнопка вообще не существует). О кнопке «знает» только окно, в котором она размещена. Окно отрисовывает кнопку, перехватывает события, происходящие с кнопкой, перерисовывает кнопку если она была чем-либо заслонена и т.д. А вот само окно как таковое – о нем операционная система осведомлена и именно потому, что окно – тяжеловесный компонент. Только окно имеет доступ к ресурсам операционной системы (в частности – графическим).

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

Dimension sSize = Toolkit.getDefaultToolkit ().getScreenSize ()

Что может предоставить такую информацию? Операционная система и только она! Далее, мы использовали код

try {UIManager.setLookAndFeel

(UIManager.getSystemLookAndFeelClassName ());

catch (Exception lfe) {}

для того, чтобы внешний вид окна соответствовал стандарту, принятому в конкретной операционной системе. Что может предоставить такую информацию? Опять же – операционная система! Так что для создания графических интерфейсов без пакета java.awt.* нам не обойтись.

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

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

public class MoneyForNothing extends JFrame {

Здесь новыми являются два элемента: ключевое слово extends и слово JFrame.

Ключевое слово extends (в переводе означает «расширить», но по смыслу близко к слову «наследует» или к словосочетанию «заимствовать свойства и поведение») выражает фундаментальную концепцию объектно-ориентированного программирования (или проще говоря, программирования, основанного на классах). Эта концепция носит название «наследование». С этим надо хорошенько разобраться.

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

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

В программировании такой предок обычно называется «родитель» или «суперкласс», т.е. класс, от которого произошли другие классы. Имя суперкласса указывается непосредственно после extends. Таким образом, в переводе на обычный язык, вышеприведенный фрагмент кода можно прочитать так: «класс... расширяет класс JFrame», «класс... наследует класс JFrame» или «класс... заимствует свойства и поведение класса JFrame». В классе JFrame определены основные свойства и поведение «стандартных» графических окон. Сам класс JFrame находится в пакете javax.swing.* и именно его мы и импортировали в начале программы.

JFrame – это родитель (в терминологии Java – суперкласс) графических окон (есть еще и другие окна, например, диалоговые, но о них мы поговорим в свое время). Если обратиться к документации по Java , то в ней вы обнаружите, что в классе JFrame имеются несколько конструкторов, полей и около двух десятков методов, которые определяют поведение некоего «стандартного» окна. Таким образом, наш класс с именем MoneyForNothing является наследником класса JFrame (обычно говорят не о наследниках, а о потомках или о дочерних классах).

Обратите внимание, что класс JFrame сам в свою очередь является наследником нескольких классов (большая часть которых принадлежит уже знакомому нам пакету java.awt.*):

Чтобы уже не возвращаться к этому вопросу, обращаем ваше внимание на то, что в вершине иерархии наследования Java лежат классы из пакета java.lang.*. Это единственный пакет из JDK, который не нужно явно импортировать – он всегда импортируется автоматически. Судя по этой «лесенке», класс JFrame является пра-пра-правнуком java.lang.Object (картинка, приведенная выше по-научному называется иерархией классов).

Все компоненты графического пользовательского интерфейса (сокращенно GUI, от Graphical User Interface), которые, напомним, являются легковесными элементами, должны быть размещены внутри основного окна – наследника JFrame. Сейчас у нас никаких компонентов нет, но скоро они появятся – обещаем.

А теперь «пробежимся» напоследок по нашему исходному коду, который выглядит так:

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

public MoneyForNothing () {

setTitle ("Добро пожаловать в Money for Nothing");

setSize (new Dimension (600, 400));

Dimension sSize = Toolkit.getDefaultToolkit ().getScreenSize (),

fSize = getSize ();

if (fSize.height > sSize.height) {fSize.height = sSize.height;}

if (fSize.width > sSize.width) {fSize.width = sSize.width;}

setLocation ((sSize.width - fSize.width)/2,

(sSize.height - fSize.height)/2);

setDefaultCloseOperation (EXIT_ON_CLOSE);

setVisible (true);

Прежде всего, мы задаем заголовок окна (метод setTitle («...»)). Затем задаются размеры окна по горизонтали и вертикали (метод setSize (...)). После определения разрешения экрана монитора расчитываются координаты верхнего левого угла нашего окна и окно выводится (метод setLocation (...)) в указанном месте экрана.

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

Наконец, последняя строка (метод setVisible (true)) делает окно видимым.

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

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

Удачи и до скорой встречи!

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

И так, какие инструменты нам необходимы:

  • Java Virtual Machine (OpenJDK или Oracle JDK)
  • Intellij IDEA (или другое IDE для Java)

После установки необходимого софта, открываем Intellij IDEA и создаем новый проект: File -> New Project…

Я назвал проект guiBase . Как видно на скрине, папка src не содержит ничего, поэтому создаем в ней наш главный класс, содержащий функцию main .

Public class Main { public static void main(String args) { System.out.println("Hello, Govzalla!"); } }

Содеражние главного класса видите выше. Мы уже сейчас можем создать проект (Build project ) и запустить его (Run ). Внизу в терминале вашего IDE вы увидите сообщение “Hello, Govzalla!“ . Но как вы сами поняли - GUI он не поддерживает.

На данном этапе у нас уже есть работающая программа, но без поддержки GUI. А сейчас в той же папке src создадим GUI Form : New -> GUI Form

Открываем созданную GUI форму, нажимаем на JPanel и задаем его идентификатор в поле field name , я задал panel .

После чего перетаскиваем на форму с правой стороны JTextField , JPasswordField и JButton :

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

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

В данный момент мы имеем форму MainWindow и класс MainWindow расширенный с помощью JFrame . Сейчас нам необходимо определить все добавленные GUI элементы как содержание класса MainWindow this.getContentPane().add(panel); После чего содержание файла MainWindow.java будет изменено следующим образом:

Import javax.swing.*; public class MainWindow extends JFrame { private JTextField textField1; private JPasswordField passwordField1; private JButton button1; private JPanel panel; public MainWindow() { this.getContentPane().add(panel); } }

Если попробуете запустить код, вы снова увидите то же самое сообщение “Hello, Govzalla!“. Дело в том, что мы создали класс и форму к нему, но не создали инстанцию этого класса.

Пришло время изменить файл Main.java и добавить туда код создания нашего GUI:

Import java.awt.*; public class Main { public static void main(String args) { // Создаем инстанцию класса MainWindow MainWindow mainWindow = new MainWindow(); // Упаковываем все элементы с нашей формы mainWindow.pack(); // Изменяем размеры окна mainWindow.setSize(new Dimension(200, 200)); // Отображаем созданное окно mainWindow.setVisible(true); } }

Запускаем код

Нажав на кнопку Button вы заметите, что программа никак не реагирует. Дело в том, что мы еще не добавили слушатель (Listener ) для событий (Events ) кнопки Button.

Слушатель событий (Event listener ) JButton должен быть имплентацией адаптера ActionListener , поэтому добавим следующий код в тело класса MainWindow :

Private class MyButtonListener implements ActionListener { @Override public void actionPerformed(ActionEvent actionEvent) { } }

Метод actionPerformed () будет обрабатывать все события кнопки button1, но для начала еще необходимо указать кнопке button1 какой класс будет обрабатывать, поэтому добавим следующий код в конструктор класса MainWIndow: this.button1.addActionListener(new MyButtonListener()); Чтобы наш обработчик не был бессмысленным добавим следующий код в метод actionPerformed ():

@Override public void actionPerformed(ActionEvent actionEvent) { if (textField1.getText().equals(passwordField1.getText())) { JOptionPane.showMessageDialog(null, "Success"); } else { JOptionPane.showMessageDialog(null, "Failure"); } }

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

Давыдов Антон Валериевич
Студент ТГУ, Россия, г. Тольятти
Научный руководитель: Ерофеева Е.А.

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

Abstract Window Toolkit

Abstract Window Toolkit (сокращённо AWT) впервые была выпущена в 1995 году компанией Sun Microsystems. Это была первая попытка создать графический интерфейс для Java. AWT выступал в качестве прослойки, вызывающей методы из библиотек, написанных на С. А эти методы, в свою очередь, использовали графические компоненты операционной системы . С одной стороны, программа, построенная таким образом, внешне была похожа на все остальные программы в используемой операционной системе, но с другой, одна и та же программа может выглядеть совершенно по-разному на разных операционных системах, что осложняло разработку. К тому же, ради мультиплатформенности пришлось унифицировать интерфейсы вызовов компонентов, что привело к несколько урезанной функциональности. Набор компонентов также довольно скромный. Например, отсутствуют таблицы, а в кнопки нельзя поместить иконки. AWT старается автоматически освобождать использованные ресурсы. Это влияет на производительность и усложняет архитектуру. AWT прост для освоения, но написание чего-то сложного вызывает затруднения. Сейчас AWT используется в основном для аплетов. Oracle в данный момент поощряет переход разработчиков на Swing, как более безопасный.

Рис.1 – Образец программы, написанной с использованием AWT в среде Windows

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

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


Рис.2 – Образец программы, написанной с использованием Swing

Standard Widget Toolkit

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

Рис.3 – Образец программы, написанной с использованием Swing

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

Таким образом, описав основным особенности и недостатки вышеперечисленных графических пользовательских интерфейсов, мы можем решить, для каких задач они лучше подходят. Abstract Window Toolkit больше подойдёт для создания аплетов. Новичку можно порекомендовать Swing в виду того, что для него можно найти огромное количество документации в интернете, в том числе и на русском языке. Для создания насыщенных интернет-приложений отлично подойдёт JavaFX.

Список использованных источников

    Рыженко А. В. Объектно-ориентированное программирование: Учебно-методический комплекс по дисциплине для специальности 010501 – "Прикладная математика и информатика". – 2007.

    Хабибуллин И. Ш. Java 7 (4-е изд.). – БХВ-Петербург, 2012.

    Clarke J., Connors J., Bruno E. J. JavaFX: Developing Rich Internet Applications. – Pearson Education, 2009.

    Northover S., Wilson M. Swt: the standard widget toolkit, volume 1. – Addison Wesley Professional, 2004.

Пользовательский интерфейс на 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); }

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