4-Tutorial JavaFX Aplicación Con Tablas URL
4-Tutorial JavaFX Aplicación Con Tablas URL
Lo que aprenderás
Creación de un nuevo projecto JavaFX
Uso de Scene Builder para diseñar la interfaz de usuario
Estructuración de una aplicación según el patrón MVC (Modelo, Vista, Controlador)
Uso de ObservableList para la actualización automática de la interfaz de usuario
Uso de TableView y respuesta a cambios de selección en la tabla
Creación de un diálogo personalizado para editar personas
Validación de la entrada del usuario
Aplicación de estilos usando CSS
Persistencia de datos mediante XML
Guardado del último archivo abierto en las preferencias de usuario
Creación de un gráfico JavaFX para mostrar estadísticas
Despliegue de una aplicación JavaFX nativa
Después de completar esta serie de tutoriales deberías estar preparado para desarrollar aplicaciones
sofisticadas con JavaFX.
Prerequisitos
Última versión de Java JDK 8 (includes JavaFX 8).
Eclipse 4.3 o superior con el plugin e(fx)clipse. La forma más sencilla de obtenerlo es descargarse la
distribución preconfigurada desde e(fx)clipse website. Como alternativa puedes usar un sitio de
actualización para tu instalación de Eclipse.
Scene Builder 2.0 o superior
Configuración de Eclipse
Hay que indicarle a Eclipse que use JDK 8 y también dónde se encuentra el ejecutable del Scene Builder:
1. Abre las Preferencias de Eclipse (menú Window | Preferences y navega hasta Java | Installed
JREs.
2. Si no lo tienes el jre1.8 en la lista de JREs, entonces pulsa Add..., selecciona Standard VM y elige
el Directorio de instalación (JRE Home directory) de tu JDK 8.
3. Elimina otros JREs o JDKs de tal manera que JDK 8 se convierta en la opción por defecto.
4. Navega a Java | Compiler. Establece el nivel de cumplimiento del compilador en
1.8 (Compiler compliance level).
Enlaces útiles
Te podría interesar mantener los siguientes enlaces:
Java 8 API - Documentación (JavaDoc) de las clases estándar de Java
JavaFX 8 API - Documentación de las clases JavaFX
ControlsFX API - Documentación para el proyecto ControlsFX, el cual ofrece controles JavaFX adicionales
Oracle's JavaFX Tutorials - Tutoriales oficiales de Oracle sobre JavaFX
Nota: Nuestro paquete dedicado a las vistas contendrá también algunos controladores dedicados
exclusivamente a una vista. Les llamaremos controladores-vista.
Haz clic derecho sobre PersonOverview.fxml y elige Open with Scene Builder. Ahora deberías ver el
Scene Builder con un AnchorPane (visible en la jerarquía de componentes (herramienta Hierarchy)
situada a la izquierda).
1. Selecciona el AnchorPane en tu jerarquía y ajusta el tamaño en el apartado Layout (a la derecha):
2. Añade un SplitPane (Horizontal Flow) arrastrándolo desde la librería (Library) al área principal
de edición. Haz clic derecho sobre el SplitPane en la jerarquía y elige Fit to Parent.
7. Añade una Label al lado derecho del SplitPane con el texto "Person Details" (truco: usa la
búsqueda en la librería para encontrar el componente Label). Ajusta su apariencia usando
anclajes.
8. Añade un GridPane* al lado derecho, selecciónalo y ajusta su apariencia usando anclajes
(superior, derecho e izquierdo).
10. Añade 3 botones a la parte inferior. Truco: Selecciónalos todos, haz click derecho e invoca Wrap
In | HBox. Esto los pondrá a los 3 juntos en un HBox. Podrías necesitar establecer un
espaciado spacingdentro del HBox. Después, establece también anclajes (derecho e inferior) para
que se mantengan en el lugar correcto.
11. Ahora deberías ver algo parecido a lo siguiente. Usa el menú Preview para comprobar su
comportamiento al cambiar el tamaño de la ventana.
Fuente de la imagen: http://www.oracle.com
Es como una obra de teatro: El Stage (escenario) es el contenedor principal, normalmente una
ventana con borde y los típicos botones para maximizar, minimizar o cerrar la ventana. Dentro
del Stage se puede añadir una Scene (escena), la cual puede cambiarse dinámicamente por otra Scene.
Dentro de un Scene se añaden los nodos JavaFX, tales como AnchorPane, TextBox, etc.
Para tener más información puedes consultar Working with the JavaFX Scene Graph.
package ch.makery.address;
import java.io.IOException;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
@Override
this.primaryStage = primaryStage;
this.primaryStage.setTitle("AddressApp");
initRootLayout();
showPersonOverview();
/**
*/
try {
loader.setLocation(MainApp.class.getResource("view/RootLayout.fxml"));
primaryStage.setScene(scene);
primaryStage.show();
} catch (IOException e) {
e.printStackTrace();
/**
*/
try {
loader.setLocation(MainApp.class.getResource("view/PersonOverview.fxml"));
rootLayout.setCenter(personOverview);
} catch (IOException e) {
e.printStackTrace();
/**
* @return
*/
return primaryStage;
}
public static void main(String[] args) {
launch(args);
Los diferentes comentarios deben darte pistas sobre lo que hace cada parte del código.
Si ejecutas la aplicación ahjora, verás ago parecido a la captura de pantalla incluida al principio de este
artículo.
Problemas frecuentes
Si JavaFX no encuentra un archivo fxml puedes obtener el siguiente mensaje de error
java.lang.IllegalStateException: Location is not set.
Para resolverlo comprueba otra vez que no hayas escrito mal el nombre de tus archivos fxml.
Si todavía no te funciona, descárgate el código de esta parte del tutorial y pruébalo con el fxml proporcionado.
¿Qué es lo siguiente?
En Tutorial Parte 2 añadiremos algunos datos y funcionalidad a nuestra aplicación de contactos.
Person.java
package ch.makery.address.model;
import java.time.LocalDate;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
/**
*/
/**
* Default constructor.
*/
public Person() {
this(null, null);
/**
*
* @param firstName
* @param lastName
*/
return firstName.get();
this.firstName.set(firstName);
return firstName;
return lastName.get();
return lastName;
return street.get();
this.street.set(street);
return street;
return postalCode.get();
this.postalCode.set(postalCode);
return postalCode;
}
public String getCity() {
return city.get();
this.city.set(city);
return city;
return birthday.get();
this.birthday.set(birthday);
return birthday;
/**
*/
/**
* Constructor
*/
public MainApp() {
/**
* @return
*/
return personData;
PersonOverviewController
Finalmente vamos a añadir datos a nuestra table. Para ello necesitaremos un controlador específico para
la vista PersonOverview.fxml.
PersonOverviewController.java
package ch.makery.address.view;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import ch.makery.address.MainApp;
import ch.makery.address.model.Person;
@FXML
@FXML
@FXML
@FXML
@FXML
@FXML
@FXML
@FXML
@FXML
* The constructor.
*/
public PersonOverviewController() {
/**
*/
@FXML
firstNameColumn.setCellValueFactory(cellData ->
cellData.getValue().firstNameProperty());
/**
* @param mainApp
*/
this.mainApp = mainApp;
personTable.setItems(mainApp.getPersonData());
}
Este código necesita cierta explicación:
Los campos y métodos donde el archivo fxml necesita acceso deben ser anotados con @FXML. En realidad,
sólo si son privados, pero es mejor tenerlos privados y marcarlos con la anotación.
El método initialize() es invocado automáticamente tras cargar el fxml. En ese momento, todos los
atributos FXML deberían ya haber sido inicializados..
El método setCellValueFactory(...) que aplicamos sobre las columnas de la tabla se usa para
determinar qué atributos de la clase Person deben ser usados para cada columna particular. La flecha -
> indica que estamos usando una característica de Java 8 denominada Lambdas. Otra opción sería utilizar
un PropertyValueFactory, pero entonces no ofrecería seguridad de tipo (type-safe).
/**
*/
try {
loader.setLocation(MainApp.class.getResource("view/PersonOverview.fxml"));
rootLayout.setCenter(personOverview);
controller.setMainApp(this);
} catch (IOException e) {
e.printStackTrace();
}
Inicia la aplicación
Ahora, cuando ejecutes la aplicación, deberías obtener algo parecido a la captura de pantalla incluida al
principio de este artículo.
Enhorabuena!
¿Qué es lo siguiente?
En Tutorial Parte 3 añadiremos la funcionalidad requerida para añadir, borrar y editar contactos.
/**
*/
if (person != null) {
firstNameLabel.setText(person.getFirstName());
lastNameLabel.setText(person.getLastName());
streetLabel.setText(person.getStreet());
postalCodeLabel.setText(Integer.toString(person.getPostalCode()));
cityLabel.setText(person.getCity());
// birthdayLabel.setText(...);
} else {
firstNameLabel.setText("");
lastNameLabel.setText("");
streetLabel.setText("");
postalCodeLabel.setText("");
cityLabel.setText("");
birthdayLabel.setText("");
}
Convierte la fecha de nacimiento en una cadena
Te darás cuenta de que no podemos usar el atributo birthday directamente para establecer el valor de
una Label porque se requiere un String, y birthday es de tipo LocalDate. Así pues necesitamos
convertir birthday de LocalDate a String.
En la práctica vamos a necesitar convertir entre LocalDate y String en varios sitios y en ambos sentidos.
Una buena práctica es crear una clase auxiliar con métodos estáticos ( static) para esta finalidad.
Llamaremos a esta clase DateUtil y la ubicaremos una paquete separado
denominado ch.makery.address.util:
DateUtil.java
package ch.makery.address.util;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
/**
*/
/** The date pattern that is used for conversion. Change as you wish. */
DateTimeFormatter.ofPattern(DATE_PATTERN);
/**
* Returns the given date as a well formatted String. The above defined
*/
if (date == null) {
return null;
return DATE_FORMATTER.format(date);
/**
*/
try {
} catch (DateTimeParseException e) {
return null;
/**
*
* @param dateString
*/
Truco: Puedes cambiar el formato de la fecha cambiando el patrón DATE_PATTERN. Para conocer los diferentes
tipos de formato consulta DateTimeFormatter.
birthdayLabel.setText(DateUtil.format(person.getBirthday()));
@FXML
firstNameColumn.setCellValueFactory(
lastNameColumn.setCellValueFactory(
cellData -> cellData.getValue().lastNameProperty());
showPersonDetails(null);
// Listen for selection changes and show the person details when changed.
personTable.getSelectionModel().selectedItemProperty().addListener(
/**
*/
@FXML
personTable.getItems().remove(selectedIndex);
Gestión de errores
Si ejecutas tu aplicación en este punto deberías ser capaz de borrar personas de la tabla. Pero, ¿qué
ocurre si pulsas el botón de borrar sin seleccionar a nadie en la tabla.
Se produce un error de tipo ArrayIndexOutOfBoundsException porque no puede borrar una persona en
el índice -1, que es el valor devuelto por el método getSelectedIndex() - cuando no hay ningún
elemento seleccionado.
Ignorar semejante error no es nada recomendable. Deberíamos hacerle saber al usuario que tiene que
seleccionar una persona previamente para poderla borrar (incluso mejor sería deshabilitar el botón para
que el usuario ni siquiera tenga la oportunidad de realizar una acción incorrecta).
Vamos a añadir un diálogo emergente para informar al usuario. Desafortunadamente no hay
componentes para diálogos incluidos en JavaFX 8. Para evitar tener que crearlos manualmente
podemos añadir una librería que ya los incluya (Dialogs):
/**
*/
@FXML
if (selectedIndex >= 0) {
personTable.getItems().remove(selectedIndex);
} else {
// Nothing selected.
Dialogs.create()
.title("No Selection")
.showWarning();
2. Usa un panel de rejilla (GridPane), etiquetas (Label), campos de texto (TextField) y botones
(Button) para crear una ventana de diálogo como la siguiente:
package ch.makery.address.view;
import javafx.fxml.FXML;
import javafx.scene.control.TextField;
import javafx.stage.Stage;
import org.controlsfx.dialog.Dialogs;
import ch.makery.address.model.Person;
import ch.makery.address.util.DateUtil;
/**
*/
@FXML
@FXML
@FXML
@FXML
@FXML
/**
*/
@FXML
/**
* @param dialogStage
*/
this.dialogStage = dialogStage;
/**
* @param person
*/
this.person = person;
firstNameField.setText(person.getFirstName());
lastNameField.setText(person.getLastName());
streetField.setText(person.getStreet());
postalCodeField.setText(Integer.toString(person.getPostalCode()));
cityField.setText(person.getCity());
birthdayField.setText(DateUtil.format(person.getBirthday()));
birthdayField.setPromptText("dd.mm.yyyy");
/**
* @return
*/
return okClicked;
/**
*/
@FXML
if (isInputValid()) {
person.setFirstName(firstNameField.getText());
person.setLastName(lastNameField.getText());
person.setStreet(streetField.getText());
person.setPostalCode(Integer.parseInt(postalCodeField.getText()));
person.setCity(cityField.getText());
person.setBirthday(DateUtil.parse(birthdayField.getText()));
okClicked = true;
dialogStage.close();
/**
*/
@FXML
dialogStage.close();
/**
*/
}
if (streetField.getText() == null || streetField.getText().length() == 0) {
} else {
try {
Integer.parseInt(postalCodeField.getText());
} catch (NumberFormatException e) {
} else {
if (!DateUtil.validDate(birthdayField.getText())) {
if (errorMessage.length() == 0) {
return true;
} else {
.title("Invalid Fields")
.message(errorMessage)
.showError();
return false;
El método setPerson(...) puede ser invocado desde otra clase para establecer la persona que será
editada.
Cuando el usuario pula el botón OK, el método handleOk() es invocado. Primero se valida la entrada del
usuario mediante la ejecución del método isInputValid(). Sólo si la validación tiene éxito el objeto
persona es modificado con los datos introducidos por el usuario. Esos cambios son aplicados directamente
sobre el objeto pasado como argumento del método setPerson(...)!
El método boolean okClicked se utiliza para determinar si el usuario ha pulsado el botón OK o el botón
Cancel.
1. Abre el archivo PersonEditDialog.fxml.
2. En la sección Controller a la izquierda selecciona PersonEditDialogController como clase de control.
3. Establece el campo fx:id de todas los TextField con los identificadores de los atributos del controlador
correspondientes.
4. Especifica el campo onAction de los dos botones con los métodos del controlador correspondientes a
cada acción.
/**
* Opens a dialog to edit details for the specified person. If the user
* clicks OK, the changes are saved into the provided person object and true
* is returned.
*/
try {
// Load the fxml file and create a new stage for the popup dialog.
loader.setLocation(MainApp.class.getResource("view/PersonEditDialog.fxml"));
dialogStage.setTitle("Edit Person");
dialogStage.initModality(Modality.WINDOW_MODAL);
dialogStage.initOwner(primaryStage);
dialogStage.setScene(scene);
controller.setDialogStage(dialogStage);
controller.setPerson(person);
dialogStage.showAndWait();
return controller.isOkClicked();
} catch (IOException e) {
e.printStackTrace();
return false;
/**
* Called when the user clicks the new button. Opens a dialog to edit
*/
@FXML
if (okClicked) {
mainApp.getPersonData().add(tempPerson);
/**
* Called when the user clicks the edit button. Opens a dialog to edit
*/
@FXML
if (selectedPerson != null) {
if (okClicked) {
showPersonDetails(selectedPerson);
}
} else {
// Nothing selected.
Dialogs.create()
.title("No Selection")
.showWarning();
¡Ya está!
Llegados a este punto deberías tener una aplicación de libreta de contactos en funcionamiento. Esta
aplicación es capaz de añadir, editar y borrar personas. Tiene incluso algunas capacidades de validación
para evitar que el usuario introduzca información incorrecta.
Espero que los conceptos y estructura de esta aplicación te permitan empezar tu propia aplicación
JavaFX. ¡ Disfruta !
Qué es lo siguiente?
En Tutorial Parte 4 introduciremos algo de diseño mediante hojas de estilo CSS.
.background {
-fx-background-color: #1d1d1d;
.label {
-fx-font-size: 11pt;
-fx-text-fill: white;
-fx-opacity: 0.6;
.label-bright {
-fx-font-size: 11pt;
-fx-text-fill: white;
-fx-opacity: 1;
.label-header {
-fx-font-size: 32pt;
-fx-text-fill: white;
-fx-opacity: 1;
.table-view {
-fx-base: #1d1d1d;
-fx-control-inner-background: #1d1d1d;
-fx-background-color: #1d1d1d;
-fx-table-cell-border-color: transparent;
-fx-table-header-border-color: transparent;
-fx-padding: 5;
.table-view .column-header-background {
-fx-background-color: transparent;
-fx-size: 35;
-fx-border-width: 0 0 1 0;
-fx-background-color: transparent;
-fx-border-color:
transparent
transparent
derive(-fx-base, 80%)
transparent;
-fx-border-insets: 0 10 1 0;
-fx-font-size: 20pt;
-fx-text-fill: white;
-fx-alignment: center-left;
-fx-opacity: 1;
.table-view:focused .table-row-cell:filled:focused:selected {
-fx-background-color: -fx-focus-color;
.split-pane {
-fx-padding: 1 0 0 0;
.menu-bar {
-fx-background-color: derive(#1d1d1d,20%);
.context-menu {
-fx-background-color: derive(#1d1d1d,50%);
}
.menu-bar .label {
-fx-font-size: 14pt;
-fx-text-fill: white;
-fx-opacity: 0.9;
.menu .left-container {
-fx-background-color: black;
.text-field {
-fx-font-size: 12pt;
/*
* http://pixelduke.wordpress.com/2012/10/23/jmetro-windows-8-controls-on-java/
*/
.button {
-fx-padding: 5 22 5 22;
-fx-border-color: #e2e2e2;
-fx-border-width: 2;
-fx-background-radius: 0;
-fx-background-color: #1d1d1d;
-fx-font-size: 11pt;
-fx-text-fill: #d8d8d8;
-fx-background-insets: 0 0 0 0, 0, 1, 2;
.button:hover {
-fx-background-color: #3a3a3a;
.button:pressed, .button:default:hover:pressed {
-fx-background-color: white;
-fx-text-fill: #1d1d1d;
.button:focused {
-fx-border-width: 1, 1;
-fx-border-radius: 0, 0;
-fx-border-insets: 1 1 1 1, 0;
.button:disabled, .button:default:disabled {
-fx-opacity: 0.4;
-fx-background-color: #1d1d1d;
-fx-text-fill: white;
.button:default {
-fx-background-color: -fx-focus-color;
-fx-text-fill: #ffffff;
}
.button:default:hover {
-fx-background-color: derive(-fx-focus-color,30%);
A continuación necesitamos vincular el CSS a nuestra escena. Podemos hacer esto programáticamente,
mediante código Java, pero en esta ocasión vamos a utilizar Scene Builder para añadirlo a nuestros
archivos FXML:
El archivo de icono
Un posible sitio para obtener iconos gratuitos es Icon Finder. Yo por ejemplo descargué este icono de
libreta de direcciones.
Crea una carpeta dentro de tu aplicación llamado resources y añádele una subcarpeta para almacenar
imágenes, llámala images. Pon el icono que hayas elegido dentro de la carpeta de imágenes. La
estructura de directorios de tu carpeta debe tener un aspecto similar a este:
Establece el icono de la escena principal
Para establecer el icono de nuestra escena debemos añadir la línea de código siguiente al
método start(...) dentro de MainApp.java
MainApp.java
this.primaryStage.getIcons().add(new Image("file:resources/images/address_book_32.png"));
this.primaryStage = primaryStage;
this.primaryStage.setTitle("AddressApp");
this.primaryStage.getIcons().add(new Image("file:resources/images/address_book_32.png"));
initRootLayout();
showPersonOverview();
También puedes añadir un icono a la escena que contiene la venta de edición de los detalles de una
persona.
¿Qué es lo siguiente?
En Tutorial Parte 5 añadiremos la capacidad de almacenar datos mediante XML.
Tutorial JavaFX 8 - Parte 5:
Persistencia de datos con XML
Sep 17, 2014
Contenidos en Parte 5
Persistencia de datos en XML
Utilización de FileChooser
Utilización de Menu
Guardando la ruta al último archivo abierto en las preferencias de usuario
Actualmente, los datos de nuestra aplicación de libreta de direcciones reside únicamente en memoria.
Cada vez que cerramos la aplicación los datos se pierden. Así pues, ha llegado la hora de pensar en como
guardar los datos de forma persistente.
Guardando preferencias del usuario
Java nos permite guardar cierta información mediante una clase llamada Preferences, pensada para
guardar las preferencias de usuario de una aplicación. Dependiendo del sistema operativo, estas
preferencias son guardadas en un sitio u otro (por ejemplo el registro de Windows).
No podemos usar un archivo de Preferences para guardar nuestra libreta de direcciones completa, pero
nos sirve para guardar información de estado muy simple. Un ejemplo del tipo de cosas que
podemos guardar en estas preferencias es la ruta al último archivo abierto. Con esta información
podemos recuperar el último estado de la aplicación cuando el usuario vuelva a ejecutar la aplicación.
Los siguientes dos métodos se encargan de guardar y recuperar las Preferences. Añádelos al final de la
clase MainApp:
MainApp.java
/**
* Returns the person file preference, i.e. the file that was last opened.
* @return
*/
if (filePath != null) {
} else {
return null;
/**
* Sets the file path of the currently loaded file. The path is persisted in
*
* @param file the file or null to remove the path
*/
if (file != null) {
prefs.put("filePath", file.getPath());
} else {
prefs.remove("filePath");
primaryStage.setTitle("AddressApp");
<persons>
<person>
<birthday>1999-02-21</birthday>
<city>some city</city>
<firstName>Hans</firstName>
<lastName>Muster</lastName>
<postalCode>1234</postalCode>
<street>some street</street>
</person>
<person>
<birthday>1999-02-21</birthday>
<city>some city</city>
<firstName>Anna</firstName>
<lastName>Best</lastName>
<postalCode>1234</postalCode>
<street>some street</street>
</person>
</persons>
Utilización de JAXB
JAXB viene incluido en el JDKm. Eso significa que no necesitamos añadir ninguna librería adicional.
JAXB proporciona dos funcionalidades principales: la capacidad de convertir objectos Java en XML
(marshalling), y a la inversa, la capacidad de convertir XML en objetos Java (unmarshalling).
Para que JAXB sea capaz de hacer la conversión, necesitamos preparar nuestro modelo.
package ch.makery.address.model;
import java.util.List;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
/**
* Helper class to wrap a list of persons. This is used for saving the
*/
@XmlRootElement(name = "persons")
@XmlElement(name = "person")
return persons;
this.persons = persons;
/**
* Loads person data from the specified file. The current person data will
* be replaced.
* @param file
*/
try {
.newInstance(PersonListWrapper.class);
Unmarshaller um = context.createUnmarshaller();
personData.clear();
personData.addAll(wrapper.getPersons());
setPersonFilePath(file);
Dialogs.create()
.title("Error")
.showException(e);
}
/**
* @param file
*/
try {
.newInstance(PersonListWrapper.class);
Marshaller m = context.createMarshaller();
m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
wrapper.setPersons(personData);
m.marshal(wrapper, file);
setPersonFilePath(file);
Dialogs.create().title("Error")
.showException(e);
}
Los métodos de escritura (marshalling) y lectura (unmarshalling) ya están listos. Ahora crearemos unas
opciones de menú para poder utilizar esos métodos.
package ch.makery.address.view;
import java.io.File;
import javafx.fxml.FXML;
import javafx.stage.FileChooser;
import org.controlsfx.dialog.Dialogs;
import ch.makery.address.MainApp;
/**
* The controller for the root layout. The root layout provides the basic
* application layout containing a menu bar and space where other JavaFX
*/
/**
* @param mainApp
*/
this.mainApp = mainApp;
/**
*/
@FXML
mainApp.getPersonData().clear();
mainApp.setPersonFilePath(null);
/**
*/
@FXML
fileChooser.getExtensionFilters().add(extFilter);
if (file != null) {
mainApp.loadPersonDataFromFile(file);
/**
* Saves the file to the person file that is currently open. If there is no
*/
@FXML
private void handleSave() {
if (personFile != null) {
mainApp.savePersonDataToFile(personFile);
} else {
handleSaveAs();
/**
*/
@FXML
fileChooser.getExtensionFilters().add(extFilter);
if (file != null) {
if (!file.getPath().endsWith(".xml")) {
mainApp.savePersonDataToFile(file);
}
}
/**
*/
@FXML
Dialogs.create()
.title("AddressApp")
.masthead("About")
.showInformation();
/**
*/
@FXML
System.exit(0);
FileChooser
Fíjate en los métodos que usan la clase FileChooser dentro de RootLayoutController. Primero, se crea
una nueva instancia de la clase FileChooser. A continuación, se le añade un filtro de extensión para que
sólo se muestren los archivos terminados en .xml. Finalmente, el objeto FileChooser se muestra justo
encima de la escena principal.
Si el usuario cierra la ventana del FileChoosersin escoger un archivo, se devuelve null. En otro caso, se
obtiene el archivo seleccionado, y se lo podemos pasar al método loadPersonDataFromFile(...) o al
método savePersonDataToFile(...) de la clase MainApp.
Conectando el FXML con el controlador
1. Abre RootLayout.fxml en Scene Builder. En la
sección Controller selecciona RootLayoutController como controlador.
2. Vuelve a la sección Hierarchy y elige un ítem del menú. En el campo On Action de la
sección Codedebes tener como opciones todos los métodos disponibles en la clase de control. Elije
el que corresponda a cada uno de los ítems del menú.
/**
* Initializes the root layout and tries to load the last opened
* person file.
*/
try {
.getResource("view/RootLayout.fxml"));
primaryStage.setScene(scene);
controller.setMainApp(this);
primaryStage.show();
} catch (IOException e) {
e.printStackTrace();
if (file != null) {
loadPersonDataFromFile(file);
Fíjate en los 2 cambios introducidos: Las líneas que dan acceso a MainApp" y las últimas tres líneas
para cargar el último archivo abierto*.
Pruebas
Si pruebas ahora tu aplicación deberías ser capaz de usar los menús para grabar los datos de los
contactos en un archivo XML.
Si abres el archivo XML resultante en un editor, notarás que la fecha de nacimiento no se guarda
correctamente, aparece una etiqueta <birthday/> vacía. La razón es que JAXB no sabe como
convertir LocalDate a XML. Debemos proporcionar un adaptador a medida para realizar esta
conversión.
Dentro de ch.makery.address.util crea una nueva clase denominada LocalDateAdapter con el
contenido siguiente:
LocalDateAdapter.java
package ch.makery.address.util;
import java.time.LocalDate;
import javax.xml.bind.annotation.adapters.XmlAdapter;
/**
* Adapter (for JAXB) to convert between the LocalDate and the ISO 8601
*/
@Override
return LocalDate.parse(v);
@Override
return v.toString();
return birthday.get();
Ahora prueba a guardar los datos de nuevo y abre el archivo XML otra vez. Debería abrir
automáticamente el último archivo abierto durante la ejecución previa.
Como funciona
Ahora veamos como funciona todo junto
¿Qué es lo siguiente?
En Tutorial Parte 6 añadiremos un gráfico con estadísticas de las fechas de nacimiento de la lista de
personas.
package ch.makery.address.view;
import java.text.DateFormatSymbols;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.scene.chart.BarChart;
import javafx.scene.chart.CategoryAxis;
import javafx.scene.chart.XYChart;
import ch.makery.address.model.Person;
/**
*/
@FXML
@FXML
/**
*/
@FXML
monthNames.addAll(Arrays.asList(months));
xAxis.setCategories(monthNames);
/**
* @param persons
*/
monthCounter[month]++;
barChart.getData().add(series);
}
}
/**
* Opens a dialog to show birthday statistics.
*/
try {
// Load the fxml file and create a new stage for the popup.
loader.setLocation(MainApp.class.getResource("view/BirthdayStatistics.fxml"));
dialogStage.setTitle("Birthday Statistics");
dialogStage.initModality(Modality.WINDOW_MODAL);
dialogStage.initOwner(primaryStage);
dialogStage.setScene(scene);
controller.setPersonData(personData);
dialogStage.show();
} catch (IOException e) {
e.printStackTrace();
Todo está dispuesto, pero todavía no tenemos nada para invocar al nuevo
método showBirthdayStatistics(). Afortunadamente ya tenemos un menú en en la
vista RootLayout.fxml que puede ser usado para estos fines.
*/
@FXML
mainApp.showBirthdayStatistics();
¿Qué es lo siguiente?
En la última parte del tutorial, Part 7, vamos por fin a desplegar nuestra aplicación (es decir, la
empaquetaremos y la haremos llegar a los usuarios).
Tutorial JavaFX 8 - Parte 7:
Despliegue
Sep 17, 2014
He pensado escribir una última parte de este tutorial para mostrar como desplegar (es decir empaquetar
y publicar) la aplicación de libreta de direcciones.
Contenidos en Parte 7
Desplegando nuestra aplicación JavaFX como un Paquete nativo con e(fx)clipse
Qué es el despliegue
El despliegue es el proceso de empaquetar y distribuir o hacer llegar una aplicación al usuario. Es una
fase crucial del desarrollo porque es el primer contacto del usuario final con nuestro software.
Java se anuncia con el slogan Escribe una vez, ejecuta donde sea para ilustar sobre los
beneficios multi-plataforma del lenguaje Java. Idealmente, esto significa que nuestra aplicación Java
puede ser ejecutada en cualquier dispositivo equipado con una Máquina Virtual Java (JVM).
En el pasado, la experiencia de usuario instalando una aplicación Java no ha sido siempre agradable. Si
el usuario no tenía en su sistema la versión requerida de Java Runtime (JRE), debía ser guíado para su
instalaci´no previa. Esto originaba ciertas dificultades, como problemas de privilegios (había que ser
administrador) o problemas de compatibilidad.
Afortunadamente, JavaFX ofrece una nueva opción para el despliegue de una aplicación
denominada Native Packaging (también llamada Self-Contained Application Package). Un paquete
nativo es un lote que contiene tanto la aplicación como la JRE específica requerida.
Orable en su documentación oficial sobre JavaFX ofrece una guía extensiva de todas las opciones de
despliegue en su JavaFX deployment options.
En esta parte del tutorial mostraré como crear un paquete nativo con Eclipse y el plugin e(fx)clipse.
3. Importante: El nombre de los iconos debe coincidir exactamente con el título de la aplicación tal y como
ha sido especificada en la propiedad Application title del archivo build.fxbuild:
o YourAppTitle.ico
o YourAppTitle-setup-icon.bmp
o YourAppTitle.icns
<path id="fxant">
<filelist>
<file name="${java.home}\..\lib\ant-javafx.jar"/>
<file name="${java.home}\lib\jfxrt.jar"/>
<file name="${basedir}"/>
</filelist>
</path>
<fx:resources id="appRes">
</fx:resources>
Por alguna razón el número de versión se añade a la aplicación (propiedad fx:application) lo que hace
que el instalador adopte siempre el valor por defecto 1.0 (tal y como han indicado varios comentarios).
Para corregir esto, hay que añadir el número de versión de forma manual (gracias a Marc por descubrir
como hacerlo):
build.xml - añade número de "version"
<fx:application id="fxApplication"
name="AddressApp"
mainClass="ch.makery.address.MainApp"
version="1.0"
/>
Ya podemos ejecutar build.xml con Ant. Esto generará un jar ejecutable del proyecto. Pero queremos ir
un paso más allá y crear un práctico instalador.
Paso 5 (WINDOWS) - Instalador de Windows (exe)
¿Qué es lo siguiente?
Espero que este tutorial te haya resultado de ayuda iniciarte en JavaFX y a partir de aquí seas capaz de
desarrollar tu propio proyecto.
Cualquier feedback es bienvenido. No dudes en comentar si tienes sugerencias que hacer o algo no te ha
quedado claro.