javafx
FXML y controladores
Buscar..
Sintaxis
- xmlns: fx = " http://javafx.com/fxml " // declaración de espacio de nombres
Ejemplo FXML
Un documento FXML simple que describe un AnchorPane
contiene un botón y un nodo de etiqueta:
<?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>
Este archivo FXML de ejemplo está asociado con una clase de controlador. La asociación entre el FXML y la clase del controlador, en este caso, se realiza especificando el nombre de la clase como el valor del atributo fx:controller
en el elemento raíz del FXML: fx:controller="com.example.FXMLDocumentController"
. La clase del controlador permite que el código Java se ejecute en respuesta a las acciones del usuario en los elementos de la IU definidos en el archivo 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
}
}
Se puede FXMLLoader
un FXMLLoader
para cargar el archivo 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();
}
}
El método de load
realiza varias acciones, y es útil para entender el orden en que suceden. En este sencillo ejemplo:
El
FXMLLoader
lee y analiza el archivo FXML. Crea objetos correspondientes a los elementos definidos en el archivo y toma nota de los atributosfx:id
definidos en ellos.Dado que el elemento raíz del archivo FXML definió un atributo
fx:controller
,FXMLLoader
crea una nueva instancia de la clase que especifica. De forma predeterminada, esto sucede al invocar el constructor sin argumentos en la clase especificada.Todos los elementos con atributos
fx:id
definidos que tienen campos en el controlador con nombres de campo coincidentes y que sonpublic
(no recomendados) o anotados en@FXML
(recomendado) se "inyectan" en esos campos correspondientes. Entonces, en este ejemplo, ya que hay unaLabel
en el archivo FXML confx:id="label"
y un campo en el controlador definido como@FXML private Label label ;
el campo de
label
se inicializa con la instancia deLabel
creada porFXMLLoader
.Los controladores de eventos se registran con cualquier elemento en el archivo
onXXX="#..."
con lasonXXX="#..."
definidas. Estos controladores de eventos invocan el método especificado en la clase de controlador. En este ejemplo, dado que elButton
tieneonAction="#handleButtonAction"
, y el controlador define un método@FXML private void handleButtonAction(ActionEvent event) { ... }
cuando se dispara una acción en el botón (por ejemplo, el usuario lo presiona), se invoca este método. El método debe tener un tipo de retorno
void
y puede definir un parámetro que coincida con el tipo de evento (ActionEvent
en este ejemplo) o no puede definir ningún parámetro.Finalmente, si la clase del controlador define un método de
initialize
, se invoca este método. Observe que esto sucede después de que se hayan inyectado los campos@FXML
, por lo que se puede acceder a ellos de manera segura con este método y se inicializarán con las instancias correspondientes a los elementos en el archivo FXML. El métodoinitialize()
puede tomar ningún parámetro o puede tomar unaURL
y unResourceBundle
. En este último caso, estos parámetros se rellenarán con laURL
representa la ubicación del archivo FXML y cualquier conjunto deResourceBundle
en elFXMLLoader
través deloader.setResources(...)
. Cualquiera de estos puede sernull
si no se establecieron.
Controladores anidados
No es necesario crear la IU completa en un solo FXML utilizando un solo controlador.
La etiqueta <fx:include>
se puede usar para incluir un archivo fxml en otro. El controlador del fxml incluido se puede inyectar en el controlador del archivo de inclusión al igual que cualquier otro objeto creado por FXMLLoader
.
Esto se hace agregando el atributo fx:id
al elemento <fx:include>
. De esta manera, el controlador del fxml incluido se inyectará en el campo con el nombre <fx:id value>Controller
.
Ejemplos:
fx: valor de id | nombre de campo para inyección |
---|---|
foo | fooController |
respuesta42 | answer42Controller |
xYz | xYzController |
Muestra fxmls
Mostrador
Este es un fxml que contiene un StackPane
con un nodo de Text
. El controlador para este archivo fxml permite obtener el valor del contador actual, así como incrementar el contador:
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;
}
}
Incluyendo 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>
Controlador externo
El controlador del fxml incluido se inyecta en este controlador. Aquí, el controlador para el evento onAction
para el Button
se utiliza para incrementar el contador.
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();
}
}
Los fxmls se pueden cargar de esta manera, asumiendo que el código se llama desde una clase en el mismo paquete que outer.fxml
:
Parent parent = FXMLLoader.load(getClass().getResource("outer.fxml"));
Definir bloques y
A veces, un elemento debe crearse fuera de la estructura de objeto habitual en el fxml.
Aquí es donde entran en juego los bloques definidos:
Los contenidos dentro de un elemento <fx:define>
no se agregan al objeto creado para el elemento padre.
Cada elemento hijo de <fx:define>
necesita un atributo fx:id
.
Los objetos creados de esta manera pueden ser referenciados posteriormente usando el elemento <fx:reference>
o usando el enlace de expresión.
El elemento <fx:reference>
se puede usar para hacer referencia a cualquier elemento con un atributo fx:id
que se maneja antes de que el elemento <fx:reference>
se maneje utilizando el mismo valor que el atributo fx:id
del elemento referenciado en el Atributo source
del 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>
Pasando datos a FXML - accediendo al controlador existente
Problema: algunos datos deben pasarse a una escena cargada desde un fxml.
Solución
Especifique un controlador utilizando el atributo fx:controller
y obtenga la instancia del controlador creada durante el proceso de carga de la instancia de FXMLLoader
utilizada para cargar el fxml.
Agregue métodos para pasar los datos a la instancia del controlador y maneje los datos en esos métodos.
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>
Controlador
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);
}
}
Código utilizado para cargar el 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);
Pasando datos a FXML - Especificando la instancia del controlador
Problema: algunos datos deben pasarse a una escena cargada desde un fxml.
Solución
Configure el controlador utilizando la instancia de FXMLLoader
usa más adelante para cargar el fxml.
Asegúrese de que el controlador contenga los datos relevantes antes de cargar el fxml.
Nota: en este caso, el archivo fxml no debe contener el atributo 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>
Controlador
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);
}
}
Código utilizado para cargar el 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();
Pasando parámetros a FXML - usando un controllerFactory
Problema: algunos datos deben pasarse a una escena cargada desde un fxml.
Solución
Especifique una fábrica de controladores que sea responsable de crear los controladores. Pasar los datos a la instancia del controlador creado por la fábrica.
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>
Controlador
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);
}
}
Código utilizado para cargar el fxml
Cadena de datos = "¡Hola mundo!";
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();
Esto puede parecer complejo, pero puede ser útil, si el fxml debería poder decidir, qué clase de controlador necesita.
Creación de instancias en FXML
La siguiente clase se utiliza para demostrar cómo se pueden crear instancias de clases:
La anotación en Person(@NamedArg("name") String name)
debe eliminarse, ya que la anotación @NamedArg
no está disponible.
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();
}
}
Suponga que la clase Person
ya se ha inicializado antes de cargar el fxml.
Una nota sobre las importaciones.
En el siguiente ejemplo de fxml, la sección de importaciones quedará fuera. Sin embargo, el fxml debe comenzar con
<?xml version="1.0" encoding="UTF-8"?>
seguido de una sección de importaciones que importa todas las clases utilizadas en el archivo fxml. Esas importaciones son similares a las importaciones no estáticas, pero se agregan como instrucciones de procesamiento. Incluso las clases del paquete java.lang
necesitan ser importadas.
En este caso se deben agregar las siguientes importaciones:
<?import java.lang.*?>
<?import fxml.sample.Person?>
@NamedArg
anotado @NamedArg
Si hay un constructor donde todos los parámetros se anotan con @NamedArg
y todos los valores de las anotaciones de @NamedArg
están presentes en el fxml, el constructor se usará con esos parámetros.
<Person name="John"/>
<Person xmlns:fx="http://javafx.com/fxml">
<name>
<String fx:value="John"/>
</name>
</Person>
Ambos dan como resultado la siguiente salida de consola, si está cargada:
Person(String)
No args constructor
Si no hay un constructor anotado de @NamedArg
adecuado, se utilizará el constructor que no toma parámetros.
Elimine la anotación @NamedArg
del constructor e intente cargar.
<Person name="John"/>
Esto utilizará el constructor sin parámetros.
Salida:
Person()
setter
fx:value
atributo de fx:value
El atributo fx:value
se puede usar para pasar su valor a un método valueOf
static
que toma un parámetro String
y devuelve la instancia a usar.
Ejemplo
<Person xmlns:fx="http://javafx.com/fxml" fx:value="John"/>
Salida:
valueOf
Person(String)
fx:factory
El atributo fx:factory
permite la creación de objetos utilizando métodos static
arbitrarios que no toman parámetros.
Ejemplo
<Person xmlns:fx="http://javafx.com/fxml" fx:factory="createPerson">
<name>
<String fx:value="John"/>
</name>
</Person>
Salida:
createPerson
Person()
setter
<fx:copy>
Usando fx:copy
se puede invocar un constructor de copia. Especificar el fx:id
de otro El atributo de source
de la etiqueta invocará al constructor de copia con ese objeto como parámetro.
Ejemplo:
<ArrayList xmlns:fx="http://javafx.com/fxml">
<Person fx:id="p1" fx:constant="JOHN"/>
<fx:copy source="p1"/>
</ArrayList>
Salida
Person(Person)
getter
fx:constant
fx:constant
permite obtener un valor de un campo static final
.
Ejemplo
<Person xmlns:fx="http://javafx.com/fxml" fx:constant="JOHN"/>
no producirá ningún resultado, ya que esto solo hace referencia a JOHN
que se creó al inicializar la clase.
Configuración de propiedades
Hay varias formas de agregar datos a un objeto en fxml:
etiqueta <property>
Una etiqueta con el nombre de una propiedad se puede agregar como elemento secundario de un elemento utilizado para crear una instancia. El elemento secundario de esta etiqueta se asigna a la propiedad mediante el establecedor o se agrega al contenido de la propiedad (propiedades de lista / mapa de solo lectura).
Propiedad por defecto
Una clase se puede anotar con la anotación @DefaultProperty
. En este caso, los elementos se pueden agregar directamente como elemento secundario sin utilizar un elemento con el nombre de la propiedad.
property="value"
atributo property="value"
Las propiedades se pueden asignar utilizando el nombre de la propiedad como nombre de atributo y el valor como valor de atributo. Esto tiene el mismo efecto que agregar el siguiente elemento como elemento secundario de la etiqueta:
<property>
<String fx:value="value" />
</property>
colocadores estáticos
Las propiedades también se pueden establecer utilizando establecedores static
. Estos son métodos static
llamados setProperty
que toman el elemento como primer parámetro y el valor para establecer como segundo parámetro. Esos métodos se pueden anular en cualquier clase y se pueden usar usando ContainingClass.property
lugar del nombre de propiedad habitual.
Nota: actualmente parece necesario tener un método getter estático correspondiente (es decir, un método estático denominado getProperty
toma el elemento como parámetro en la misma clase que el establecedor estático) para que esto funcione a menos que el tipo de valor sea String
.
Tipo de coerción
El siguiente mecanismo se utiliza para obtener un objeto de la clase correcta durante las asignaciones, por ejemplo, para adaptarse al tipo de parámetro de un método de establecimiento.
Si las clases son asignables, entonces se usa el valor mismo.
De lo contrario, el valor se convierte de la siguiente manera
Tipo de objetivo | valor utilizado (valor fuente s ) |
---|---|
Boolean , boolean | Boolean.valueOf(s) |
char , Character | s.toString.charAt(0) |
otro tipo primitivo o tipo de envoltura | método apropiado para el tipo de destino, en el caso de que s sea un Number , el valueOf(s.toString()) para el tipo de contenedor de lo contrario |
BigInteger | BigInteger.valueOf(s.longValue()) es s es un Number , new BigInteger(s.toString()) contrario |
BigDecimal | BigDecimal.valueOf(s.doubleValue()) es s es un Number , new BigDecimal(s.toString()) contrario |
Número | Double.valueOf(s.toString()) si s.toString() contiene a . , Long.valueOf(s.toString()) contrario |
Class | Class.forName(s.toString()) invocado usando el contexto ClassLoader del hilo actual sin inicializar la clase |
enumerar | El resultado del método valueOf , que además se convierte en una String mayúsculas separadas por _ insertada antes de cada letra en mayúscula, si s es una String que comienza con una letra en minúscula |
otro | el valor devuelto por un método valueOf static en targetType, que tiene un parámetro que coincide con el tipo de s o una superclase de ese tipo |
Nota: este comportamiento no está bien documentado y podría estar sujeto a cambios.
Ejemplo
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;
}
}
Imprimiendo el resultado de cargar los siguientes rendimientos de archivos 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>