javafx
FXML et contrôleurs
Recherche…
Syntaxe
- xmlns: fx = " http://javafx.com/fxml " // déclaration d'espace de noms
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:
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 attributsfx:id
définis sur ceux-ci.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.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 sontpublic
(non recommandés) ou annotés@FXML
(recommandé) sont "injectés" dans ces champs correspondants. Donc, dans cet exemple, comme il y a uneLabel
dans le fichier FXML avecfx:id="label"
et un champ dans le contrôleur défini comme@FXML private Label label ;
le champ
label
est initialisé avec l'instanceLabel
créée parFXMLLoader
.Les gestionnaires d'événements sont enregistrés avec tous les éléments du fichier
onXXX="#..."
avec lesonXXX="#..."
définies. Ces gestionnaires d'événements appellent la méthode spécifiée dans la classe du contrôleur. Dans cet exemple, puisque leButton
aonAction="#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.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éthodeinitialize()
ne peut prendre aucun paramètre ou peut prendre uneURL
et unResourceBundle
. Dans ce dernier cas, ces paramètres seront renseignés par l’URL
représentant l’emplacement du fichier FXML et tout ensembleResourceBundle
défini surFXMLLoader
vialoader.setResources(...)
. L'une ou l'autre peut êtrenull
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:
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?>
@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>