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>