Skip to content

7 FXML

Introducción

Hasta ahora hemos creado aplicaciones simples en JavaFX donde todo el código está en un archivo. Son pequeños ejemplos, pero cuando las aplicaciones empiezan a crecer es útil tener el código estructurado en diferentes archivos para que sea más fácil de entender y manejar.

Dado que JavaFX fue diseñado con el patrón MVC en mente, es muy fácil crear uno aplicación que sigue este patrón (Modelo Vista Controlador), asegurando que el código proporciona la interfaz (FXML) está separada del código que manipula los datos de la aplicación. El controlador será un intermediario entre la interfaz y los datos, es decir, es la clase encargada de gestionar los eventos que suceden en la aplicación.

En JavaFX, la estructura jerárquica de los componentes de la interfaz se puede definir utilizando el lenguaje XML. El formato XML específico de JavaFX se llama FXML.

En el archivo FXML de una aplicación JavaFX, todos los componentes gráficos y como sus propiedades y estará vinculado a una clase Controlador, que será responsable de Manejar la lógica de control del programa.

El formato FXML es un formato basado en XML para realizar diseños de interfaces gráficas de forma similar a como se crean las interfaces gráficas en HTML. Además, FXML permite separar el diseño de la interfaz del resto del código, lo que es una gran ventaja a la hora de programar, ya que sigue el patrón de diseño Modelo-Vista-Controlador.

FXML no tiene un esquema, pero tiene una estructura predefinida. La forma de construir el FXML depende de la API de los objetos construidos, por lo que se puede consultar la documentación en dicha API para entender que elementos y atributos se pueden añadir. En general, la mayoría de las clases JavaFX pueden ser usadas como elementos y la mayoría de sus propiedades pueden ser usadas como atributos (incluyendo las heredadas).

Veamos la siguiente vista, haciendo uso de programación pura y haciendo uso de XML.

Ejemplo FXML

Figura 14 - Ejemplo FXML

BorderPane border = new BorderPane();
Label topPaneText = new Label("Page title");
Label centerPaneText = new Label("Some data here");

border.setTop(topPaneText);
border.setCenter(centerPaneText);
<BorderPane>
    <top>
        <Label text="Page title" />
    </top>
    <center>
        <Label text="Some data here" />
    </center>
</BorderPane>

Objectos FXML

Para crear objetos en FXML debe de usarse el elemento FXML apropiado. El nombre del elemento FXML usado correspondiente al nombre de la clase Java. Hay que tener en cuenta que para poder usar el elemento, primero hay que importarlo al igual que en el código Java. Para importar un paquete es necesario usar la siguiente sintaxis <?import package?> siendo package el nombre del paquete a importar. Veamos un ejemplo, donde se use VBox y Label:

<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.control.Label?>

<VBox spacing="20">
    <children>
        <Label text="Line 1"/>
        <Label text="Line 2"/>
    </children>
</VBox>

Las propiedades se pueden modificar por código o por atributos XML. En el ejemplo anterior se añade espacio haciendo uso del atributo spacing = 20, pero también se puede usar en código setSpacing(20). La etiqueta children representa al método getChildren() de la clase VBox. Los elementos añadidos en su interior serán convertidos en componentes JavaFX y añadidos a la colección de descendientes del objeto VBox obtenidos en el getChildren(). Solo se pueden establecer los atributos de aquellos campos de los elementos que tienen getters y setters, de lo contrario será omitido.

En JavaFX es posible añadir asignar ids a los elementos. Estos ids pueden ser usados para referenciar elementos en otra parte del fichero FXML, desde CSS, o desde la clase controladora.

Para asignar los ids hay que utilizar el atributo id del espacio de nombre de FXML:

<VBox xmlns:fx="http://javafx.com/fmxl/1">
    <Label fx:id="label1" text="Line 1" />
</VBox>

FXML tiene un espacio de nombre que puede establecer en el elemento raíz. Este espacio de nombre es necesario para algunos atributos como el atributo id.

Cuando se desea manipular un campo del elemento a través de los métodos estáticos, en FXML, también se usa de forma estática. Por ejemplo, la clase GridPane tiene métodos estáticos para posicionar sus hijos en la cuadricula, public static void setColumnIndex(Node node, int index) y public static void setRowIndex(Node node, int index), al igual que sus getters public static int getColumnIndex(Node node) y public static int getRowIndex(Node node), en el FXML su uso sería GridPane.rowIndex="0". Veamos un ejemplo:

<GridPane>
    <children>
        <Label text="Username:" GridPane.columnIndex="0" GridPane.rowIndex="0"/>
        <!-- elementos adicionales -->
    </children>
</GridPane>

Algunas propiedades de los elementos son más complejas (como objetos, en lugar de primitivos) y se escriben como etiquetas hijas de las etiquetas que representa el elemento, en lugar de atributos. Para que una etiqueta sea representada como una etiqueta hija del elemento, es necesario que en la clase que representa el elemento este la anotación DefaultProperty(property). Por ejemplo, en la clase VBox o GridPane debe haber una anotación como DefaultProperty("children"). Si una propiedad no es indicada dentro de dicha anotación debe ser usada como atributo.

Clase contenedora

Los documentos FXML tienen asociada una clase contenedora. Una clase contenedora permite acceder a los componentes declarados en un fichero FXML y hacer que el objeto controlador actúe de intermediario.

Para establecer un controlador en un fichero FXML, se debe especificar el fichero con el atributo fx:controller="package" en la raíz del elemento. Cuando se carga el fichero FXML crea una instancia de la clase controladora. Para que esto sea funcional, el constructor de la clase contenedora no debe recibir ningún parámetro:

<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.control.Button?>

<VBox xmlns:fx="http://javafx.com/fxml" fx:controller="com.example.javafx.MyFxmlController">
    <Button text="Click me!" />
</VBox>

Se puede asociar cada elemento del fichero FXML con los campos de clase contenedora. Para que ello sea posible, hay que añadir el atributo fx:id al elemento FXML con el mismo nombre que el campo de la clase controladora. Además, en la clase controladora, se debe añadir la anotación @FXML:

public class MyFxmlController{

    @FXML
    public Label label1;
}

También es posible referencias métodos de la clase contenedora:

public class MyFxmlController{

    // ...

    @FXML
    public void buttonClicked(Event e){
        System.out.println("Button Clicked")
    }
}
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.control.Button?>

<VBox xmlns:fx="http://javafx.com/fxml" fx:controller="com.example.javafx.MyFxmlController">
    <Button text="Click me!" onAction="#buttonClicked"/>
</VBox>

En este caso, sería similar a hacer:

public class HelloApplication extends Application {

    public void buttonClicked(Event e){
        System.out.println("Button Clicked")
    }

    @Override
    public void start(Stage stage){
        VBox vbox = new VBox();

        Button button = new Button("Click me!");
        button.setOnAction(this::buttonClicked);

        vbox.getChildren().addAll(button);
    }
}

Si es necesario añadir algo en la interfaz, antes que está sea cargada, se puede añadir el método initialize() al controlador con las instrucciones a ejecutar cada vez que se encargue la interfaz gráfica durante la ejecución de la aplicación.

SceneBuilder

La aplicación SceneBuilder permite diseñar de forma visual la interfaz gráfica de una aplicación JavaFX. Para usar Scene Builder hay que descargarlo desde su web

SceneBuilder permite diseñar de forma visual las interfaces, almacenando la configuración de la misma en ficheros con extensión fxml.

Cargar FXML

Para poder obtener el componente de un fichero FXML es necesario FXMLLoader. Veamos un ejemplo:

Class<HelloApplication> helloApplicationClass = HelloApplication.class;
URL url = helloApplicationClass.getResource("hello-view.xml");
FXMLLoader fxmlLoader = new FXMLLoader(url);
Parent component = fxmlLoader.load();


Scene scene = new Scene(component, 320, 240);

stage.setTitle("Hello!");
stage.setScene(scene);
stage.show();

Como se puede observar en el ejemplo anterior, para poder cargar el componente, hay que seguir varios pasos:

  • Obtener el objeto de tipo Class<T> de la clase actual, siendo el tipo T el tipo de la clase actual. En el ejemplo, se obtiene Class<HelloApplication> ya que la clase donde se está ejecutando el código es HelloApplication
  • Obtener la url como un objeto URL a través del método getResource(String res) del objeto de tipo Class<HelloApplicationClass>.
  • Dicha url se pasa como parámetro del constructor de la clase FXMLLoader.
  • Con el método load() de la clase FXMLLoader se obtiene el componente padre de nuestra aplicación.
  • Con dicho componente se crea la escena y el stage.

Todo esto se puede simplificar mucho más:

FXMLLoader fxmlLoader = new FXMLLoader(HelloApplication.class.getResource("hello-view.fxml"));
Scene scene = new Scene(fxmlLoader.load(), 320, 240);
stage.setTitle("Hello!");
stage.setScene(scene);
stage.show();