JavaFX: 3 Formas de Pasar Información entre Escenas
En el siguiente artículo se explicarán tres diferentes formas en las que se puede pasar información de una escena a otra en JavaFX sin necesidad de guardarla en archivos o en bases de datos.
Requisitos
- Conocimientos en Java hasta su versión 8
- Conocimientos básicos en JavaFX
- Conocimientos básicos en Git
- Conocimientos básicos en Maven (Opcional)
Para empezar el tutorial, en tu IDE favorito (en mi caso utilizaré IntelliJ) crea un nuevo proyecto JavaFX preferiblemente utilizando algún arquetipo Maven. Esto último no es particularmente importante, los conceptos que veremos se pueden aplicar de cualquier forma.
Independientemente del arquetipo que elijas, tu código boilerplate (el código que viene por defecto) será parecido a esto:
Con una estructura de carpetas parecida a ésta (los contenidos pueden variar en función del arquetipo elegido) :
├───src
│ └───main
│ ├───java
│ │ └───org
│ │ └───mediumtutorials
│ └───resources
│ ├───fxml
│ ├───images
│ └───styles
Puedes ignorar (y eliminar, si deseas) las clases controladores o archivos que hayan sido creados al momento de la inicialización del proyecto, pues empezaremos desde cero. Después de haber hecho eso, inicializa un repositorio vacío en tu proyecto.
Antes de empezar con la explicación de las diferentes formas en las que podemos compartir la información, vamos a pensar en qué casos podríamos necesitarlo. Imagina por un momento que debes construir una aplicación que le pida cierta información a un usuario, lo básico, podríamos entonces crear una clase que encapsule esta información, tal que así:
Ahora seguramente tu puedes crear formularios con diseños extravagantes, pero para este ejemplo vamos a mantenerlo simple. Crea un nuevo archivo Home.fxml
y guardalo en la carpeta resources/fxml
, seguido de eso, crea la clase HomeController.java
(como recomendación, mueve la clase a un paquete dedicado a los controladores). A continuación en el archivo FXML pega el siguiente código:
En tu archivo MainApp.java
reemplaza el código que tengas ahí por el siguiente:
Si ejecutas tu aplicación ahora, deberías ver la siguiente ventana con nuestro formulario aparecer:
Excelente. Ahora vamos a crear una escena en la cual mostraremos la información que enviaremos desde la ventana principal y, de nuevo, será algo simple. Similar a lo que hicimos anteriormente, crea un nuevo archivo Destino.fxml
con su controlador asociado DestinoController.java
. En el archivo FXML, pega el siguiente código:
Eso renderizará una ventana con tres etiquetas simples, una para cada campo del formulario y un botón que nos será de utilidad más adelante.
Habrás notado que los campos en la ventana principal venían con un id
fijo, esto con el fin de recolectar los datos y guardarlos en una instancia de la clase Usuario, para ello crea una nueva función en la clase HomeController.java
llamada enviarDatos(Event)
en donde guardes la información del formulario, tal que así:
Ahora tenemos todo lo que necesitamos para empezar a explorar los distintos métodos con los cuales podemos enviar información a nuestra ventana destino. Como paso final, guarda todos los cambios que hayas hecho en tu repositorio, pues este será nuestro punto de partida para cada una de las tres formas.
Método 1: UserData
En JavaFX todas las clases que hereden de la clase Node, poseen los métodos setUserData(Object)
y getUserData()
que, en palabras de la documentación oficial, permiten guardar cualquier objeto, efectivamente anclandolo al nodo, para poder recuperarlo en un momento posterior. Utilizaremos este método para guardar la instancia de la clase usuario. Como el stage es el mismo para ambas ventanas, es un excelente candidato para guardar la información ahí.
Comencemos creando una nueva rama en el repositorio de nuestro proyecto con el siguiente comando:
git checkout -b metodo_uno
A continuación, referencia los elementos de tu vista en tu archivo DestinoController.java
y crea una función llamada recuperarDatos
que esté anclada al botón que diseñamos anteriormente, ahí como podrás adivinar, vamos a recuperar la instancia de la clase Usuario a partir del nodo raíz, para eso sigue estos pasos:
- Recupera la instancia del stage a través de la instancia del AnchorPane
- Utiliza el método
getUserData()
y guarda el contenido en una instancia de la clase Usuario - Llena los labels con la información correspondiente
Tu clase debería verse así:
De vuelta en la clase HomeController.java
en la función enviarDatos
, agrega el siguiente código:
Mira con atención la línea número 6 del código de arriba, ahí estamos pasando el usuario a la instancia del stage.
Ejecuta tu aplicación con datos de prueba y verás que podrás recuperarlos en la ventana destino, justo como esperábamos.
Método 2: Creando manualmente el controlador
El método setUserData
es útil cuando queremos pasar una única instancia de la clase, pero no se recomienda si queremos pasar una cantidad mayor de datos, en su lugar podemos optar por crear las instancias de las clases que necesitamos directamente en el controlador.
Para empezar vuelve nuevamente a la rama master con el comando:
git switch master
Y crea una nueva rama con el comando:
git checkout -b metodo_dos
En la clase DestinoController.java
crea un atributo privado de tipo Usuario con su respectivo método setter, a continuación y similar a como se hizo anteriormente, crea una función recuperarDatos
que esté anclado al botón que diseñamos, la lógica es la misma que en el método anterior, con la diferencia de que llenaremos los labels directamente con esta instancia, tal que así:
Para que este método funcione, debemos crear manualmente el controlador y pasarle la instancia de usuario en el momento que creamos la vista, es decir, debemos sobreescribir el comportamiento por defecto de JavaFX. Para ello empieza por editar tu archivo destino.fxml
y en la etiqueta raíz, el AnchorPane
elimina la siguiente propiedad:
fx:controller="controllers.DestinoController"
A continuación, en la clase HomeController.java
, en el método enviarDatos
sigue los siguientes pasos:
- Crea una nueva instancia de
FXMLLoader
a partir del constructor de la clase, pasándole la ruta del archivo FXML - Crea una nueva instancia de la clase
DestinoController.java
y pasale la instancia de la clase usuario - A la instancia de
FXMLLoader
pasale el objeto controlador utilizando el métodosetController
- Crea el objeto
root
utilizando el métodoload
en la instancia del objetoFXMLLoader
El resto de la lógica es igual al método anterior. El código completo de la función sería:
Ejecuta tu aplicación una vez más con datos de prueba y envíalos, verás que los datos se muestran tal cual en la otra vista, como es de esperarse.
Método 3: Clases Singleton
En los métodos anteriores nos hemos apoyado en los controladores para pasar información, pero si realmente queremos imitar el comportamiento que tendríamos con, por ejemplo, una base de datos, tenemos que poner esa lógica en una clase aparte. Ahí es dónde este método tiene lugar, la idea es crear una clase que contenga todos los atributos que sean comunes entre las vistas involucradas, ya que ambos controladores acceden a los mismos atributos, se necesita que compartan la misma instancia, para ello podemos utilizar el patrón de diseño Singleton.
Para empezar vuelve nuevamente a la rama master con:
git switch master
Y crea una nueva rama con el comando:
git checkout -b metodo_tres
Crea una nueva clase UsuarioHolder en el paquete que prefieras. Una vez lo hayas hecho crea un atributo público y estático de tipo Usuario. Como paso final, implementa los pasos del método Singleton. Hay muchas formas de implementar esto último, una de ellas siendo esta:
A continuación en la clase HomeController.java
en el método enviarDatos
sigue los siguientes pasos:
- Obtén la instancia de la clase UsuarioHolder
- Pásale el objeto usuario a través del método
setter
El resto de la lógica es igual al método uno. La función completa queda entonces como:
Luego de que tengas eso listo, crea el método recuperarDatos
en la clase DestinoController.java
que estará anclado al botón que diseñamos, en ella recupera el usuario a partir de la instancia compartida de UsuarioHolder y llena los labels con la información respectiva, tal que así:
Ejecuta nuevamente tu aplicación con datos de prueba y verás que, efectivamente, los datos se han pasado de la ventana principal a la ventana destino. Este método es particularmente útil para cuando la aplicación tiene un apartado de configuraciones, de esa forma puedes acceder fácilmente a las preferencias del usuario desde cualquier parte del programa a través de la instancia compartida de la clase.
Con esto hemos llegado al final de este tutorial, si tuviste inconvenientes siguiendo los pasos o te gustaria ver el proyecto completo, puedes visitar el repositorio en GitHub:
También te invito a experimentar tus propias soluciones realizando un fork al proyecto o si tienes alguna sugerencia puedes dejarla en los comentarios o abrir un issue. Muchas gracias por leer hasta aquí y espero verte en una próxima ocasión 👋.
Si te gustó el tutorial y quieres apoyarme para seguir creando más contenido así puedes hacerme una donación aquí.