Поиск…


Синтаксис

Пример 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 выполняет несколько действий, и полезно понять порядок их возникновения. В этом простом примере:

  1. FXMLLoader считывает и анализирует файл FXML. Он создает объекты, соответствующие элементам, определенным в файле, и учитывает любые атрибуты fx:id определенные на них.

  2. Поскольку корневой элемент файла FXML определяет атрибут fx:controller , FXMLLoader создает новый экземпляр класса, который он задает. По умолчанию это происходит при вызове конструктора без аргументов в указанном классе.

  3. Любые элементы с определенными атрибутами fx:id которые имеют поля в контроллере с соответствующими именами полей и которые являются либо public (не рекомендуется), либо аннотированными @FXML (рекомендуется), вводятся в соответствующие поля. Итак, в этом примере, поскольку в файле FXML есть Label с fx:id="label" и поле в контроллере, определенное как

    @FXML
    private Label label ;
    

    поле label инициализируется экземпляром Label созданным FXMLLoader .

  4. Обработчики событий регистрируются любыми элементами в файле FXML с установленными свойствами onXXX onXXX="#..." . Эти обработчики событий вызывают указанный метод в классе контроллера. В этом примере, поскольку Button имеет onAction="#handleButtonAction" , а контроллер определяет метод

    @FXML
    private void handleButtonAction(ActionEvent event) { ... }
    

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

  5. Наконец, если класс контроллера определяет метод 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

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

JavaFX 8

Аннотацию в 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?>

JavaFX 8

Аннотированный конструктор @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>


Modified text is an extract of the original Stack Overflow Documentation
Лицензировано согласно CC BY-SA 3.0
Не связан с Stack Overflow