javafx
FXML e controller
Ricerca…
Sintassi
- xmlns: fx = " http://javafx.com/fxml " // dichiarazione dello spazio dei nomi
Esempio FXML
Un documento FXML semplice che descrive un AnchorPane
contenente un pulsante e un nodo etichetta:
<?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>
Questo file FXML di esempio è associato a una classe controller. L'associazione tra FXML e la classe controller, in questo caso, viene effettuata specificando il nome della classe come valore dell'attributo fx:controller
nell'elemento radice di FXML: fx:controller="com.example.FXMLDocumentController"
. La classe controller consente l'esecuzione del codice Java in risposta alle azioni dell'utente sugli elementi dell'interfaccia utente definiti nel file 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
può essere utilizzato per caricare il file 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();
}
}
Il metodo di load
esegue diverse azioni ed è utile per capire l'ordine in cui si verificano. In questo semplice esempio:
FXMLLoader
legge e analizza il file FXML. Crea oggetti corrispondenti agli elementi definiti nel file e prende nota di eventuali attributifx:id
definiti su di essi.Poiché l'elemento radice del file FXML ha definito un attributo
fx:controller
,FXMLLoader
crea una nuova istanza della classe che specifica. Per impostazione predefinita ciò avviene richiamando il costruttore no-argument sulla classe specificata.Tutti gli elementi con gli attributi
fx:id
definiti che hanno campi nel controller con nomi di campi corrispondenti e che sonopublic
(non consigliato) o annotati@FXML
(consigliato) vengono "iniettati" in quei campi corrispondenti. Quindi in questo esempio, dal momento che esisteLabel
nel file FXML confx:id="label"
e un campo nel controller definito come@FXML private Label label ;
il campo
label
viene inizializzato con l'istanzaLabel
creata daFXMLLoader
.I gestori di eventi sono registrati con qualsiasi elemento nel file
onXXX="#..."
con le proprietàonXXX="#..."
definite. Questi gestori eventi invocano il metodo specificato nella classe controller. In questo esempio, poichéButton
haonAction="#handleButtonAction"
e il controller definisce un metodo@FXML private void handleButtonAction(ActionEvent event) { ... }
quando un'azione viene attivata sul pulsante (ad esempio, l'utente lo preme), questo metodo viene richiamato. Il metodo deve avere un tipo restituito
void
e può definire un parametro corrispondente al tipo di evento (ActionEvent
in questo esempio) oppure non può definire alcun parametro.Infine, se la classe controller definisce un metodo di
initialize
, questo metodo viene richiamato. Si noti che questo accade dopo che i campi@FXML
sono stati iniettati, quindi possono essere tranquillamente utilizzati con questo metodo e verranno inizializzati con le istanze corrispondenti agli elementi nel file FXML. Il metodoinitialize()
può accettare alcun parametro o può prendere unURL
e unResourceBundle
. In quest'ultimo caso, questi parametri verranno popolatiURL
rappresenta la posizione del file FXML e da qualsiasi set diResourceBundle
suFXMLLoader
tramiteloader.setResources(...)
. Ciascuno di questi può esserenull
se non sono stati impostati.
Controller annidati
Non è necessario creare l'intera interfaccia utente in un singolo FXML utilizzando un singolo controller.
Il <fx:include>
può essere utilizzato per includere un file fxml in un altro. Il controller del fxml incluso può essere iniettato nel controller del file incluso come qualsiasi altro oggetto creato da FXMLLoader
.
Questo viene fatto aggiungendo l'attributo fx:id
all'elemento <fx:include>
. In questo modo il controller del fxml incluso verrà iniettato nel campo con il nome <fx:id value>Controller
.
Esempi:
fx: valore id | nome del campo per l'iniezione |
---|---|
foo | fooController |
answer42 | answer42Controller |
Xyz | xYzController |
Esempio di fxmls
contatore
Questo è un fxml che contiene uno StackPane
con un nodo di Text
. Il controller per questo file fxml consente di ottenere il valore del contatore corrente e di incrementare il contatore:
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;
}
}
Compreso 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
Il controller del fxml incluso viene iniettato su questo controller. Qui il gestore dell'evento onAction
per il Button
viene utilizzato per incrementare il contatore.
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();
}
}
Gli fxmls possono essere caricati in questo modo, assumendo che il codice sia chiamato da una classe nello stesso pacchetto di outer.fxml
:
Parent parent = FXMLLoader.load(getClass().getResource("outer.fxml"));
Definisci i blocchi e
A volte un elemento deve essere creato al di fuori della solita struttura di oggetti in fxml.
Qui entra in gioco Define Blocks :
I contenuti all'interno di un elemento <fx:define>
non vengono aggiunti all'oggetto creato per l'elemento genitore.
Ogni elemento figlio di <fx:define>
richiede un attributo fx:id
.
Gli oggetti creati in questo modo possono essere successivamente referenziati usando l'elemento <fx:reference>
o usando l'espressione binding.
L'elemento <fx:reference>
può essere utilizzato per fare riferimento a qualsiasi elemento con un attributo fx:id
che viene gestito prima che l'elemento <fx:reference>
venga gestito utilizzando lo stesso valore dell'attributo fx:id
dell'elemento di riferimento nel attributo source
dell'elemento <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>
Trasmissione dei dati a FXML - accesso al controller esistente
Problema: alcuni dati devono essere passati a una scena caricata da un fxml.
Soluzione
Specificare un controller utilizzando l'attributo fx:controller
e ottenere l'istanza del controller creata durante il processo di caricamento FXMLLoader
utilizzata per caricare l'fxml.
Aggiungi metodi per passare i dati all'istanza del controller e gestisci i dati con questi metodi.
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>
controllore
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);
}
}
Codice utilizzato per caricare il file 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);
Trasmissione dei dati a FXML - Specifica dell'istanza del controllore
Problema: alcuni dati devono essere passati a una scena caricata da un fxml.
Soluzione
Impostare il controller usando l'istanza FXMLLoader
utilizzata in seguito per caricare il file fxml.
Assicurarsi che il controller contenga i dati rilevanti prima di caricare il file fxml.
Nota: in questo caso il file fxml non deve contenere l'attributo 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>
controllore
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);
}
}
Codice utilizzato per caricare il file 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();
Passaggio dei parametri a FXML - utilizzando un controllerFactory
Problema: alcuni dati devono essere passati a una scena caricata da un fxml.
Soluzione
Specificare una factory controller che è responsabile della creazione dei controller. Passa i dati all'istanza del controller creata dalla fabbrica.
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>
controllore
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);
}
}
Codice utilizzato per caricare il file 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();
Questo può sembrare complesso, ma può essere utile, se l'fxml dovrebbe essere in grado di decidere, quale classe di controller ha bisogno.
Creazione di istanze in FXML
La seguente classe è utilizzata per dimostrare come possono essere create istanze di classi:
L'annotazione in Person(@NamedArg("name") String name)
deve essere rimossa, poiché l'annotazione @NamedArg
non è disponibile.
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();
}
}
Supponiamo che la classe Person
sia già stata inizializzata prima di caricare il file fxml.
Una nota sulle importazioni
Nel seguente esempio fxml la sezione delle importazioni sarà esclusa. Comunque il fxml dovrebbe iniziare con
<?xml version="1.0" encoding="UTF-8"?>
seguito da una sezione delle importazioni che importa tutte le classi utilizzate nel file fxml. Tali importazioni sono simili alle importazioni non statiche, ma vengono aggiunte come istruzioni di elaborazione. È necessario importare anche le classi dal pacchetto java.lang
.
In questo caso è necessario aggiungere le seguenti importazioni:
<?import java.lang.*?>
<?import fxml.sample.Person?>
@NamedArg
annotato @NamedArg
Se esiste un costruttore in cui ogni parametro è annotato con @NamedArg
e tutti i valori delle annotazioni @NamedArg
sono presenti in fxml, il costruttore verrà utilizzato con tali parametri.
<Person name="John"/>
<Person xmlns:fx="http://javafx.com/fxml">
<name>
<String fx:value="John"/>
</name>
</Person>
Entrambi producono il seguente output della console, se caricato:
Person(String)
Nessun costruttore di args
Se non è disponibile un @NamedArg
annotato @NamedArg
adatto, @NamedArg
utilizzato il costruttore che non accetta parametri.
Rimuovi l'annotazione @NamedArg
dal costruttore e prova a caricare.
<Person name="John"/>
Questo userà il costruttore senza parametri.
Produzione:
Person()
setter
fx:value
attributo fx:value
L'attributo fx:value
può essere utilizzato per passare il suo valore a un metodo valueOf
static
valueOf
un parametro String
e restituisce l'istanza da utilizzare.
Esempio
<Person xmlns:fx="http://javafx.com/fxml" fx:value="John"/>
Produzione:
valueOf
Person(String)
fx:factory
L'attributo fx:factory
consente la creazione di oggetti usando metodi static
arbitrari che non prendono parametri.
Esempio
<Person xmlns:fx="http://javafx.com/fxml" fx:factory="createPerson">
<name>
<String fx:value="John"/>
</name>
</Person>
Produzione:
createPerson
Person()
setter
<fx:copy>
Usando fx:copy
un costruttore di copia può essere invocato. Specificare fx:id
di un altro L'attributo source
del tag invocherà il costruttore di copia con quell'oggetto come parametro.
Esempio:
<ArrayList xmlns:fx="http://javafx.com/fxml">
<Person fx:id="p1" fx:constant="JOHN"/>
<fx:copy source="p1"/>
</ArrayList>
Produzione
Person(Person)
getter
fx:constant
fx:constant
consente di ottenere un valore da un campo static final
.
Esempio
<Person xmlns:fx="http://javafx.com/fxml" fx:constant="JOHN"/>
non produrrà alcun output, poiché questo si riferisce solo a JOHN
che è stato creato durante l'inizializzazione della classe.
Impostazione delle proprietà
Esistono diversi modi per aggiungere dati a un oggetto in fxml:
tag <property>
Un tag con il nome di una proprietà può essere aggiunto come figlio di un elemento utilizzato per creare un'istanza. Il figlio di questo tag viene assegnato alla proprietà utilizzando il setter o aggiunto al contenuto della proprietà (proprietà readonly list / map).
Proprietà di default
Una classe può essere annotata con l'annotazione @DefaultProperty
. In questo caso gli elementi possono essere aggiunti direttamente come elemento figlio senza utilizzare un elemento con il nome della proprietà.
property="value"
attributo property="value"
Le proprietà possono essere assegnate utilizzando il nome della proprietà come nome attributo e il valore come valore dell'attributo. Questo ha lo stesso effetto dell'aggiunta del seguente elemento come figlio del tag:
<property>
<String fx:value="value" />
</property>
setter statici
Le proprietà possono essere impostate anche con setter static
. Questi sono metodi static
denominati setProperty
che accettano l'elemento come primo parametro e il valore da impostare come secondo parametro. Questi metodi possono essere inseriti in qualsiasi classe e possono essere utilizzati utilizzando ContainingClass.property
anziché il solito nome di proprietà.
Nota: attualmente sembra necessario disporre di un metodo getter statico corrispondente (ovvero un metodo statico denominato getProperty
prende l'elemento come parametro nella stessa classe del setter statico) affinché funzioni, a meno che il tipo di valore non sia String
.
Tipo coercizione
Il seguente meccanismo viene utilizzato per ottenere un oggetto della classe corretta durante le assegnazioni, ad esempio per soddisfare il tipo di parametro di un metodo setter.
Se le classi sono assegnabili, viene utilizzato il valore stesso.
Altrimenti il valore viene convertito come segue
Tipo di bersaglio | valore usato (valore sorgente s ) |
---|---|
Boolean , boolean | Boolean.valueOf(s) |
char , Character | s.toString.charAt(0) |
altro tipo primitivo o tipo di wrapper | metodo appropriato per il tipo di destinazione, nel caso in cui s sia un Number , il valueOf(s.toString()) per il tipo di wrapper in caso contrario |
BigInteger | BigInteger.valueOf(s.longValue()) is s è un Number , new BigInteger(s.toString()) altrimenti |
BigDecimal | BigDecimal.valueOf(s.doubleValue()) is s è un Number , new BigDecimal(s.toString()) altrimenti |
Numero | Double.valueOf(s.toString()) se s.toString() contiene a . , Long.valueOf(s.toString()) altrimenti |
Class | Class.forName(s.toString()) invocato utilizzando il contesto ClassLoader del thread corrente senza inizializzare la classe |
enum | Il risultato del metodo valueOf , ulteriormente convertito in una String tutta maiuscola separata da _ inserita prima di ogni lettera maiuscola, se s è una String che inizia con una lettera minuscola |
altro | il valore restituito da un metodo valueOf static in targetType, che ha un parametro corrispondente al tipo di s o una superclasse di quel tipo |
Nota: questo comportamento non è ben documentato e potrebbe essere soggetto a modifiche.
Esempio
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;
}
}
Stampa il risultato del caricamento dei seguenti fxml
file 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>