Java графический интерфейс примеры. Интерфейсы и множественное наследование

В Java есть 2 основных пакета для создания графических интерфейсов (Graphics User Interface). Это Abstract Windows Toolkit (AWT) и Swing. AWT использует виджеты операционной системы, поэтому эта библиотека немного быстрее. Но на мой взгляд, Swing более хорошо спроектирован.

В данном туториале мы рассмотрим основные элементы библиотеки Swing и создадим простой интерфейс (GUI) в качестве примера.

Для группировки компонент интерфейса используются контейнеры (Container). Для создания основного контейнера для приложения чаще всего используется контейнер JFrame (есть еще JWindows и JApplet). Проще всего унаследоваться от JFrame тем самым получить доступ ко множеству методов, например:

setBounds(x, y, w, h) - указывает координаты верхней левой вершины окна, а также его ширину и высоту.

setResizable(bool) - указывает, можно ли изменять размер окна.

setTitle(str) - устанавливает название окна.

setVisible(bool) - собственно отображает окно.

setDefaultCloseOperation(operation) - указывает операцию, которая будет произведена при закрытии окна.

Основные элементы управления:

  • JLabel - элемент для отображения фиксированного текста;
  • JTextField - простой edit-box;
  • JButton - обычная кнопка (button);
  • JCheckBox - элемент выбора (аналог checkbox);
  • JRadioButton - радио кнопка

Как видите, все довольно просто и логично.

При отображении элементов управления используются специальные менеджеры - LayoutManager. У всех LayoutManager"ов есть методы для добавления у удаления элементов.

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

GridLayout - отображения элементов в виде таблицы с одинаковыми размерами ячеек.

BorderLayout - используется при отображении не более 5 элементов. Эти элементы располагаются по краям фрейма и в ценрте: North, South, East, West, Center.

BoxLayout - отображает элементы в виде рядка или колонки.

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

Стоит еще обратить внимание на обработку событий. Для этого используются так называемые Event Listeners.

Ну все, довольно теории, перейдем к примеру GUI:

Import java.awt.*; import java.awt.event.*; import javax.swing.*; public class SimpleGUI extends JFrame { private JButton button = new JButton("Press"); private JTextField input = new JTextField("", 5); private JLabel label = new JLabel("Input:"); private JRadioButton radio1 = new JRadioButton("Select this"); private JRadioButton radio2 = new JRadioButton("Select that"); private JCheckBox check = new JCheckBox("Check", false); public SimpleGUI() { super("Simple Example"); this.setBounds(100,100,250,100); this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); Container container = this.getContentPane(); container.setLayout(new GridLayout(3,2,2,2)); container.add(label); container.add(input); ButtonGroup group = new ButtonGroup(); group.add(radio1); group.add(radio2); container.add(radio1); radio1.setSelected(true); container.add(radio2); container.add(check); button.addActionListener(new ButtonEventListener()); container.add(button); } class ButtonEventListener implements ActionListener { public void actionPerformed(ActionEvent e) { String message = ""; message += "Button was pressed\n"; message += "Text is " + input.getText() + "\n"; message += (radio1.isSelected()?"Radio #1":"Radio #2") + " is selected\n"; message += "CheckBox is " + ((check.isSelected()) ?"checked":"unchecked"); JOptionPane.showMessageDialog(null, message, "Output", JOptionPane.PLAIN_MESSAGE); } } public static void main(String args) { SimpleGUI app = new SimpleGUI(); app.setVisible(true); } }

Примечания:

getContentPane возвращает контейнер верхнего уровня. ButtonGroup служит для создания группы взаимосвязанных радио-кнопок.

Внутренний класс ButtonActionListener реализует интерфейс ActionListener. Для этого необходимо предоставить имплементацию метода actionPerformed.

JOptionPane служит для отображения диалоговых окон.

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

What is Swing?

Java Swing is a lightweight Graphical User Interface (GUI) toolkit that includes a rich set of widgets. It includes package lets you make GUI components for your Java applications, and It is platform independent.

The Swing library is built on top of the Java Abstract Widget Toolkit (AWT ), an older, platform dependent GUI toolkit. You can use the Java GUI components like button, textbox, etc. from the library and do not have to create the components from scratch.

In this tutorial, you will learn-

Java Swing class Hierarchy Diagram

All components in swing are JComponent which can be added to container classes.

What is a container class?

Container classes are classes that can have other components on it. So for creating a GUI, we need at least one container object. There are 3 types of containers.

  1. Panel : It is a pure container and is not a window in itself. The sole purpose of a Panel is to organize the components on to a window.
  2. Frame : It is a fully functioning window with its title and icons.
  3. Dialog : It can be thought of like a pop-up window that pops out when a message has to be displayed. It is not a fully functioning window like the Frame.

Java GUI Example

Example : To learn to design GUI in Java
Step 1) Copy the following code into an editor

Import javax.swing.*; class gui{ public static void main(String args){ JFrame frame = new JFrame("My First GUI"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setSize(300,300); JButton button = new JButton("Press"); frame.getContentPane().add(button); // Adds Button to content pane of frame frame.setVisible(true); } }

Step 2) Save, Compile, and Run the code.
Step 3) Now let"s Add a Button to our frame. Copy following code into an editor

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

Step 4) Execute the code. You will get a big button

Step 5) How about adding two buttons? Copy the following code into an editor.

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

Step 6) Save , Compile , and Run the program.
Step 7) Unexpected output =? Buttons are getting overlapped.

Java Layout Manger

The Layout manager is used to layout (or arrange) the GUI java components inside a container.There are many layout managers, but the most frequently used are-

Java BorderLayout

A BorderLayout places components in up to five areas: top, bottom, left, right, and center. It is the default layout manager for every java JFrame

Java FlowLayout

FlowLayout is the default layout manager for every JPanel . It simply lays out components in a single row one after the other.

Java GridBagLayout

It is the more sophisticated of all layouts. It aligns components by placing them within a grid of cells, allowing components to span more than one cell.

Step 8) How about creating a chat frame like below?

Try to code yourself before looking at the program below.

//Usually you will require both swing and awt packages // even if you are working with just swings. import javax.swing.*; import java.awt.*; class gui { public static void main(String args) { //Creating the Frame JFrame frame = new JFrame("Chat Frame"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setSize(400, 400); //Creating the MenuBar and adding components JMenuBar mb = new JMenuBar(); JMenu m1 = new JMenu("FILE"); JMenu m2 = new JMenu("Help"); mb.add(m1); mb.add(m2); JMenuItem m11 = new JMenuItem("Open"); JMenuItem m22 = new JMenuItem("Save as"); m1.add(m11); m1.add(m22); //Creating the panel at bottom and adding components JPanel panel = new JPanel(); // the panel is not visible in output JLabel label = new JLabel("Enter Text"); JTextField tf = new JTextField(10); // accepts upto 10 characters JButton send = new JButton("Send"); JButton reset = new JButton("Reset"); panel.add(label); // Components Added using Flow Layout panel.add(label); // Components Added using Flow Layout panel.add(tf); panel.add(send); panel.add(reset); // Text Area at the Center JTextArea ta = new JTextArea(); //Adding Components to the frame. frame.getContentPane().add(BorderLayout.SOUTH, panel); frame.getContentPane().add(BorderLayout.NORTH, mb); frame.getContentPane().add(BorderLayout.CENTER, ta); frame.setVisible(true); } }

JFace — это доплнительный слой абстракции над графической библиотекой SWT, предоставляющий возможности для разработки графического интерфейса приложения на основе модели MVC(Model View Controller). Основные компоненты JFace включают:

  • Viewers — классы для инкапсуляции задач предоставления данных для отображения, их фильтрации, сортировки и т.п.
  • Actions and contributions — вводят семантику описания пользовательских действий и того, как они должны быть доступны пользователю.
  • Image and font registries — предоставляют классы для управления ресурсами, такими как изображения и шрифты.
  • Dialogs and wizards — фреймворк для построения сложного диалогового взаимодействия с пользователем.
  • Field assist — предоставляет возможности для реализации вспомогательной функциональности для полей, такой как отображение статуса поля или подсказки о содержимом.

В первой части будет рассмотрено создание простого окна с использованием JFace, добавление к нему меню, статусной строки и панели инструментов.

Настройка проекта

Для того, чтобы разрабатывать графические приложения с использованием JFace в Eclipse, необходимо подключить jar-файлы SWT и JFace.

  • В случае, если в Eclipse установлено PDE, то это можно сделать, указав в настройках проекта использовать библиотеку SWT, и поставить галочку для поддержки JFace.
  • Если же PDE не установлен, то необходимо подключить jar-файлы к проекту вручную. Найти их можно в директории plugins эклипса, называются они org.eclipse.swt_*.jar и org.eclipse.jface_*.jar

Создание окна

Окно приложения в JFace наследуется от класса ApplicationWindow, предоставляющего возможности для создания меню, панелей управления и строки статуса.

Простейший вариант создания окна приведен в следующем примере:

import org.eclipse.jface.window.ApplicationWindow; import org.eclipse.swt.widgets.Display; public class MainWindow extends ApplicationWindow { public static void main(String args) { MainWindow window = new MainWindow(); // Создаем наше окно window.setBlockOnOpen(true); // Устанавливаем флаг - ждать закрытия окна window.open(); // Открываем окно Display.getCurrent().dispose(); // Освобождаем ресурсы } public MainWindow() { super(null); // Вызываем конструктор родительского класса } }

Создание содержимого окна

Для того, чтобы создать какое-то содержимое окна, необходимо переопределить метод Control createContents(Composite parent) класса ApplicationWindow. Метод должен возвращать компонент, который будет являться содержимым окна.

В этом же методе можно установить и заголовок окна, вызвав метод setText(String text) шелла, доступного по вызову getShell()

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

Собственно, пример:

protected Control createContents(Composite parent) { getShell().setText("My window"); // Устанавливаем заголовок окна Label lbl = new Label(parent, SWT.NONE); // Создаем новый элемент, в нашем случае - просто метку lbl.setText("Window contents"); // Устанавливаем ей текст lbl.setBackground(new Color(Display.getCurrent(), 255, 255, 255)); // Устанавливаем белый цвет фона return lbl; }

Добавление статусной строки

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

Для того, чтобы отобразить текст в статусной строке необходимо вызвать метод setStatus(String status), аргументом которого и является та самая строка, которую необходимо отобразить.

Создание меню

Для создания строки меню в JFace необходимо, вызвать метод addMenuBar(), как и для создания строки статуса.
Но затем необходимо добавить пункты меню на панель. Осуществляется это следующим образом:

  • Раздел меню описывается классом MenuManager. В конструкторе ему может быть передана строка — имя раздела, где символ «&» означает, что следующий за ним символ будет ключом при навигации с помошью клавиши Alt. Добавляется раздел меню на панель с помощью конструкции getMenuBar().add(menu).
  • Пункт меню описывается классом Action. Сам класс является абстрактным, пункт меню должен быть унаследован от него и переопределять метод void run(), в котором размещается код, выполняемый при выборе пункта меню пользователем. На самом деле, метод можно и не переопределять, но тогда, зачем этот пункт меню нужен?=) Имя пункта может быть задано путем передачи в конструктор, или вызовом метода void setText(String text). После создания пункт меню добавляется в раздел вызовом метода add у объекта раздела меню. Например: menu.add(menuItem)
  • Подменю создаются очень просто: Необходимо в один раздел меню методом add добавить другой раздел. Вот и все.

В следующем примере мы создаем раздел меню File и один пункт Hello в нем, выбор которого вызывает появление текста в статусной строке окна. Код создания меню я оформил в один метод, который вызывается из конструктора окна:

private void createMenu() { addMenuBar(); // Добавляем панель меню MenuManager fileMenu = new MenuManager("&File"); // Создаем новое меню getMenuBarManager().add(fileMenu); // Добавляем меню на панель fileMenu.add(new Separator()); // Добавляем разделитель в меню fileMenu.add(new Action("&Hello") { // Создаем новое действие, указываем его текст @Override public void run() { // Код выполняемый при активации действия setStatus("Hello world!!"); // Мы просто отображаем новое значение в статусной строке } }); // И добавляем действие в качестве пункта меню }

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

Для того, чтобы создать панель инструментов в окне необходимо в конструкторе окна вызвать метод addToolBar(int style), в который передать стиль компонента панели.

Для доступа к созданной панели используется метод getToolBarManager(). Для добавления действия на панель используется метод add панели, куда передается действие.

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

private void createToolBar() { addToolBar(SWT.NONE); getToolBarManager().add(new Action("&Hello") { // Создаем новое действие, указываем его текст @Override public void run() { // Код выполняемый при активации действия setStatus("Hello world!!"); // Мы просто отображаем новое значение в статусной строке } }); }

Создание нескольких перемещаемых панелей инструментов

Вместо одной панели инструментов можно создать набор перемещаемых панелей (CoolBar). Для этого необходимо:

  1. Вместо метода addToolBar вызвать метод addCoolBar
  2. Для доступа к набору панелей используется метод getCoolBarManager()
  3. Создать панель инструментов, на которую можно будет добавлять действия. Для этого необходимо создать новый экземпляр класса ToolBarManager, который будет представлять панель и вызвать метод add у CoolBarManager, передав туда панель.
  4. На созданную панель можно добавлять действия

Пример кода, создающего две плавающие панели:

private void createCoolbar() { addCoolBar(SWT.NONE); ToolBarManager tm = new ToolBarManager(); getCoolBarManager().add(tm); tm.add(helloAction); ToolBarManager tm2 = new ToolBarManager(); getCoolBarManager().add(tm2); tm2.add(helloAction); tm2.add(helloAction); }

Важно! Окно приложение может иметь или одну панель управления или набор перемещаемых, попытка создания и того и другого приведет к ошибке!

Возможные проблемы

Здесь будут описаны некоторые проблемы, с которыми можно столкнуться:

  • При добавлении пунктов меню после запуска приложения они не появляются — чтобы изменения меню после создания окна отобразились на нем, необходимо вызвать метод getMenuBarManager().update(true) — указать обновить панель меню.

Ссылки

Дополнительная информация по JFace может быть найдена по следующим ссылкам:

  • Русскоязычная вводная статья на ibm.com
  • Цикл англоязычных статей на ibm.com
  • Описание API Eclipse — среди прочего там есть и пакеты, относящиеся к SWT и JFace

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