Recherche…


Syntaxe

Exemple FXML

Un document FXML simple décrivant un AnchorPane contenant un bouton et un noeud d’étiquette:

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

Cet exemple de fichier FXML est associé à une classe de contrôleur. L'association entre la classe FXML et la classe de contrôleur, dans ce cas, s'effectue en spécifiant le nom de la classe comme valeur de l'attribut fx:controller dans l'élément racine du fichier FXML: fx:controller="com.example.FXMLDocumentController" . La classe du contrôleur permet l'exécution du code Java en réponse aux actions de l'utilisateur sur les éléments d'interface utilisateur définis dans le fichier 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
    }    
    
}

Un FXMLLoader peut être utilisé pour charger le fichier 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();
    }

}

La méthode de load effectue plusieurs actions et il est utile de comprendre l'ordre dans lequel elles se produisent. Dans cet exemple simple:

  1. Le FXMLLoader lit et analyse le fichier FXML. Il crée des objets correspondant aux éléments définis dans le fichier et note tous les attributs fx:id définis sur ceux-ci.

  2. L'élément racine du fichier FXML définissant un attribut fx:controller , FXMLLoader crée une nouvelle instance de la classe spécifiée. Par défaut, cela se produit en invoquant le constructeur sans argument sur la classe spécifiée.

  3. Tous les éléments avec des attributs fx:id définis qui ont des champs dans le contrôleur avec des noms de champs correspondants, et qui sont public (non recommandés) ou annotés @FXML (recommandé) sont "injectés" dans ces champs correspondants. Donc, dans cet exemple, comme il y a une Label dans le fichier FXML avec fx:id="label" et un champ dans le contrôleur défini comme

    @FXML
    private Label label ;
    

    le champ label est initialisé avec l'instance Label créée par FXMLLoader .

  4. Les gestionnaires d'événements sont enregistrés avec tous les éléments du fichier onXXX="#..." avec les onXXX="#..." définies. Ces gestionnaires d'événements appellent la méthode spécifiée dans la classe du contrôleur. Dans cet exemple, puisque le Button a onAction="#handleButtonAction" , et que le contrôleur définit une méthode

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

    Lorsqu'une action est déclenchée sur le bouton (par exemple, l'utilisateur appuie dessus), cette méthode est appelée. La méthode doit avoir un type de retour void et peut soit définir un paramètre correspondant au type d'événement ( ActionEvent dans cet exemple), soit ne définir aucun paramètre.

  5. Enfin, si la classe du contrôleur définit une méthode d' initialize , cette méthode est appelée. Notez que cela se produit une fois que les champs @FXML ont été injectés, ils peuvent donc être @FXML toute sécurité dans cette méthode et seront initialisés avec les instances correspondant aux éléments du fichier FXML. La méthode initialize() ne peut prendre aucun paramètre ou peut prendre une URL et un ResourceBundle . Dans ce dernier cas, ces paramètres seront renseignés par l’ URL représentant l’emplacement du fichier FXML et tout ensemble ResourceBundle défini sur FXMLLoader via loader.setResources(...) . L'une ou l'autre peut être null si elles n'ont pas été définies.

Contrôleurs imbriqués

Il n'est pas nécessaire de créer l'interface utilisateur complète dans un seul fichier FXML à l'aide d'un seul contrôleur.

La <fx:include> peut être utilisée pour inclure un fichier fxml dans un autre. Le contrôleur du fichier fxml inclus peut être injecté dans le contrôleur du fichier inclus, comme tout autre objet créé par FXMLLoader .

Cela se fait en ajoutant l'attribut fx:id à l'élément <fx:include> . De cette façon, le contrôleur du fichier fxml inclus sera injecté dans le champ avec le nom <fx:id value>Controller .

Exemples:

fx: valeur de l'identifiant nom du champ pour l'injection
foo fooController
réponse42 answer42Controller
xYz xYzController

Échantillon de fxml

Compteur

Ceci est un fichier fxml contenant un StackPane avec un noeud Text . Le contrôleur de ce fichier fxml permet d’obtenir la valeur actuelle du compteur et d’incrémenter le compteur:

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

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

Contrôleur externe

Le contrôleur du fichier fxml inclus est injecté dans ce contrôleur. Ici, le gestionnaire de l'événement onAction pour le Button est utilisé pour incrémenter le compteur.

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();
    }

}

Les fxml peuvent être chargés comme ceci, en supposant que le code est appelé depuis une classe dans le même paquet que outer.fxml :

Parent parent = FXMLLoader.load(getClass().getResource("outer.fxml"));

Définir des blocs et

Parfois, un élément doit être créé en dehors de la structure d'objet habituelle dans le fichier fxml.

C'est ici qu'interviennent les blocs de définition :

Les contenus contenus dans un élément <fx:define> ne sont pas ajoutés à l'objet créé pour l'élément parent.

Chaque élément enfant du <fx:define> besoin d'un attribut fx:id .

Les objets créés de cette manière peuvent être référencés ultérieurement en utilisant l'élément <fx:reference> ou en utilisant la liaison d'expression.

L'élément <fx:reference> peut être utilisé pour référencer un élément avec un attribut fx:id qui est géré avant que l'élément <fx:reference> soit géré en utilisant la même valeur que l'attribut fx:id de l'élément référencé dans le attribut source de l'élément <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>

Passer des données à FXML - accéder au contrôleur existant

Problème: Certaines données doivent être transmises à une scène chargée à partir d'un fichier fxml.

Solution

Spécifiez un contrôleur à l'aide de l'attribut fx:controller et obtenez l'instance de contrôleur créée lors du chargement à partir de l'instance FXMLLoader utilisée pour charger le fichier fxml.

Ajoutez des méthodes pour transmettre les données à l'instance de contrôleur et gérez les données dans ces méthodes.

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>

Manette

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);
    }

}

Code utilisé pour charger le fichier 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);

Transmission de données à FXML - Spécification de l'instance de contrôleur

Problème: Certaines données doivent être transmises à une scène chargée à partir d'un fichier fxml.

Solution

Définissez le contrôleur à l'aide de l'instance FXMLLoader utilisée ultérieurement pour charger le fichier fxml.

Assurez-vous que le contrôleur contient les données pertinentes avant de charger le fichier fxml.

Remarque: dans ce cas, le fichier fxml ne doit pas contenir l’attribut 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>

Manette

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);
    }

}

Code utilisé pour charger le fichier 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();

Passer des paramètres à FXML - en utilisant un controllerFactory

Problème: Certaines données doivent être transmises à une scène chargée à partir d'un fichier fxml.

Solution

Spécifiez une fabrique de contrôleurs responsable de la création des contrôleurs. Transmettez les données à l'instance de contrôleur créée par la fabrique.

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>

Manette

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);
    }

}

Code utilisé pour charger le fichier fxml

String data = "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();

Cela peut sembler complexe, mais cela peut être utile si le fxml doit pouvoir décider quelle classe de contrôleur il a besoin.

Création d'instance dans FXML

La classe suivante est utilisée pour démontrer comment créer des instances de classes:

JavaFX 8

L'annotation dans Person(@NamedArg("name") String name) doit être supprimée car l'annotation @NamedArg est indisponible.

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();
    }
    
}

Supposons que la classe Person a déjà été initialisée avant de charger le fichier fxml.

Une note sur les importations

Dans l'exemple fxml suivant, la section des importations sera omise. Cependant, le fxml devrait commencer par

<?xml version="1.0" encoding="UTF-8"?>

suivie par une section d'importation important toutes les classes utilisées dans le fichier fxml. Ces importations sont similaires aux importations non statiques, mais sont ajoutées comme instructions de traitement. Même les classes du package java.lang doivent être importées.

Dans ce cas, les importations suivantes doivent être ajoutées:

<?import java.lang.*?>
<?import fxml.sample.Person?>

JavaFX 8

@NamedArg constructeur annoté

S'il existe un constructeur où chaque paramètre est annoté avec @NamedArg et que toutes les valeurs des annotations @NamedArg sont présentes dans le fichier fxml, le constructeur sera utilisé avec ces paramètres.

<Person name="John"/>
<Person xmlns:fx="http://javafx.com/fxml">
    <name>
        <String fx:value="John"/>
    </name>
</Person>

Les deux résultent dans la sortie de console suivante, si chargée:

Person(String)

Aucun constructeur args

S'il n'y a pas de constructeur annoté @NamedArg approprié disponible, le constructeur qui ne prend aucun paramètre sera utilisé.

Supprimez l'annotation @NamedArg du constructeur et essayez de charger.

<Person name="John"/>

Cela utilisera le constructeur sans paramètres.

Sortie:

Person()
setter

attribut fx:value

L'attribut fx:value peut être utilisé pour transmettre sa valeur à une méthode valueOf static prenant un paramètre String et renvoyant l'instance à utiliser.

Exemple

<Person xmlns:fx="http://javafx.com/fxml" fx:value="John"/>

Sortie:

valueOf
Person(String)

fx:factory

L'attribut fx:factory permet de créer des objets en utilisant des méthodes static arbitraires qui ne prennent pas de paramètres.

Exemple

<Person xmlns:fx="http://javafx.com/fxml" fx:factory="createPerson">
    <name>
        <String fx:value="John"/>
    </name>
</Person>

Sortie:

createPerson
Person()
setter

<fx:copy>

En utilisant fx:copy un constructeur de copie peut être appelé. Spécifier le fx:id d'un autre L'attribut source de la balise invoquera le constructeur de copie avec cet objet comme paramètre.

Exemple:

<ArrayList xmlns:fx="http://javafx.com/fxml">
    <Person fx:id="p1" fx:constant="JOHN"/>
    <fx:copy source="p1"/>
</ArrayList>

Sortie

Person(Person)
getter

fx:constant

fx:constant permet d'obtenir une valeur à partir d'un static final .

Exemple

<Person xmlns:fx="http://javafx.com/fxml" fx:constant="JOHN"/>

ne produira aucune sortie, car cela fait juste référence à JOHN qui a été créé lors de l'initialisation de la classe.

Définition des propriétés

Il existe plusieurs manières d’ajouter des données à un objet dans fxml:

<property>

Une balise avec le nom d'une propriété peut être ajoutée en tant qu'enfant d'un élément utilisé pour créer une instance. L'enfant de cette balise est affecté à la propriété à l'aide du setter ou ajouté au contenu de la propriété (liste / propriétés en lecture seule).

Propriété par défaut

Une classe peut être annotée avec l'annotation @DefaultProperty . Dans ce cas, les éléments peuvent être ajoutés directement en tant qu’élément enfant sans utiliser d’élément portant le nom de la propriété.

property="value" attribut property="value"

Les propriétés peuvent être affectées en utilisant le nom de la propriété comme nom d'attribut et la valeur comme valeur d'attribut. Cela a le même effet que d'ajouter l'élément suivant en tant qu'enfant de la balise:

<property>
    <String fx:value="value" />
</property>

dispositifs statiques

Les propriétés peuvent également être définies à l'aide de paramètres static . Ce sont static méthodes static nommées setProperty qui prennent l'élément comme premier paramètre et la valeur à définir comme second paramètre. Ces méthodes peuvent être utilisées dans n'importe quelle classe et peuvent être utilisées avec ContainingClass.property au lieu du nom de propriété habituel.

Note: Actuellement, il semble nécessaire d'avoir une méthode getter statique correspondante (c'est-à-dire une méthode statique appelée getProperty prenant l'élément comme paramètre dans la même classe que le setter statique) pour que cela fonctionne à moins que le type de valeur soit String .

Type de coercition

Le mécanisme suivant est utilisé pour obtenir un objet de la classe correcte lors des affectations, par exemple pour s'adapter au type de paramètre d'une méthode de réglage.

Si les classes sont assignables, la valeur elle-même est utilisée.

Sinon, la valeur est convertie comme suit

Type de cible valeur utilisée (valeur source s )
Boolean boolean Boolean.valueOf(s)
char , Character s.toString.charAt(0)
autre type primitif ou type de wrapper méthode appropriée pour le type de cible, dans le cas où le s est un Number , le valueOf(s.toString()) pour le type de wrapper sinon
BigInteger BigInteger.valueOf(s.longValue()) is s est un Number , new BigInteger(s.toString()) sinon
BigDecimal BigDecimal.valueOf(s.doubleValue()) is s est un Number , new BigDecimal(s.toString()) sinon
Nombre Double.valueOf(s.toString()) si s.toString() contient un . , Long.valueOf(s.toString()) sinon
Class Class.forName(s.toString()) à l'aide du contexte ClassLoader du thread en cours sans initialiser la classe
enum Le résultat de la méthode valueOf , converti en une String tout en majuscule séparée par _ inséré avant chaque lettre majuscule, si s est une String qui commence par une lettre minuscule
autre la valeur renvoyée par une méthode valueOf static dans le targetType, qui a un paramètre correspondant au type de s ou une super-classe de ce type

Remarque: Ce comportement n'est pas bien documenté et pourrait être sujet à modification.

Exemple

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

}

Impression du résultat du chargement du fichier fxml ci-dessous

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
Sous licence CC BY-SA 3.0
Non affilié à Stack Overflow