Найти тему
Сам себе джавист

Введение в FXML

Оглавление

Мне очень нравятся декларативные языки описания интерфейса, такие как:

HTML, FXML, XAML, QML, XML

Они просты, строги и, не взирая на XML, лаконичны.

Просмотрев много сайтов по работе с FXML, больше всего интересующих меня подробностей я нашёл как раз в этой официальной инструкции от Oracle. Предлагаю вам её перевод.

Обзор

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

Элементы

В FXML элементы разметки XML представлены следующими элементами:

  • Экземпляр класса
  • Свойство экземпляра класса
  • "Статическое" свойство
  • Блок определения "define"
  • Блок скриптового кода

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

Элементы экземпляра класса

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

Объявления экземпляров

Если имя тега элемента начинается с заглавной буквы (и это не "статический" сеттер, описанный ниже), это считается объявлением экземпляра. Когда загрузчик FXML (также представленный позже) встречает такой элемент, он создает экземпляр этого класса.

Как и в Java, имена классов могут быть полными (включая имя пакета), или они могут быть импортированы с помощью инструкции обработки "import" (PI). Например, следующий PI импортирует javafx.scene.control.* Класс метки в пространство имен текущего документа FXML:

<?import javafx.scene.control.Label?>

Этот PI импортирует все классы из пакета javafx.scene.control в текущее пространство имен:

<?import javafx.scene.control.*?>

Любой класс, который придерживается соглашений об именовании конструкторов и свойств JavaBean, может быть легко создан и настроен с помощью FXML. Ниже приведен простой, но полный пример, который создает экземпляр javafx.scene.control.* Ярлык и устанавливает для его свойства "текст" значение "Привет, мир!":

<?import javafx.scene.control.Label?>
<Label text="Hello, World!"/>

Обратите внимание, что свойство метки "text" в этом примере задается с помощью атрибута XML. Свойства также можно задать с помощью вложенных элементов свойств. Элементы свойств более подробно обсуждаются далее в этом разделе. Атрибуты свойств обсуждаются в следующем разделе.

Классы, которые не соответствуют соглашениям Bean, также могут быть созданы в FXML с использованием объекта, называемого "builder". Строители обсуждаются более подробно позже.

Map

Внутренне загрузчик FXML использует экземпляр com.sun.javafx.fxml.BeanAdapter для обертывания созданного объекта и вызова его методов setter. Этот (в настоящее время) частный класс реализует java.util.Map интерфейс и позволяет вызывающему получать и устанавливать значения свойств компонента в виде пар ключ /значение.

Если элемент представляет тип, который уже реализует Map (например, java.util.HashMap), он не завернут, и его методы get() и put() вызываются напрямую. Например, следующий FXML создает экземпляр HashMap и устанавливает его значения "foo" и "bar" равными "123" и "456" соответственно:

<HashMap foo="123" bar="456"/>

fx:value

Атрибут fx:value можно использовать для инициализации экземпляра типа, который не имеет конструктора по умолчанию, но предоставляет статический метод valueOf(String). Например, java.lang.String, а также каждый из примитивных типов оболочки определяют метод valueOf() и могут быть сконструированы в FXML следующим образом:

<String fx:value="Hello, World!"/>
<Double fx:value="1.0"/>
<Boolean fx:value="false"/>

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

fx:factory

Атрибут fx:factory - это еще одно средство создания объектов, классы которых не имеют конструктора по умолчанию. Значение атрибута - это имя статического фабричного метода без аргументов для создания экземпляров класса. Например, следующая разметка создает экземпляр наблюдаемого списка массивов, заполненный тремя строковыми значениями:

<FXCollections fx:factory="observableArrayList">
<String fx:value="A"/>
<String fx:value="B"/>
<String fx:value="C"/>
</FXCollections>

Builder

Третьим средством создания экземпляров классов, которые не соответствуют соглашениям о компонентах (например, представляющих неизменяемые значения), является "конструктор". Шаблон проектирования builder делегирует построение объекта изменяемому вспомогательному классу (называемому "builder"), который отвечает за создание экземпляров неизменяемого типа.

Поддержка Builder в FXML обеспечивается двумя интерфейсами. Интерфейс javafx.util.Builder определяет единственный метод с именем build(), который отвечает за построение фактического объекта:

public interface Builder<T> {
public T build();
}

Интерфейс javafx.util.BuilderFactory отвечает за создание конструкторов, способных создавать экземпляры данного типа:

public interface BuilderFactory {
public Builder<?> getBuilder(Class<?> type);
}
  • Фабрика сборки по умолчанию, JavaFXBuilderFactory, предоставляется в пакете javafx.fxml.JavaFXBuilderFactory .Эта фабрика способна создавать и настраивать большинство неизменяемых типов JavaFX. Например, следующая разметка использует конструктор по умолчанию для создания экземпляра неизменяемого javafx.scene.Color .Класс цвета:
<Color red="1.0" green="0.0" blue="0.0"/>

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

<Color>
<red>1.0</red>
<green>0.0</green>
<blue>0.0</blue>
</Color>

Экземпляр Color не может быть полностью создан до тех пор, пока не будут известны все три компонента цвета.

При обработке разметки для объекта, который будет создан конструктором, экземпляры Builder обрабатываются как объекты значений - если Builder реализует интерфейс Map, метод put() используется для установки значений атрибутов конструктора. В противном случае конструктор завернут в BeanAdapter, и предполагается, что его свойства будут доступны через стандартные установщики компонентов.

<fx:include>

Тег <fx:include> создает объект из разметки FXML, определенной в другом файле. Он используется следующим образом:

<fx:include source="filename"/>

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

Например, учитывая следующую разметку:

<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<VBox xmlns:fx="http://javafx.com/fxml">
<children>
<fx:include source="my_button.fxml"/>
</children>
</VBox>

Если my_button.fxml содержит следующее:

<?import javafx.scene.control.*?>
<Button text="My Button"/>

результирующий график сцены будет содержать VBox в качестве корневого объекта с одной кнопкой в качестве дочернего узла.

Обратите внимание на использование префикса пространства имен "fx". Это зарезервированный префикс, который определяет ряд элементов и атрибутов, используемых для внутренней обработки исходного файла FXML. Обычно он объявляется в корневом элементе документа FXML. Другие функции, предоставляемые пространством имен "fx", описаны в следующих разделах.

<fx:include> также поддерживает атрибуты для указания имени пакета ресурсов, который должен использоваться для локализации включаемого содержимого, а также набора символов, используемого для кодирования исходного файла. Разрешение ресурсов обсуждается в следующем разделе.

<fx:constant>

Элемент <fx:constant> создает ссылку на константу класса. Например, следующая разметка устанавливает значение свойства "MinWidth" экземпляра Button равным значению константы NEGATIVE_INFINITY, определенной java.lang.Double класс:

<Button>
<minHeight><Double fx:constant="NEGATIVE_INFINITY"/></minHeight>
</Button>

<fx:reference>

Элемент <fx:reference> создает новую ссылку на существующий элемент. Где бы ни появлялся этот тег, он будет эффективно заменен значением именованного элемента. Он используется в сочетании либо с атрибутом fx:id, либо с переменными скрипта, оба из которых более подробно обсуждаются в последующих разделах. Атрибут "source" элемента <fx:reference> задает имя объекта, на который будет ссылаться новый элемент.

Например, следующая разметка присваивает ранее определенный экземпляр изображения с именем "myImage" свойству "image" элемента управления ImageView:

<ImageView>
<image>
<fx:reference source="myImage"/>
</image>
</ImageView>

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

<ArrayList>
<fx:reference source="element1"/>
<fx:reference source="element2"/>
<fx:reference source="element3"/>
</ArrayList>

В большинстве других случаев использование атрибута проще и лаконичнее.

<fx:copy>

Элемент <fx:copy> создает копию существующего элемента. Как и <fx:reference>, он используется с атрибутом fx:id или переменной скрипта. Атрибут элемента "source" указывает имя объекта, который будет скопирован. Тип источника должен определять конструктор копирования, который будет использоваться для создания копии из исходного значения.

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

<fx:root>

Элемент <fx:root> создает ссылку на ранее определенный корневой элемент. Он действителен только в качестве корневого узла документа FXML. <fx:root> используется в основном при создании пользовательских элементов управления, поддерживаемых разметкой FXML. Это обсуждается более подробно в разделе FXMLLoader.

Свойства

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

  • setter свойства
  • Свойство List, доступное только для чтения
  • Свойство Map, доступное только для чтения

Установщики свойств(setter)

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

Например, следующий FXML создает экземпляр класса Label и устанавливает значение свойства label "text" равным "Привет, мир!":

<?import javafx.scene.control.Label?>
<Label>
<text>Hello, World!</text>
</Label>

Это приводит к тому же результату, что и в предыдущем примере, в котором использовался атрибут для установки свойства "text":

<?import javafx.scene.control.Label?>
<Label text="Hello, World!"/>

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

Приведение типов

FXML использует "приведение типов" для преобразования значений свойств в соответствующий тип по мере необходимости. Приведение требуется, поскольку единственными типами данных, поддерживаемыми XML, являются элементы, текст и атрибуты (значения которых также являются текстовыми). Однако Java поддерживает ряд различных типов данных, включая встроенные примитивные типы значений, а также расширяемые ссылочные типы.

Загрузчик FXML использует метод coerce() BeanAdapter для выполнения любых требуемых преобразований типов. Этот метод способен выполнять базовые преобразования примитивных типов, такие как String в boolean или int в double, а также преобразует String в Class или String в Enum. Дополнительные преобразования могут быть реализованы путем определения статического метода valueOf() для целевого типа.

Свойства только для чтения

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

Например, свойство "дочерние элементы" javafx.scene.Group- это свойство списка, доступное только для чтения, представляющее дочерние узлы группы:

<?import javafx.scene.*?>
<?import javafx.scene.shape.*?>
<Group xmlns:fx="http://javafx.com/fxml">
<children>
<Rectangle fx:id="rectangle" x="10" y="10" width="320" height="240"
fill="
#ff0000"/> ... </children> </Group>

По мере чтения каждого подэлемента элемента <children> он добавляется в список, возвращаемый Group#getChildren().

Свойства типа Map только для чтения

Свойство Map, доступное только для чтения, - это свойство компонента, получатель которого возвращает экземпляр java.util.Map и не имеет соответствующего метода настройки. Атрибуты элемента Map, доступного только для чтения, применяются к Map при обработке закрывающего тега.

Свойство "properties" javafx.scene.Node - это пример свойства карты, доступного только для чтения. Следующая разметка устанавливает для свойств "foo" и "bar" экземпляра Label значения "123" и "456" соответственно:

<?import javafx.scene.control.*?>
<Button>
<properties foo="123" bar="456"/>
</Button>

Обратите внимание, что свойство, доступное только для чтения, тип которого не является ни List, ни Map, будет обрабатываться так, как если бы это была карта, доступная только для чтения. Возвращаемое значение метода getter будет заключено в BeanAdapter и может использоваться так же, как и любая другая карта, доступная только для чтения.

Свойства по умолчанию

Класс может определять "свойство по умолчанию", используя аннотацию @DefaultProperty, определенную в пакете javafx.beans. Если присутствует, подэлемент, представляющий свойство по умолчанию, может быть опущен из разметки.

Например, начиная с javafx.scene.layout.Pane (суперкласс javafx.scene.layout.VBox) определяет свойство по умолчанию для "дочерних элементов", элемент <children> не требуется; загрузчик автоматически добавит вложенные элементы VBox в коллекцию "дочерних элементов" контейнера:

<?import javafx.scene.*?>
<?import javafx.scene.shape.*?>
<VBox xmlns:fx="http://javafx.com/fxml">
<Button text="Click Me!"/>
...
</VBox>

Обратите внимание, что свойства по умолчанию не ограничиваются коллекциями. Если свойство элемента по умолчанию ссылается на скалярное значение, любой подэлемент этого элемента будет установлен в качестве значения свойства.

Например, начиная с javafx.scene.control.ScrollPane определяет свойство по умолчанию "content". Область прокрутки, содержащая текстовую область в качестве содержимого, может быть указана следующим образом:

<ScrollPane>
<TextArea text="Once upon a time..."/>
</ScrollPane>

Использование преимуществ свойств по умолчанию может значительно снизить детализацию разметки FXML.

Статические свойства

Элемент может также представлять "статическое" свойство (иногда называемое "присоединенным свойством"). Статические свойства - это свойства, которые имеют смысл только в определенном контексте. Они не являются внутренними для класса, к которому они применяются, но определяются другим классом (часто родительским контейнером элемента управления).

Статические свойства имеют префикс имени класса, который их определяет. Например, следующий FXML вызывает статический установщик для свойств GridPane "RowIndex" и "ColumnIndex":

<GridPane>
<children>
<Label text="My Label">
<GridPane.rowIndex>0</GridPane.rowIndex>
<GridPane.columnIndex>0</GridPane.columnIndex>
</Label>
</children>
</TabPane>

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

GridPane gridPane = new GridPane();

Label label = new Label();
label.setText("My Label");

GridPane.setRowIndex(label, 0);
GridPane.setColumnIndex(label, 0);

gridPane.getChildren().add(label);

Вызовы GridPane.setRowIndex() и GridPane.setColumnIndex() "прикрепляют" данные индекса к экземпляру метки. Затем GridPane использует их во время компоновки, чтобы соответствующим образом расположить свои дочерние элементы. Другие контейнеры, включая AnchorPane, BorderPane и StackPane, определяют аналогичные свойства.

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

Блоки определений <fx:define>

Элемент <fx:define> используется для создания объектов, которые существуют вне иерархии объектов, но на которые может потребоваться ссылаться в другом месте.

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

<VBox>
<fx:define>
<ToggleGroup fx:id="myToggleGroup"/>
</fx:define>
<children>
<RadioButton text="A" toggleGroup="$myToggleGroup"/>
<RadioButton text="B" toggleGroup="$myToggleGroup"/>
<RadioButton text="C" toggleGroup="$myToggleGroup"/>
</children>
</VBox>

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

Атрибуты

Атрибут в FXML может представлять одно из следующих значений:

  • Свойство экземпляра класса
  • "Статическое" свойство
  • Обработчик событий

Каждый из них более подробно обсуждается в следующих разделах.

Свойства экземпляра

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

<?import javafx.scene.control.*?>
<Button text="Click Me!"/>

Как и в случае с элементами свойств, атрибуты свойств поддерживают принуждение к типу. При обработке следующей разметки значения "x", "y", "width" и "height" будут преобразованы в двойные значения, а значение "fill" будет преобразовано в цвет:

<Rectangle fx:id="rectangle" x="10" y="10" width="320" height="240"
fill="
#ff0000"/>

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

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

  • Разрешение местоположения
  • Разрешение ресурсов
  • Разрешение переменных

Разрешение местоположения

В виде строк атрибуты XML не могут изначально представлять типизированную информацию о местоположении, такую как URL-адрес. Однако часто бывает необходимо указать такие местоположения в разметке; например, источник ресурса изображения. Оператор разрешения местоположения (представленный префиксом "@" к значению атрибута) используется для указания того, что значение атрибута следует рассматривать как местоположение относительно текущего файла, а не как простую строку.

Например, следующая разметка создает ImageView и заполняет его данными изображения из my_image.png, которые, как предполагается, расположены по пути относительно текущего файла FXML:

<ImageView>
<image>
<Image url="@my_image.png"/>
</image>
</ImageView>

Поскольку изображение является неизменяемым объектом, для его создания требуется конструктор. В качестве альтернативы, если бы Image определял фабричный метод valueOf(URL), представление изображения могло бы быть заполнено следующим образом:

<ImageView image="@my_image.png"/>

Значение атрибута "image" будет преобразовано в URL-адрес загрузчиком FXML, а затем принудительно преобразовано в изображение с помощью метода valueOf().

Resource Resolution

В FXML замена ресурсов может выполняться во время загрузки для целей локализации. При наличии экземпляра java.util.ResourceBundle, загрузчик FXML заменит экземпляры имен ресурсов их значениями, зависящими от локали. Имена ресурсов обозначаются префиксом "%", как показано ниже:

<Label text="%myText"/>

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

myText = This is the text!

результатом загрузки FXML будет экземпляр метки, содержащий текст "Это текст!".

Разрешение переменных

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

<fx:define>
<ToggleGroup fx:id="myToggleGroup"/>
</fx:define>
...
<RadioButton text="A" toggleGroup="$myToggleGroup"/>
<RadioButton text="B" toggleGroup="$myToggleGroup"/>
<RadioButton text="C" toggleGroup="$myToggleGroup"/>

Присвоение элементу значения "fx:id" создает переменную в пространстве имен документа, на которую впоследствии можно ссылаться с помощью атрибутов разыменования переменных, таких как атрибут "ToggleGroup", показанный выше, или в коде скрипта, обсуждаемом в следующем разделе. Кроме того, если тип объекта определяет свойство "id", это значение также будет передано методу setId().

Escape-последовательности

Если значение атрибута начинается с одного из префиксов разрешения ресурсов, символ можно экранировать, добавив к нему начальную обратную косую черту ("\"). Например, следующая разметка создает экземпляр Label, текст которого гласит "$10.00".:

<Label text="\$10.00"/>

Привязка выражений

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

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

<TextField fx:id="textField"/>
<Label text="${textField.text}"/>

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

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

Статические Свойства

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

<GridPane>
<children>
<Label text="My Label" GridPane.rowIndex="0" GridPane.columnIndex="0"/>
</children>
</
GridPane>

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

Обработчики событий

Атрибуты обработчика событий - это удобное средство привязки поведения к элементам документа. Любому классу, который определяет метод setOnEvent(), может быть назначен обработчик событий в разметке, как и любому наблюдаемому свойству (через атрибут "onPropertyChange").

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

Обработчики событий скриптов

Обработчик событий сценария - это обработчик событий, который выполняет код сценария при запуске события, аналогично обработчикам событий в HTML. Например, следующий обработчик на основе скрипта для события кнопки "onAction" использует JavaScript для записи текста "Вы нажали на меня!" на консоль, когда пользователь нажимает кнопку:

<?language javascript?>
...

<VBox>
<children>
<Button text="Click Me!"
onAction="java.lang.System.out.println('You clicked me!');"/>
</children>
</VBox>

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

Обработчики событий методами контроллера

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

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

<VBox fx:controller="com.foo.MyController"
xmlns:fx="http://javafx.com/fxml">
<children>
<Button text="Click Me!" onAction="
#handleButtonAction"/> </children> </VBox>

Обратите внимание на использование атрибута fx:controller для корневого элемента. Этот атрибут используется для связывания класса контроллера с документом. Если MyController определен следующим образом:

package com.foo;

public class MyController {
public void handleButtonAction(ActionEvent event) {
System.out.println("You clicked me!");
}
}

handleButtonAction() будет вызываться, когда пользователь нажимает кнопку, и текст "Вы нажали на меня!" будет записан в консоль.

В общем случае метод обработчика должен соответствовать сигнатуре стандартного обработчика событий; то есть он должен принимать один аргумент типа, который расширяет javafx.event.Event и должно возвращать void (аналогично делегату события в C#). Аргумент события часто содержит важную и полезную информацию о характере события; однако он является необязательным и при желании может быть опущен.

Контроллеры более подробно обсуждаются в следующем разделе.

Scripting

Тег <fx:script> позволяет вызывающему абоненту импортировать скриптовый код в файл FXML или встраивать скрипт в него. Можно использовать любой язык сценариев JVM, включая, среди прочего, JavaScript, Groovy и Clojure. Код сценария часто используется для определения обработчиков событий непосредственно в разметке или в связанном исходном файле, поскольку обработчики событий часто могут быть написаны более кратко на более свободно типизированных языках сценариев, чем на статически типизированных языках, таких как Java.

Например, следующая разметка определяет функцию с именем handleButtonAction(), которая вызывается обработчиком действия, прикрепленным к элементу Button:

<?language javascript?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>

<VBox xmlns:fx="http://javafx.com/fxml">
<fx:script>
importClass(java.lang.System);

function handleButtonAction(event) {
System.out.println('You clicked me!');
}
</fx:script>

<children>
<Button text="Click Me!" onAction="handleButtonAction(event);"/>
</children>
</VBox>

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

Код скрипта также может быть определен во внешних файлах. Предыдущий пример можно было бы разделить на файл FXML и исходный файл JavaScript без какой-либо разницы в функциональности:

example.fxml

<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>

<VBox xmlns:fx="http://javafx.com/fxml">
<fx:script source="example.js"/>

<children>
<Button text="Click Me!" onAction="handleButtonAction(event);"/>
</children>
</VBox>

example.js

importClass(java.lang.System);

function handleButtonAction(event) {
System.out.println('You clicked me!');
}

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

Обратите внимание, что блоки сценариев не ограничиваются определением функций обработчика событий. Код скрипта выполняется по мере его обработки, поэтому его также можно использовать для динамической настройки структуры результирующего вывода. В качестве простого примера следующий FXML включает в себя блок скрипта, который определяет переменную с именем "LabelText". Значение этой переменной используется для заполнения свойства text экземпляра Label:

<fx:script>
var myText = "This is the text of my label.";
</fx:script>

...

<Label text="$myText"/>

Контроллеры

Хотя может быть удобно писать простые обработчики событий в скрипте, встроенные или определенные во внешних файлах, часто предпочтительнее определять более сложную логику приложения на скомпилированном строго типизированном языке, таком как Java. Как обсуждалось ранее, атрибут fx:controller позволяет вызывающему объекту связать класс "controller" с документом FXML. Контроллер - это скомпилированный класс, который реализует "код, лежащий в основе" иерархии объектов, определенной документом.

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

<VBox fx:controller="com.foo.MyController"
xmlns:fx="http://javafx.com/fxml">
<children>
<Button text="Click Me!" onAction="#handleButtonAction"/>
</children>
</VBox>

package com.foo;

public class MyController {
public void handleButtonAction(ActionEvent event) {
System.out.println("You clicked me!");
}
}

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

public void initialize();

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

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

<VBox fx:controller="com.foo.MyController"
xmlns:fx="http://javafx.com/fxml">
<children>
<Button fx:id="button" text="Click Me!"/>
</children>
</VBox>

package com.foo;

public class MyController implements Initializable {
public Button button;

@Override
public void initialize(URL location, Resources resources)
button.setOnAction(new EventHandler<ActionEvent>() {
@Override
public void handle(ActionEvent event) {
System.out.println("You clicked me!");
}
});
}
}

@FXML

Обратите внимание, что в предыдущих примерах поля-члены контроллера и методы обработчика событий были объявлены как общедоступные, поэтому они могут быть установлены или вызваны загрузчиком. На практике это не часто является проблемой, поскольку контроллер обычно виден только загрузчику FXML, который его создает. Однако для разработчиков, которые предпочитают более ограниченную видимость полей контроллера или методов обработчика, javafx.fxml.FXML Можно использовать аннотацию FXML. Эта аннотация помечает защищенный или закрытый член класса как доступный для FXML.

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

package com.foo;

public class MyController {
@FXML
private void handleButtonAction(ActionEvent event) {
System.out.println("You clicked me!");
}
}

package com.foo;

public class MyController implements Initializable {
@FXML private Button button;

@FXML
protected void initialize()
button.setOnAction(new EventHandler<ActionEvent>() {
@Override
public void handle(ActionEvent event) {
System.out.println("You clicked me!");
}
});
}
}

В первой версии handleButtonAction() помечен тегом @FXML, чтобы позволить разметке, определенной в документе контроллера, вызывать его. Во втором примере поле кнопки снабжено комментариями, позволяющими загрузчику устанавливать его значение. Метод initialize() имеет аналогичную аннотацию.

Обратите внимание, что аннотацию @FXML в настоящее время можно использовать только с надежным кодом. Поскольку загрузчик FXML полагается на отражение для установки полей-членов и вызова методов-членов, он должен вызывать setAccessible() для любого непубличного поля. setAccessible() - это привилегированная операция, которая может быть выполнена только в безопасном контексте. Это может измениться в будущем выпуске.

Вложенные контроллеры

Экземпляры контроллера для вложенных документов FXML, загруженных через элемент <fx:include>, сопоставляются непосредственно с полями-членами включающего контроллера. Это позволяет разработчику легко получить доступ к функциям, определенным включением (например, к диалоговому окну, представленному контроллером главного окна приложения). Например, учитывая следующий код:

main_window_content.fxml

<VBox fx:controller="com.foo.MainController">
<fx:include fx:id="dialog" source="dialog.fxml"/>
...
</VBox>

MainController.java

public class MainController extends Controller {
@FXML private Window dialog;
@FXML private DialogController dialogController;

...
}

когда вызывается метод initialize() контроллера, поле диалога будет содержать корневой элемент, загруженный из включения "dialog.fxml", а поле dialogController будет содержать контроллер включения. Затем главный контроллер может вызывать методы на включенном контроллере, например, для заполнения и отображения диалогового окна.

FXMLLoader

Класс FXMLLoader отвечает за фактическую загрузку исходного файла FXML и возврат результирующего графа объектов. Например, следующий код загружает файл FXML из местоположения на пути к классу относительно загружаемого класса и локализует его с помощью пакета ресурсов с именем "com.foo.example". Предполагается, что тип корневого элемента является подклассом javafx.scene.layout.Pane, и предполагается, что документ определяет контроллер типа MyController:

URL location = getClass().getResource("example.fxml");
ResourceBundle resources = ResourceBundle.getBundle("com.foo.example");
FXMLLoader fxmlLoader = new FXMLLoader(location, resources);

Pane root = (Pane)fxmlLoader.load();
MyController controller = (MyController)fxmlLoader.getController();

Обратите внимание, что результатом операции FXMLLoader.load() является иерархия экземпляров, которая отражает фактические именованные классы в документе, а не узлы org.w3c.dom, представляющие эти классы. Внутренне FXMLLoader использует javax.xml.stream API (также известный как Streaming API для XML или StAX) для загрузки документа FXML. StAX - это чрезвычайно эффективный основанный на событиях API синтаксического анализа XML, который концептуально похож на своего предшественника W3C, SAX. Это позволяет обрабатывать документ FXML за один проход, а не загружать его в промежуточную структуру DOM с последующей последующей обработкой.

Пользовательские компоненты

Методы setRoot() и setController() FXMLLoader позволяют вызывающей стороне вводить значения корня документа и контроллера соответственно в пространство имен документа, вместо того, чтобы делегировать создание этих значений самому FXMLLoader. Это позволяет разработчику легко создавать повторно используемые элементы управления, которые внутренне реализованы с использованием разметки, но (с точки зрения API) выглядят идентично элементам управления, реализованным программно.

Например, следующая разметка определяет структуру простого пользовательского элемента управления, содержащего текстовое поле и экземпляр кнопки. Корневой контейнер определяется как экземпляр javafx.scene.макет.VBox:

<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>

<fx:root type="javafx.scene.layout.VBox" xmlns:fx="http://javafx.com/fxml">
<TextField fx:id="textField"/>
<Button text="Click Me" onAction="
#doSomething"/> </fx:root>

Как упоминалось ранее, тег <fx:root> создает ссылку на ранее определенный корневой элемент. Значение этого элемента получается путем вызова метода getRoot() FXMLLoader. Перед вызовом load() вызывающий должен указать это значение с помощью вызова setRoot(). Вызывающий объект может аналогичным образом предоставить значение для контроллера документа, вызвав setController(), который задает значение, которое будет использоваться в качестве контроллера документа при чтении документа. Эти два метода обычно используются вместе при создании пользовательских компонентов на основе FXML.

In the following example, the CustomControl class extends VBox (the type declared by the <fx:root> element), and sets itself as both the root and controller of the FXML document in its constructor. When the document is loaded, the contents of CustomControl will be populated with the contents of the previous FXML document:

package fxml;

import java.io.IOException;
import javafx.beans.property.StringProperty;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.control.TextField;
import javafx.scene.layout.VBox;

public class CustomControl extends VBox {
@FXML private TextField textField;

public CustomControl() {
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("custom_control.fxml"));
fxmlLoader.setRoot(this);
fxmlLoader.setController(this);

try {
fxmlLoader.load();
} catch (IOException exception) {
throw new RuntimeException(exception);
}
}

public String getText() {
return textProperty().get();
}

public void setText(String value) {
textProperty().set(value);
}

public StringProperty textProperty() {
return textField.textProperty();
}

@FXML
protected void doSomething() {
System.out.println("The button was clicked!");
}
}

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

Java:

HBox hbox = new HBox();
CustomControl customControl = new CustomControl();
customControl.setText("Hello World!");
hbox.getChildren().add(customControl);

FXML:

<HBox>
<CustomControl text="Hello World!"/>
</HBox>

Copyright (c) 2008, 2014, Oracle and/or its affiliates. All rights reserved.