javafx
FXML и контроллеры
Поиск…
Синтаксис
- xmlns: fx = " http://javafx.com/fxml " // Объявление пространства имен
Пример FXML
Простой документ FXML, в котором описывается AnchorPane
содержащая кнопку и узел метки:
<?xml version="1.0" encoding="UTF-8"?>
<?import java.lang.*?>
<?import java.util.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<AnchorPane id="AnchorPane" prefHeight="200" prefWidth="320" xmlns:fx="http://javafx.com/fxml/1"
fx:controller="com.example.FXMLDocumentController">
<children>
<Button layoutX="126" layoutY="90" text="Click Me!" onAction="#handleButtonAction" fx:id="button" />
<Label layoutX="126" layoutY="120" minHeight="16" minWidth="69" fx:id="label" />
</children>
</AnchorPane>
Этот пример файла FXML связан с классом контроллера. Связь между FXML и классом контроллера в этом случае производится путем указания имени класса как значения атрибута fx:controller
в корневом элементе FXML: fx:controller="com.example.FXMLDocumentController"
. Класс контроллера позволяет выполнять Java-код в ответ на действия пользователя на элементах пользовательского интерфейса, определенных в файле FXML:
package com.example ;
import java.net.URL;
import java.util.ResourceBundle;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Label;
public class FXMLDocumentController {
@FXML
private Label label;
@FXML
private void handleButtonAction(ActionEvent event) {
System.out.println("You clicked me!");
label.setText("Hello World!");
}
@Override
public void initialize(URL url, ResourceBundle resources) {
// Initialization code can go here.
// The parameters url and resources can be omitted if they are not needed
}
}
FXMLLoader
можно использовать для загрузки файла FXML:
public class MyApp extends Application {
@Override
public void start(Stage stage) throws Exception {
FXMLLoader loader = new FXMLLoader();
loader.setLocation(getClass().getResource("FXMLDocument.fxml"));
Parent root = loader.load();
Scene scene = new Scene(root);
stage.setScene(scene);
stage.show();
}
}
Метод load
выполняет несколько действий, и полезно понять порядок их возникновения. В этом простом примере:
FXMLLoader
считывает и анализирует файл FXML. Он создает объекты, соответствующие элементам, определенным в файле, и учитывает любые атрибутыfx:id
определенные на них.Поскольку корневой элемент файла FXML определяет атрибут
fx:controller
,FXMLLoader
создает новый экземпляр класса, который он задает. По умолчанию это происходит при вызове конструктора без аргументов в указанном классе.Любые элементы с определенными атрибутами
fx:id
которые имеют поля в контроллере с соответствующими именами полей и которые являются либоpublic
(не рекомендуется), либо аннотированными@FXML
(рекомендуется), вводятся в соответствующие поля. Итак, в этом примере, поскольку в файле FXML естьLabel
сfx:id="label"
и поле в контроллере, определенное как@FXML private Label label ;
поле
label
инициализируется экземпляромLabel
созданнымFXMLLoader
.Обработчики событий регистрируются любыми элементами в файле FXML с установленными свойствами onXXX
onXXX="#..."
. Эти обработчики событий вызывают указанный метод в классе контроллера. В этом примере, посколькуButton
имеетonAction="#handleButtonAction"
, а контроллер определяет метод@FXML private void handleButtonAction(ActionEvent event) { ... }
когда действие запускается над кнопкой (например, пользователь нажимает на нее), этот метод вызывается. Метод должен иметь тип возврата
void
и может либо определять параметр, соответствующий типу события (ActionEvent
в этом примере), либо не определять параметры.Наконец, если класс контроллера определяет метод
initialize
, этот метод вызывается. Обратите внимание, что это происходит после@FXML
полей@FXML
, поэтому они могут быть безопасно доступны в этом методе и будут инициализированы экземплярами, соответствующими элементам в файле FXML. Методinitialize()
может либо не принимать никаких параметров, либо приниматьURL
иResourceBundle
. В последнем случае эти параметры будут заполненыURL
представляющим местоположение файла FXML, и любымResourceBundle
установленным наFXMLLoader
черезloader.setResources(...)
. Любой из них может бытьnull
если они не были установлены.
Вложенные контроллеры
Нет необходимости создавать весь пользовательский интерфейс в одном FXML, используя один контроллер.
Тег <fx:include>
можно использовать для включения одного файла fxml в другой. Контроллер включенного fxml может быть введен в контроллер включенного файла так же, как и любой другой объект, созданный FXMLLoader
.
Это делается добавлением атрибута fx:id
в элемент <fx:include>
. Таким образом, контроллер включенного fxml будет введен в поле с именем <fx:id value>Controller
.
Примеры:
Значение fx: id | название поля для инъекций |
---|---|
Foo | FooController |
answer42 | answer42Controller |
АБВ | xYzController |
Образец fxmls
счетчик
Это fxml, содержащий StackPane
с Text
узлом. Контроллер для этого файла fxml позволяет получать текущее значение счетчика, а также увеличивать счетчик:
counter.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.text.*?>
<?import javafx.scene.layout.*?>
<StackPane prefHeight="200" prefWidth="200" xmlns:fx="http://javafx.com/fxml/1" fx:controller="counter.CounterController">
<children>
<Text fx:id="counter" />
</children>
</StackPane>
CounterController
package counter;
import javafx.fxml.FXML;
import javafx.scene.text.Text;
public class CounterController {
@FXML
private Text counter;
private int value = 0;
public void initialize() {
counter.setText(Integer.toString(value));
}
public void increment() {
value++;
counter.setText(Integer.toString(value));
}
public int getValue() {
return value;
}
}
Включая fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<BorderPane prefHeight="500" prefWidth="500" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="counter.OuterController">
<left>
<Button BorderPane.alignment="CENTER" text="increment" onAction="#increment" />
</left>
<center>
<!-- content from counter.fxml included here -->
<fx:include fx:id="count" source="counter.fxml" />
</center>
</BorderPane>
OuterController
Контроллер включенного fxml вводится в этот контроллер. Здесь обработчик для события onAction
для Button
используется для увеличения счетчика.
package counter;
import javafx.fxml.FXML;
public class OuterController {
// controller of counter.fxml injected here
@FXML
private CounterController countController;
public void initialize() {
// controller available in initialize method
System.out.println("Current value: " + countController.getValue());
}
@FXML
private void increment() {
countController.increment();
}
}
Fxmls можно загрузить таким образом, предполагая, что код вызывается из класса в том же пакете, что и outer.fxml
:
Parent parent = FXMLLoader.load(getClass().getResource("outer.fxml"));
Определить блоки и
Иногда элемент должен быть создан за пределами обычной структуры объекта в fxml.
Здесь Define Blocks вступают в игру:
Содержимое внутри элемента <fx:define>
не добавляется к объекту, созданному для родительского элемента.
Каждому дочернему элементу <fx:define>
нужен атрибут fx:id
.
Объекты, созданные таким образом, могут быть позже указаны с помощью элемента <fx:reference>
или с помощью привязки выражения.
Элемент <fx:reference>
может использоваться для ссылки на любой элемент с атрибутом fx:id
который обрабатывается до обработки элемента <fx:reference>
, используя то же значение, что и атрибут fx:id
ссылочного элемента в source
атрибут элемента <fx:reference>
.
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.text.*?>
<?import java.lang.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<VBox xmlns:fx="http://javafx.com/fxml/1" prefHeight="300.0" prefWidth="300.0" xmlns="http://javafx.com/javafx/8">
<children>
<fx:define>
<String fx:value="My radio group" fx:id="text" />
</fx:define>
<Text>
<text>
<!-- reference text defined above using fx:reference -->
<fx:reference source="text"/>
</text>
</Text>
<RadioButton text="Radio 1">
<toggleGroup>
<ToggleGroup fx:id="group" />
</toggleGroup>
</RadioButton>
<RadioButton text="Radio 2">
<toggleGroup>
<!-- reference ToggleGroup created for last RadioButton -->
<fx:reference source="group"/>
</toggleGroup>
</RadioButton>
<RadioButton text="Radio 3" toggleGroup="$group" />
<!-- reference text defined above using expression binding -->
<Text text="$text" />
</children>
</VBox>
Передача данных в FXML - доступ к существующему контроллеру
Проблема. Некоторые данные должны быть переданы в сцену, загруженную из fxml.
Решение
Укажите контроллер, используя атрибут fx:controller
и получите экземпляр контроллера, созданный во время процесса загрузки, из экземпляра FXMLLoader
используемого для загрузки fxml.
Добавьте методы для передачи данных в экземпляр контроллера и обработайте данные в этих методах.
FXML
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.text.*?>
<?import javafx.scene.layout.*?>
<VBox xmlns:fx="http://javafx.com/fxml/1" fx:controller="valuepassing.TestController">
<children>
<Text fx:id="target" />
</children>
</VBox>
контроллер
package valuepassing;
import javafx.fxml.FXML;
import javafx.scene.text.Text;
public class TestController {
@FXML
private Text target;
public void setData(String data) {
target.setText(data);
}
}
Код, используемый для загрузки fxml
String data = "Hello World!";
FXMLLoader loader = new FXMLLoader(getClass().getResource("test.fxml"));
Parent root = loader.load();
TestController controller = loader.<TestController>getController();
controller.setData(data);
Передача данных в FXML - указание экземпляра контроллера
Проблема. Некоторые данные должны быть переданы в сцену, загруженную из fxml.
Решение
Установите контроллер с FXMLLoader
экземпляра FXMLLoader
используется позже для загрузки fxml.
Перед загрузкой fxml убедитесь, что контроллер содержит соответствующие данные.
Примечание: в этом случае файл fxml не должен содержать атрибут fx:controller
.
FXML
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.text.*?>
<?import javafx.scene.layout.*?>
<VBox xmlns:fx="http://javafx.com/fxml/1">
<children>
<Text fx:id="target" />
</children>
</VBox>
контроллер
import javafx.fxml.FXML;
import javafx.scene.text.Text;
public class TestController {
private final String data;
public TestController(String data) {
this.data = data;
}
@FXML
private Text target;
public void initialize() {
// handle data once the fields are injected
target.setText(data);
}
}
Код, используемый для загрузки fxml
String data = "Hello World!";
FXMLLoader loader = new FXMLLoader(getClass().getResource("test.fxml"));
TestController controller = new TestController(data);
loader.setController(controller);
Parent root = loader.load();
Передача параметров в FXML - с использованием контроллераFactory
Проблема. Некоторые данные должны быть переданы в сцену, загруженную из fxml.
Решение
Укажите фабрику контроллера, которая отвечает за создание контроллеров. Передайте данные экземпляру контроллера, созданному на заводе.
FXML
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.text.*?>
<?import javafx.scene.layout.*?>
<VBox xmlns:fx="http://javafx.com/fxml/1" fx:controller="valuepassing.TestController">
<children>
<Text fx:id="target" />
</children>
</VBox>
контроллер
package valuepassing;
import javafx.fxml.FXML;
import javafx.scene.text.Text;
public class TestController {
private final String data;
public TestController(String data) {
this.data = data;
}
@FXML
private Text target;
public void initialize() {
// handle data once the fields are injected
target.setText(data);
}
}
Код, используемый для загрузки fxml
Строковые данные = «Hello World!»;
Map<Class, Callable<?>> creators = new HashMap<>();
creators.put(TestController.class, new Callable<TestController>() {
@Override
public TestController call() throws Exception {
return new TestController(data);
}
});
FXMLLoader loader = new FXMLLoader(getClass().getResource("test.fxml"));
loader.setControllerFactory(new Callback<Class<?>, Object>() {
@Override
public Object call(Class<?> param) {
Callable<?> callable = creators.get(param);
if (callable == null) {
try {
// default handling: use no-arg constructor
return param.newInstance();
} catch (InstantiationException | IllegalAccessException ex) {
throw new IllegalStateException(ex);
}
} else {
try {
return callable.call();
} catch (Exception ex) {
throw new IllegalStateException(ex);
}
}
}
});
Parent root = loader.load();
Это может показаться сложным, но может быть полезно, если fxml должен решить, какой класс контроллера ему нужен.
Создание экземпляра в FXML
Следующий класс используется для демонстрации того, как могут создаваться экземпляры классов:
Аннотацию в Person(@NamedArg("name") String name)
необходимо удалить, поскольку аннотация @NamedArg
недоступна.
package fxml.sample;
import javafx.beans.NamedArg;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
public class Person {
public static final Person JOHN = new Person("John");
public Person() {
System.out.println("Person()");
}
public Person(@NamedArg("name") String name) {
System.out.println("Person(String)");
this.name.set(name);
}
public Person(Person person) {
System.out.println("Person(Person)");
this.name.set(person.getName());
}
private final StringProperty name = new SimpleStringProperty();
public final String getName() {
System.out.println("getter");
return this.name.get();
}
public final void setName(String value) {
System.out.println("setter");
this.name.set(value);
}
public final StringProperty nameProperty() {
System.out.println("property getter");
return this.name;
}
public static Person valueOf(String value) {
System.out.println("valueOf");
return new Person(value);
}
public static Person createPerson() {
System.out.println("createPerson");
return new Person();
}
}
Предположим, что класс Person
уже был инициализирован перед загрузкой fxml.
Примечание об импорте
В следующем примере fxml раздел импорта будет отсутствовать. Однако fxml должен начинаться с
<?xml version="1.0" encoding="UTF-8"?>
а затем раздел импорта, импортирующий все классы, используемые в файле fxml. Импорт аналогичен нестационарному импорту, но добавляется в качестве инструкций по обработке. Необходимо импортировать даже классы из пакета java.lang
.
В этом случае следует добавить следующие импорты:
<?import java.lang.*?>
<?import fxml.sample.Person?>
Аннотированный конструктор @NamedArg
Если есть конструктор, где каждый параметр аннотируется с @NamedArg
и все значения аннотаций @NamedArg
присутствуют в fxml, конструктор будет использоваться с этими параметрами.
<Person name="John"/>
<Person xmlns:fx="http://javafx.com/fxml">
<name>
<String fx:value="John"/>
</name>
</Person>
Оба они приводят к следующему выходу консоли, если они загружаются:
Person(String)
Нет конструктора args
Если нет подходящего @NamedArg
annotated @NamedArg
будет использоваться конструктор, который не принимает никаких параметров.
Удалите аннотацию @NamedArg
из конструктора и попробуйте загрузить.
<Person name="John"/>
Это будет использовать конструктор без параметров.
Выход:
Person()
setter
атрибут fx:value
Атрибут fx:value
может использоваться для передачи его значения static
методу valueOf
принимающему параметр String
и возвращающему экземпляр для использования.
пример
<Person xmlns:fx="http://javafx.com/fxml" fx:value="John"/>
Выход:
valueOf
Person(String)
fx:factory
Атрибут fx:factory
позволяет создавать объекты с использованием произвольных static
методов, которые не принимают параметры.
пример
<Person xmlns:fx="http://javafx.com/fxml" fx:factory="createPerson">
<name>
<String fx:value="John"/>
</name>
</Person>
Выход:
createPerson
Person()
setter
<fx:copy>
С помощью fx:copy
copy можно вызвать конструктор копирования. Указание fx:id
другого. source
атрибут тега будет вызывать конструктор копирования с этим объектом в качестве параметра.
Пример:
<ArrayList xmlns:fx="http://javafx.com/fxml">
<Person fx:id="p1" fx:constant="JOHN"/>
<fx:copy source="p1"/>
</ArrayList>
Выход
Person(Person)
getter
fx:constant
fx:constant
позволяет получить значение из static final
поля.
пример
<Person xmlns:fx="http://javafx.com/fxml" fx:constant="JOHN"/>
не будет выдавать какой-либо вывод, так как это просто ссылается на JOHN
который был создан при инициализации класса.
Настройка свойств
Существует несколько способов добавления данных в объект в файле fxml:
<property>
Тег с именем свойства может быть добавлен как дочерний элемент элемента, используемого для создания экземпляра. Ребенку этого тега присваивается свойство с помощью установщика или добавляется к содержимому свойства (свойства только для чтения / карты).
Свойство по умолчанию
Класс можно аннотировать с @DefaultProperty
аннотации @DefaultProperty
. В этом случае элементы могут быть непосредственно добавлены как дочерний элемент без использования элемента с именем свойства.
property="value"
Свойства могут быть назначены с использованием имени свойства в качестве имени атрибута и значения как значения атрибута. Это имеет тот же эффект, что и добавление следующего элемента в качестве дочернего элемента тега:
<property>
<String fx:value="value" />
</property>
статические сеттеры
Свойства можно также установить с помощью static
сеттеров. Это static
методы с именем setProperty
которые принимают элемент как первый параметр и значение, заданное как второй параметр. Эти методы могут указывать в любом классе и могут использоваться с использованием ContainingClass.property
вместо обычного имени свойства.
Примечание. В настоящее время кажется необходимым иметь соответствующий статический метод getter (то есть статический метод с именем getProperty
принимает элемент как параметр в том же классе, что и статический сеттер), чтобы это работало, если только тип значения не является String
.
Тип Принуждение
Следующий механизм используется для получения объекта правильного класса во время присвоений, например, для соответствия типу параметра метода сеттера.
Если классы назначаются, то используется само значение.
В противном случае значение преобразуется следующим образом
Тип цели | значение используется (значение источника s ) |
---|---|
Boolean , boolean | Boolean.valueOf(s) |
char , Character | s.toString.charAt(0) |
другой примитивный тип или тип обертки | соответствующий метод для типа цели, в случае, когда s является Number , значение valueOf(s.toString()) для типа обертки иначе |
BigInteger | BigInteger.valueOf(s.longValue()) является s это Number , new BigInteger(s.toString()) в противном случае |
BigDecimal | BigDecimal.valueOf(s.doubleValue()) является s это Number , new BigDecimal(s.toString()) в противном случае |
Число | Double.valueOf(s.toString()) если s.toString() содержит a . , Long.valueOf(s.toString()) противном случае |
Class | Class.forName(s.toString()) вызванный с использованием контекста ClassLoader текущего потока без инициализации класса |
перечисление | Результат метода valueOf , дополнительно преобразованный во всю строчную String разделенную _ вставленную перед каждой прописной буквой, если s является String которая начинается с буквы в нижнем регистре |
Другой | значение, возвращаемое static методом valueOf в targetType, которое имеет параметр, соответствующий типу s или суперклассу этого типа |
Примечание. Это поведение плохо документировано и может быть изменено.
пример
public enum Location {
WASHINGTON_DC,
LONDON;
}
package fxml.sample;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javafx.beans.DefaultProperty;
@DefaultProperty("items")
public class Sample {
private Location loaction;
public Location getLoaction() {
return loaction;
}
public void setLoaction(Location loaction) {
this.loaction = loaction;
}
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
int number;
private final List<Object> items = new ArrayList<>();
public List<Object> getItems() {
return items;
}
private final Map<String, Object> map = new HashMap<>();
public Map<String, Object> getMap() {
return map;
}
private BigInteger serialNumber;
public BigInteger getSerialNumber() {
return serialNumber;
}
public void setSerialNumber(BigInteger serialNumber) {
this.serialNumber = serialNumber;
}
@Override
public String toString() {
return "Sample{" + "loaction=" + loaction + ", number=" + number + ", items=" + items + ", map=" + map + ", serialNumber=" + serialNumber + '}';
}
}
package fxml.sample;
public class Container {
public static int getNumber(Sample sample) {
return sample.number;
}
public static void setNumber(Sample sample, int number) {
sample.number = number;
}
private final String value;
private Container(String value) {
this.value = value;
}
public static Container valueOf(String s) {
return new Container(s);
}
@Override
public String toString() {
return "42" + value;
}
}
Печать результата загрузки ниже fxml
файлов fxml
Sample{loaction=WASHINGTON_DC, number=5, items=[42a, 42b, 42c, 42d, 42e, 42f], map={answer=42, g=9.81, hello=42A, sample=Sample{loaction=null, number=33, items=[], map={}, serialNumber=null}}, serialNumber=4299}
<?xml version="1.0" encoding="UTF-8"?>
<?import java.lang.*?>
<?import fxml.sample.*?>
<Sample xmlns:fx="http://javafx.com/fxml/1" Container.number="5" loaction="washingtonDc">
<!-- set serialNumber property (type coercion) -->
<serialNumber>
<Container fx:value="99"/>
</serialNumber>
<!-- Add elements to default property-->
<Container fx:value="a"/>
<Container fx:value="b"/>
<Container fx:value="c"/>
<Container fx:value="d"/>
<Container fx:value="e"/>
<Container fx:value="f"/>
<!-- fill readonly map property -->
<map g="9.81">
<hello>
<Container fx:value="A"/>
</hello>
<answer>
<Container fx:value=""/>
</answer>
<sample>
<Sample>
<!-- static setter-->
<Container.number>
<Integer fx:value="33" />
</Container.number>
</Sample>
</sample>
</map>
</Sample>