Buscar..


Sintaxis

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:

  1. El FXMLLoader lee y analiza el archivo FXML. Crea objetos correspondientes a los elementos definidos en el archivo y toma nota de los atributos fx:id definidos en ellos.

  2. 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.

  3. Todos los elementos con atributos fx:id definidos que tienen campos en el controlador con nombres de campo coincidentes y que son public (no recomendados) o anotados en @FXML (recomendado) se "inyectan" en esos campos correspondientes. Entonces, en este ejemplo, ya que hay una Label en el archivo FXML con fx:id="label" y un campo en el controlador definido como

    @FXML
    private Label label ;
    

    el campo de label se inicializa con la instancia de Label creada por FXMLLoader .

  4. Los controladores de eventos se registran con cualquier elemento en el archivo onXXX="#..." con las onXXX="#..." definidas. Estos controladores de eventos invocan el método especificado en la clase de controlador. En este ejemplo, dado que el Button tiene onAction="#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.

  5. 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étodo initialize() puede tomar ningún parámetro o puede tomar una URL y un ResourceBundle . En este último caso, estos parámetros se rellenarán con la URL representa la ubicación del archivo FXML y cualquier conjunto de ResourceBundle en el FXMLLoader través de loader.setResources(...) . Cualquiera de estos puede ser null 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:

JavaFX 8

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

JavaFX 8

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


Modified text is an extract of the original Stack Overflow Documentation
Licenciado bajo CC BY-SA 3.0
No afiliado a Stack Overflow