Содержание

Оригинал: https://openjfx.io/javadoc/11/javafx.fxml/javafx/fxml/doc-files/introduction_to_fxml.html

Краткий обзор

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

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

Элементы

В FXML XML-элемент представляет собой один из следующих вариантов:

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

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

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

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

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

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

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

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

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

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

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

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

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

Классы, которые не соответствуют соглашениям о компонентах, также могут быть созданы в FXML с использованием объекта, называемого «builder» (построитель). Подробнее о построителях рассказывается чуть позже.

Карты

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

Если элемент представляет тип, который уже реализует 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-это еще одно средство создания объектов, классы которых не имеют конструктора по умолчанию. Значение атрибута-это имя статического метода factory без аргументов для создания экземпляров класса. Например, следующая разметка создает экземпляр observableArrayList, заполненного тремя строковыми значениями:

<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. Эта фабрика способна создавать и настраивать большинство неизменяемых типов JavaFX. Например, следующая разметка использует конструктор по умолчанию для создания экземпляра неизменяемого класса javafx.scene.paint.Color:

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

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

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

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

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

<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:include source="filename" resources="resource_file" charset="utf-8"/>

<fx:constant>

Элемент <fx:constant> создает ссылку на константу класса. Например, следующая разметка устанавливает значение свойства minWidth экземпляра aButton равным значению константы 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.

Элементы - свойства.

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

  • Свойство - установщик
  • Свойство - список только для чтения
  • Свойство - карта только для чтения

Свойство - установщик

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

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

<?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() для целевого типа.

Свойств - список (только для чтения).

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

Например, свойство "children" 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().

Свойство - карта (только для чтения).

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

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

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

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

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

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

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

<?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 вызывает статический метод set для свойств 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() "прикрепляют" индексные данные к экземпляру Label. Затем GridPane использует их при создании макета, чтобы соответствующим образом расположить свои дочерние элементы. Другие контейнеры, включая AnchorPane, BorderPane, и StackPane, определяют аналогичные свойства.

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

Блоки define.

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

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

<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>

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

Атрибуты.

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

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

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

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

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

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

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

<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 является неизменяемым объектом, для его построения требуется строитель. В качестве альтернативы, если Image должен был определить метод factory valueOf(URL), представление изображения могло бы быть заполнено следующим образом:

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

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

Обратите внимание, что значения пробелов в URL-адресе должны быть закодированы; например, для ссылки на файл с именем "My Image.png" документ FXML должен содержать следующее:

<Image url="@My%20Image.png"/>

вместо:

<Image url="@My Image.png"/>

Разрешение ресурса.

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

<Label text="%myText"/>

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

myText = This is the text!

выводом загрузчика FXML будет экземпляр метки, содержащий текст "This is the text!".

Разрешение переменной.

Документ 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", показанный выше, или в коде сценария, обсужденном в более позднем разделе. Дополнительно, если тип объекта определит свойство "идентификатора", то это значение также передадут к объектам setId () метод.

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

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

<Label text="\$10.00"/>

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

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

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

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

По мере ввода текста пользователем текстовое содержимое метки будет автоматически обновляться.

Также поддерживаются более сложные выражения. Ниже приведен список поддерживаемых констант и операторов:

Таблица констант и операторов
Константа / ОператорОписание
"string"
'string'
Строковая константа
true
false
Булева константа
null Константа, представляющая нулевое значение
50.0
3e5
42
Числовая константа
-
(unary operator)
Унарный оператор минус, применяемый к числу
!
(unary operator)
Унарное отрицание булева
+ -
* / %
Числовые двоичные операторы
&& || Логические бинарные операции
> >=
< <=
== !=
Бинарные операторы сравнения.
Оба аргумента должны быть Сопоставимы по типу

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

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

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

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

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

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

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

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

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

<?language javascript?>
...

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

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

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

Обработчик событий метода контроллера - это метод, определенный в документе "controller". Контроллер-это объект, связанный с десериализованным содержимым документа 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() будет вызвана, когда пользователь нажмет кнопку, и текст "You clicked me!" будет записан на консоль.

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

package com.foo;

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

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

Обработчики событий из выражений.

Любое выражение, указывающее на переменную типа javafx.event.EventHandler можно использовать в качестве обработчика выражений.

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

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

С контроллером, который содержит такое поле

public class MyController {
    
    @FXML
    public EventHandler<ActionEvent> onActionHandler = new EventHandler<>() { ... }

    ...
}

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

Специальные обработчики для коллекций и свойств.

Коллекции и свойства объектов нельзя прослушивать с помощью методов setOnEvent(). По этой причине необходимо использовать специальные методы обработки. ObservableList, ObservableMap или ObservableSet используют специальный атрибут onChange, который указывает на метод обработчика с ListChangeListner.Change, MapChangeListener.Change или SetChangeListener.Change параметр соответственно.

<VBox fx:controller="com.foo.MyController"
    xmlns:fx="http://javafx.com/fxml">
    <children onChange="#handleChildrenChange"/>
</VBox>

где метод обработчика выглядит следующим образом:

package com.foo;

import javafx.collections.ListChangeListener.Change;

public class MyController {
    public void handleChildrenChange(ListChangeListener.Change c) {
        System.out.println("Children changed!");
    }
}

Аналогично, обработчики свойств-это методы, которые имеют те же параметры, что и измененный метод ChangeListener :

changed(ObservableValue<? extends T> observable, T oldValue, T newValue)

Обработчик родительского свойства будет выглядеть следующим образом

public class MyController {
    public void handleParentChange(ObservableValue value, Parent oldValue, Parent newValue) {
        System.out.println("Parent changed!");
    }
}

Для удобства первый параметр может быть подклассом ObservableValue, например. Property

Для регистрации свойства необходимо использовать специальный атрибут on<propertyName>Change.

<VBox fx:controller="com.foo.MyController"
    xmlns:fx="http://javafx.com/fxml" onParentChange="#handleParentChange"/>

Обратите внимание, что коллекции и свойства в настоящее время не поддерживают обработчики сценариев.

Сценарии.

Тег <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"/>

Предупреждение:Начиная с JavaFX 8, функция importClass() javascript больше не поддерживается. Вы должны использовать полные имена, как в приведенном выше примере, или загрузить сценарий совместимости nashorn.

load("nashorn:mozilla_compat.js");
importClass(java.lang.System);

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

Контроллеры.

Хотя может быть удобно писать простые обработчики событий в скриптах, встроенных или определенных во внешних файлах, часто предпочтительнее определять более сложную логику приложения на скомпилированном, строго типизированном языке, таком как 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

Обратите внимание, что в предыдущих примерах поля-члены контроллера и методы обработчика событий были объявлены как public, поэтому они могут быть установлены или вызваны загрузчиком. На практике это не часто бывает проблемой, так как контроллер обычно виден только загрузчику FXML, который его создает. Однако для разработчиков, которые предпочитают более ограниченную видимость полей контроллера или методов обработчика, javafx.fxml.FXML можно использовать аннотацию. Эта аннотация знаки члена protected или Private-класса максимально доступными для FXML. Если аннотируемый класс находится в именованном модуле, модуль, содержащий этот класс, должен открыть содержащий пакет по крайней мере для модуля javafx.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, загруженных через элемент <fx:include>, сопоставляются непосредственно с полями-членами включающего контроллера. Это позволяет разработчику легко получить доступ к функциям, определенным 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 будет содержать корневой элемент, загруженный из вложенного "dialog.fxml", а поле dialogController будет содержать вложенный контроллер. Затем главный контроллер может вызывать методы на включенном контроллере, например, для заполнения и отображения диалогового окна. Обратите внимание, что поскольку содержимое файла, на который ссылается fx:include, в противном случае стало бы частью графа сцены, охватываемого main_window_content.fxml, необходимо обернуть fx:include с помощью fx:define, чтобы разделить графики сцен обоих окон.

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 for XML или StAX) для загрузки документа FXML. StAX-это чрезвычайно эффективный API синтаксического анализа XML на основе событий, концептуально похожий на своего предшественника W3C, SAX. Он позволяет обрабатывать документ FXML за один проход, а не загружать его в промежуточную структуру DOM и затем подвергать последующей обработке.

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

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

Например, следующая разметка определяет структуру простого пользовательского элемента управления, содержащего TextField и экземпляр Кнопки. Корневой контейнер определяется как экземпляр javafx.scene.layout. 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. До вызова загрузки (), вызывающая сторона должна определить это значение через звонок setRoot (). Вызывающая сторона может так же обеспечить значение для контроллера документа, вызывая setController (), который устанавливает значение, которое будет использоваться в качестве контроллера документа, когда документ будет считан. Эти два метода обычно используются вместе, создавая пользовательские FXML-на-основе компоненты.

В следующем примере CustomControl class расширяет VBox (тип, объявленный <fx:root> элементом), и принимается и как корень и как контроллер документа FXML в его конструкторе. Когда документ будет загружен, содержание CustomControl будет заполнено с содержанием предыдущего документа FXML:

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>