Статьи

Эксперимент с Swing Application Framework

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

Библиотека Swing Application Framework предназначена для помощи в разработке настольных приложений, содержащих графический интерфейс. Это небольшой набор классов Java, называемый «каркас», который предоставляет готовую инфраструктуру для большинства приложений для настольных компьютеров. Использование этой библиотеки позволяет сделать архитектуру приложения более очевидной, а код — более легким для понимания и дальнейшей поддержки. Кроме того, этот фреймворк выполняет все рутинные операции с настройкой графических компонентов и позволяет разработчику сосредоточиться на других аспектах разработки приложений (реализация бизнес-логики и т. Д.).

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

Первоначально мы реализовали демонстрационное приложение, используя стандартную библиотеку Java Swing. Тем не менее, мы всегда ищем новые пути и технологии, которые могут помочь нам улучшить нашу продукцию. Следя за разработкой Swing Application Framework, который призван помочь в разработке приложений для настольных компьютеров, мы решили попробовать его и переписать существующее приложение на основе Swing. Целью этого «эксперимента» было исследование возможностей фреймворка и, возможно, его дальнейшее использование при разработке настольных приложений на основе Java.

Итак, у нас было приложение на основе Swing, и мы решили написать точно такое же, но с использованием Swing Application Framework.

Жизненный цикл приложения

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

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

Чтобы запустить наше приложение, нам просто нужно было унаследовать основной класс приложения от SingleFrameApplication и вызвать функцию Application.launch в методе main. Нам не нужно было оборачивать код, запускающий приложение, в поток диспетчеризации событий. Этот функционал уже реализован в функции Application.launch.

public static void main(String[] args) throws Exception {
launch(JxCaptureDemo.class, args);
}

Мы были приятно удивлены тем, что внешний вид по умолчанию зависит от системы.

Ресурсы

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

Мы использовали этот механизм при настройке всех графических компонентов нашего приложения. Например, в диалоге «О программе» мы использовали «внедрение ресурсов» для настройки большинства компонентов этого диалога. Значения параметров для компонентов находятся в специальном файле ресурсов — AboutDialog.properties. Такой подход особенно удобен для смешанного интерфейса. Кроме того, все данные для графических компонентов хранятся в одном месте, что упрощает поиск и замену любых параметров.

Ресурсы могут быть использованы для инициализации полей класса, а также для настройки параметров компонентов. Нам просто нужно было пометить обязательные поля аннотацией @Resource и установить значения в файле свойств для этих полей. После выполнения команды ResourceMap.injectFields все отмеченные поля были инициализированы автоматически. В большинстве случаев поля были простого типа (String, int и т. Д.). Но в классе ApplicationSettings мы использовали этот подход для инициализации полей, представляющих строковые массивы. Для этого нам нужно было только определить в файле свойств следующее содержимое:

ApplicationSettings.imageFormats[0] = png
ApplicationSettings.imageFormats[1] = jpeg
ApplicationSettings.imageFormats[2] = bmp
ApplicationSettings.imageFormats[3] = gif

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

public final class ApplicationSettings {
@Resource
private String[] imageFormats;

// ...

private ApplicationSettings() {
ApplicationContext context = Application.getInstance().getContext();
ResourceMap resourceMap = context.getResourceMap(ApplicationSettings.class);
imageFormats = new String[resourceMap.getInteger("ApplicationSettings.imageFormats.length")];
resourceMap.injectFields(this);
}

// ...
}

Как видите, мы должны были выделить память для поля imageFormats до инициализации этого поля. Это немного неудобно, потому что мы должны знать точное количество компонентов массива. В нашем случае мы решили сохранить длину массива в ресурсе «ApplicationSettings.imageFormats.length». Надеемся, что разработчики сделают выделение памяти как часть внедрения в будущем.

действия

Каждое настольное приложение основано на модели событий. Используя аннотацию @Action в SAF, мы можем пометить метод, который предназначен для реализации метода ActionPerformed. Метод ApplicationContext getActionMap создает ActionMap, который содержит один объект Action для каждого @Action, определенного некоторым классом. Аннотация @Action в нашем коде позволяет нам сделать его более понятным и легким для восприятия. Так, например, реализация событий в классе TrayPopupMenu с SAF имеет следующий вид:

public class TrayPopupMenu extends JPopupMenu {
private ApplicationSettings settings = ApplicationSettings.getInstance();
private CaptureOperations operations = CaptureOperations.getInstance();
private boolean captureOperationEnabled = true;

// ...

public boolean isCaptureOperationEnabled() {
return captureOperationEnabled;
}

public void setCaptureOperationEnabled(boolean captureOperationEnabled) {
boolean oldValue = this.captureOperationEnabled;
this.captureOperationEnabled = captureOperationEnabled;
firePropertyChange("captureOperationEnabled", oldValue, this.captureOperationEnabled);
}

@Action (enabledProperty = "captureOperationEnabled")
public void activeWindowCapture() {
operations.activeWindowCapture();
}

@Action (enabledProperty = "captureOperationEnabled")
public void objectCapture() {
operations.objectCapture();
}

// ...
}

Реализация того же класса с использованием стандартного подхода требует на 30% больше строк кода! Параметры для каждого события мы описали в соответствующем файле свойств:

activeWindowCapture.Action.text = Capture active window
activeWindowCapture.Action.icon = images/act_window.png
activeWindowCapture.Action.accelerator = control shift A

objectCapture.Action.text = Capture window / object
objectCapture.Action.icon = images/win_obj.png
objectCapture.Action.accelerator = control shift W
...

Эти @Actions также вводят параметр аннотации enabledProperty, который связывает включенное состояние @Action с текущим значением свойства. Наши настройки приложения позволяют установить назначаемую задержку перед операцией захвата, чтобы пользователь мог предпринять некоторые действия перед выполнением этой операции, например, он может открывать / закрывать некоторые окна. Рекомендуется ограничить для пользователя выполнение других операций во время этой задержки. Это можно реализовать, например, отключив их. В нашем примере мы имеем именно этот случай.

Параметр captureOperationEnabled проверяет, запущена ли какая-либо операция в данный момент. Благодаря записи @Action (enabledProperty = «captureOperationEnabled») мы связываем состояние переменной captureOperationEnabled с состоянием события (в нашем случае с состоянием всех событий, которые следует отключить, когда уже выполняется одна операция) , Это очень удобно и просто!

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

Резюме

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

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

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

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

Полезные ссылки