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.
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:
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
:
También es posible referencias métodos de la clase contenedora:
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 tipoT
el tipo de la clase actual. En el ejemplo, se obtieneClass<HelloApplication>
ya que la clase donde se está ejecutando el código esHelloApplication
- Obtener la url como un objeto
URL
a través del métodogetResource(String res)
del objeto de tipoClass<HelloApplicationClass>
. - Dicha url se pasa como parámetro del constructor de la clase
FXMLLoader
. - Con el método
load()
de la claseFXMLLoader
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: