Tutorial JavaFX 8
Tutorial JavaFX 8
Lo que aprenders
Despus de completar esta serie de tutoriales deberas estar preparado para desarrollar
aplicaciones sofisticadas con JavaFX.
Contenidos en Parte 1
Prerequisitos
Configuracin de Eclipse
Hay que indicarle a Eclipse que use JDK 8 y tambin dnde 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 instalacin (JRE Home directory)
de tu JDK 8.
3. Elimina otros JREs o JDKs de tal manera que JDK 8 se convierta en la opcin por
defecto.
5. Navega hasta Java | JavaFX. Especifica la ruta al ejecutable del Scene Builder.
Enlaces tiles
Te podra interesar mantener los siguientes enlaces:
Nota: Nuestro paquete dedicado a las vistas contendr tambin algunos controladores
dedicados exclusivamente a una vista. Les llamaremos controladores-vista.
5. Cambia el texto de las columnas (bajo Properties) a "First Name" y "Last Name".
7. Aade una Label al lado derecho del SplitPane con el texto "Person Details" (truco:
usa la bsqueda en la librera para encontrar el componente Label). Ajusta su
Row".
10. Aade 3 botones a la parte inferior. Truco: Seleccinalos todos, haz click derecho e
invoca Wrap In | HBox. Esto los pondr a los 3 juntos en un HBox. Podras
necesitar establecer un espaciado spacing dentro del HBox. Despus, establece
tambin anclajes (derecho e inferior) para que se mantengan en el lugar correcto.
11. Ahora deberas ver algo parecido a lo siguiente. Usa el men Preview para
comprobar su comportamiento al cambiar el tamao de la ventana.
1. Crea otro archivo FXML dentro del paquete view llamado RootLayout.fxml. Esta
vez, elige BorderPane como elemento raz..
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
public void start(Stage primaryStage) {
this.primaryStage = primaryStage;
this.primaryStage.setTitle("AddressApp");
initRootLayout();
showPersonOverview();
}
/**
* Initializes the root layout.
*/
public void initRootLayout() {
try {
// Load root layout from fxml file.
FXMLLoader loader = new FXMLLoader();
loader.setLocation(MainApp.class.getResource("view/RootLayout.fx
ml"));
rootLayout = (BorderPane) loader.load();
/**
* Shows the person overview inside the root layout.
*/
public void showPersonOverview() {
try {
// Load person overview.
FXMLLoader loader = new FXMLLoader();
loader.setLocation(MainApp.class.getResource("view/PersonOvervie
w.fxml"));
AnchorPane personOverview = (AnchorPane) loader.load();
/**
* Returns the main stage.
* @return
*/
public Stage getPrimaryStage() {
return primaryStage;
}
Los diferentes comentarios deben darte pistas sobre lo que hace cada parte del cdigo.
Si ejecutas la aplicacin ahjora, vers ago parecido a la captura de pantalla incluida al
principio de este artculo.
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 todava no te funciona, descrgate el cdigo de esta parte del tutorial y prubalo
con el fxml proporcionado.
Contenidos en Parte 2
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;
/**
* Model class for a Person.
*
* @author Marco Jakob
*/
public class Person {
/**
* Default constructor.
*/
public Person() {
this(null, null);
}
/**
* Constructor with some initial data.
*
* @param firstName
* @param lastName
*/
public Person(String firstName, String lastName) {
this.firstName = new SimpleStringProperty(firstName);
this.lastName = new SimpleStringProperty(lastName);
this.street.set(street);
}
/**
* The data as an observable list of Persons.
*/
private ObservableList<Person> personData = FXCollections.observableArra
yList();
/**
* Constructor
*/
public MainApp() {
// Add some sample data
personData.add(new Person("Hans", "Muster"));
personData.add(new Person("Ruth", "Mueller"));
personData.add(new Person("Heinz", "Kurz"));
personData.add(new Person("Cornelia", "Meier"));
personData.add(new Person("Werner", "Meyer"));
personData.add(new Person("Lydia", "Kunz"));
personData.add(new Person("Anna", "Best"));
/**
* Returns the data as an observable list of Persons.
* @return
*/
public ObservableList<Person> getPersonData() {
return personData;
}
PersonOverviewController
Finalmente vamos a aadir datos a nuestra table. Para ello necesitaremos un controlador
especfico para la vista PersonOverview.fxml.
1. Crea una clase normal dentro del
paquete view denominado PersonOverviewController.java. (Debemos
ponerlo en el mismo paquete que PersonOverview.fxml o el Scene Builder
no lo encontrar, al menos no en la versin actual).
2. Aadiremos algunos atributos para acceder a la tabla y las etiquetas de la
vista. Estos atributos irn precedidos por la anotacin @FXML. Esto es
necesario para que la vista tenga acceso a los atributos y mtodos del
controlador, incluso aunque sean privados. Una vez definida la vista en
fxml, la aplicacin se encargar de rellenar automticamente estos atributos
al cargar el fxml. As pues, aade el cdigo siguiente:
Nota: acurdate siempre de importar javafx, NO AWT Swing!
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
private Label firstNameLabel;
@FXML
private Label lastNameLabel;
@FXML
private Label streetLabel;
@FXML
private Label postalCodeLabel;
@FXML
private Label cityLabel;
@FXML
/**
* The constructor.
* The constructor is called before the initialize() method.
*/
public PersonOverviewController() {
}
/**
* Initializes the controller class. This method is automatically called
* after the fxml file has been loaded.
*/
@FXML
private void initialize() {
// Initialize the person table with the two columns.
firstNameColumn.setCellValueFactory(cellData -> cellData.getValue().
firstNameProperty());
lastNameColumn.setCellValueFactory(cellData -> cellData.getValue().l
astNameProperty());
}
/**
* Is called by the main application to give a reference back to itself.
*
* @param mainApp
*/
Los campos y mtodos donde el archivo fxml necesita acceso deben ser
anotados con @FXML. En realidad, slo si son privados, pero es mejor
tenerlos privados y marcarlos con la anotacin.
El mtodo initialize() es invocado automticamente tras cargar el fxml.
En ese momento, todos los atributos FXML deberan ya haber sido
inicializados..
El mtodo 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 caracterstica de Java 8 denominada Lambdas. Otra opcin
sera utilizar un PropertyValueFactory, pero entonces no ofrecera
seguridad de tipo (type-safe).
} catch (IOException e) {
e.printStackTrace();
}
}
Inicia la aplicacin
Ahora, cuando ejecutes la aplicacin, deberas obtener algo parecido a la captura de
pantalla incluida al principio de este artculo.
Enhorabuena!
Contenidos en Parte 3
Respuesta a cambios en la
seleccin de la Tabla
Todava no hemos usado la parte derecha de la interfaz de nuestra aplicacin. La intencin
es usar esta parte para mostrar los detalles de la persona seleccionada por el usuario en la
tabla.
En primer lugar vamos a aadir un nuevo mtodo dentro
de PersonOverviewController que nos ayude a rellenar las etiquetas con los datos de una
sola persona.
Crea un mtodo llamado showPersonDetails(Person person). Este mtodo recorrer
todas las etiquetas y establecer el texto con detalles de la persona usando setText(...) .
Si en vez de una instancia de Person se pasa null entonces las etiquetas deben ser
borradas.
PersonOverviewController.java
/**
* Fills all text fields to show details about the person.
* If the specified person is null, all text fields are cleared.
*
* @param person the person or null
*/
private void showPersonDetails(Person person) {
if (person != null) {
// Fill the labels with info from the person object.
firstNameLabel.setText(person.getFirstName());
lastNameLabel.setText(person.getLastName());
streetLabel.setText(person.getStreet());
postalCodeLabel.setText(Integer.toString(person.getPostalCode()));
cityLabel.setText(person.getCity());
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
/**
* Helper functions for handling dates.
*
* @author Marco Jakob
*/
public class DateUtil {
/** The date pattern that is used for conversion. Change as you wish. */
private static final String DATE_PATTERN = "dd.MM.yyyy";
/**
* Returns the given date as a well formatted String. The above defined
* {@link DateUtil#DATE_PATTERN} is used.
*
* @param date the date to be returned as a string
* @return formatted string
*/
public static String format(LocalDate date) {
if (date == null) {
return null;
}
return DATE_FORMATTER.format(date);
}
/**
* Converts a String in the format of the defined {@link DateUtil#DATE_P
ATTERN}
* to a {@link LocalDate} object.
*
* Returns null if the String could not be converted.
*
* @param dateString the date as String
* @return the date object or null if it could not be converted
*/
public static LocalDate parse(String dateString) {
try {
return DATE_FORMATTER.parse(dateString, LocalDate::from);
} catch (DateTimeParseException e) {
return null;
}
}
/**
* Checks the String whether it is a valid date.
*
* @param dateString
* @return true if the String is a valid date
*/
public static boolean validDate(String dateString) {
// Listen for selection changes and show the person details when changed
.
personTable.getSelectionModel().selectedItemProperty().addListener(
(observable, oldValue, newValue) -> showPersonDetails(newValue))
;
}
Gestin de errores
Si ejecutas tu aplicacin en este punto deberas ser capaz de borrar personas de la tabla.
Pero, qu ocurre si pulsas el botn 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 mtodo getSelectedIndex() cuando no hay ningn elemento seleccionado.
Ignorar semejante error no es nada recomendable. Deberamos hacerle saber al usuario que
tiene que seleccionar una persona previamente para poderla borrar (incluso mejor sera
deshabilitar el botn para que el usuario ni siquiera tenga la oportunidad de realizar una
accin incorrecta).
Vamos a aadir un dilogo emergente para informar al usuario. Desafortunadamente no hay
componentes para dilogos incluidos en JavaFX 8. Para evitar tener que crearlos
manualmente podemos aadir una librera que ya los incluya (Dialogs):
1. Descarga este controlsfx-8.0.6_20.jar (tambin se puede obtener de
la pgina web de ControlsFX).
Importante: La versin de ControlsFX debe ser la 8.0.6_20 o superior
para que funcione con JDK 8u20 debido a un cambio crtico en esa
versin.
2. Crea una subcarpeta lib dentro del proyecto y coloca dentro del archivo jar.
siguiente:
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;
/**
* Dialog to edit details of a person.
*
* @author Marco Jakob
*/
public class PersonEditDialogController {
@FXML
private TextField firstNameField;
@FXML
private TextField lastNameField;
@FXML
private TextField streetField;
@FXML
private TextField postalCodeField;
@FXML
private TextField cityField;
@FXML
private TextField birthdayField;
/**
* Initializes the controller class. This method is automatically called
* after the fxml file has been loaded.
*/
@FXML
private void initialize() {
}
/**
* Sets the stage of this dialog.
*
* @param dialogStage
*/
public void setDialogStage(Stage dialogStage) {
this.dialogStage = dialogStage;
}
/**
* Sets the person to be edited in the dialog.
*
* @param person
*/
public void setPerson(Person 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");
}
/**
* Returns true if the user clicked OK, false otherwise.
*
* @return
*/
public boolean isOkClicked() {
return okClicked;
}
/**
* Called when the user clicks ok.
*/
@FXML
private void handleOk() {
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();
}
}
/**
* Called when the user clicks cancel.
*/
@FXML
private void handleCancel() {
dialogStage.close();
}
/**
* Validates the user input in the text fields.
*
* @return true if the input is valid
*/
private boolean isInputValid() {
String errorMessage = "";
if (errorMessage.length() == 0) {
return true;
} else {
// Show the error message.
Dialogs.create()
.title("Invalid Fields")
.masthead("Please correct invalid fields")
.message(errorMessage)
.showError();
return false;
}
}
}
MainApp.java
/**
* 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.
*
* @param person the person object to be edited
* @return true if the user clicked OK, false otherwise.
*/
public boolean showPersonEditDialog(Person person) {
try {
// Load the fxml file and create a new stage for the popup dialog.
FXMLLoader loader = new FXMLLoader();
loader.setLocation(MainApp.class.getResource("view/PersonEditDialog.
fxml"));
AnchorPane page = (AnchorPane) loader.load();
return controller.isOkClicked();
} catch (IOException e) {
e.printStackTrace();
return false;
}
}
/**
* Called when the user clicks the edit button. Opens a dialog to edit
} else {
// Nothing selected.
Dialogs.create()
.title("No Selection")
.masthead("No Person Selected")
.message("Please select a person in the table.")
.showWarning();
}
}
Ya est!
Llegados a este punto deberas tener una aplicacin de libreta de contactos en
funcionamiento. Esta aplicacin es capaz de aadir, editar y borrar personas. Tiene incluso
algunas capacidades de validacin para evitar que el usuario introduzca informacin
incorrecta.
Espero que los conceptos y estructura de esta aplicacin te permitan empezar tu propia
aplicacin JavaFX. Disfruta !
Contenidos en Parte 4
En JavaFX puedes dar estilo al interfaz de usuario utilizando hojas de estilo en cascada
(CSS). Esto es estupendo ! Nunca haba sido tan fcil personalizar la apariencia de una
aplicacin Java.
En este tutorial vamos a crear un tema oscuro (DarkTheme) inspirado en el diseo de
Windows 8 Metro. El cdigo CSS de los botones est basado en el artculo de blog
JMetro - Windows 8 Metro controls on Java by Pedro Duque Vieira.
.label {
-fx-font-size: 11pt;
-fx-font-family: "Segoe UI Semibold";
-fx-text-fill: white;
-fx-opacity: 0.6;
}
.label-bright {
-fx-font-size: 11pt;
-fx-font-family: "Segoe UI Semibold";
-fx-text-fill: white;
-fx-opacity: 1;
}
.label-header {
-fx-font-size: 32pt;
-fx-font-family: "Segoe UI Light";
-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;
}
.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-font-family: "Segoe UI Light";
-fx-text-fill: white;
-fx-opacity: 0.9;
}
.menu .left-container {
-fx-background-color: black;
}
.text-field {
-fx-font-size: 12pt;
-fx-font-family: "Segoe UI Semibold";
}
/*
* Metro style Push Button
* Author: Pedro Duque Vieira
* http://pixelduke.wordpress.com/2012/10/23/jmetro-windows-8-controls-on-ja
va/
*/
.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-family: "Segoe UI", Helvetica, Arial, sans-serif;
-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-color: white, white;
-fx-border-width: 1, 1;
-fx-border-style: solid, segments(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%);
}
5. Ves a la vista Properties y elige background como clase de estilo. El fondo debera
volverse negro.
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 aplicacin llamado resources y adele una subcarpeta para
almacenar imgenes, llmala images. Pon el icono que hayas elegido dentro de la carpeta
de imgenes. La estructura de directorios de tu carpeta debe tener un aspecto similar a este:
The whole start(...) method should look something like this now:
public void start(Stage primaryStage) {
this.primaryStage = primaryStage;
this.primaryStage.setTitle("AddressApp");
initRootLayout();
showPersonOverview();
}
Tambin puedes aadir un icono a la escena que contiene la venta de edicin de los detalles
de una persona.
Contenidos en Parte 5
/**
* Sets the file path of the currently loaded file. The path is persisted in
* the OS specific registry.
*
* @param file the file or null to remove the path
*/
public void setPersonFilePath(File file) {
Preferences prefs = Preferences.userNodeForPackage(MainApp.class);
if (file != null) {
prefs.put("filePath", file.getPath());
Por qu XML?
Una de las formas ms habituales de almacenar datos es mediante una de base de datos. Las
bases de datos tpicamente contienen algn tipo de datos relacionales (tablas relacionadas
mediante ndices), mientras que los datos que tenemos que guardar. A este problema se le
denomina desadaptacin de impedancias objeto-relacional (object-relational impedance
mismatch). Cuesta bastante trabajo adaptar objetos a tablas de una base de datos relacional.
Aunque existen algunas soluciones para ayudarnos a realizar esta adaptacin (ej. Hibernate,
la ms popular), todava cuesta bastante trabajo de configuracin.
Para nuestro sencillo modelo de datos es mucho ms fcil usar XML. Usaremos una librera
llamada JAXB (Java Architecture for XMLBinding). Con apenas unas pocas lneas de
cdigo JAXB nos permitir generar una salida en XML como esta:
Ejemplo de salid en XML
<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>
Utilizacin de JAXB
JAXB viene incluido en el JDKm. Eso significa que no necesitamos aadir ninguna librera
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 conversin, necesitamos preparar nuestro modelo.
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
* list of persons to XML.
*
* @author Marco Jakob
*/
@XmlRootElement(name = "persons")
@XmlElement(name = "person")
public List<Person> getPersons() {
return persons;
}
personData.clear();
personData.addAll(wrapper.getPersons());
/**
* Saves the current person data to the specified file.
*
* @param file
*/
public void savePersonDataToFile(File file) {
try {
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
* elements can be placed.
*
* @author Marco Jakob
*/
public class RootLayoutController {
/**
* Is called by the main application to give a reference back to itself.
*
* @param mainApp
*/
public void setMainApp(MainApp mainApp) {
this.mainApp = mainApp;
}
/**
* Creates an empty address book.
*/
@FXML
private void handleNew() {
mainApp.getPersonData().clear();
mainApp.setPersonFilePath(null);
/**
* Opens a FileChooser to let the user select an address book to load.
*/
@FXML
private void handleOpen() {
FileChooser fileChooser = new FileChooser();
if (file != null) {
mainApp.loadPersonDataFromFile(file);
}
}
/**
* Saves the file to the person file that is currently open. If there is
no
* open file, the "save as" dialog is shown.
*/
@FXML
private void handleSave() {
/**
* Opens a FileChooser to let the user select a file to save to.
*/
@FXML
private void handleSaveAs() {
FileChooser fileChooser = new FileChooser();
if (file != null) {
// Make sure it has the correct extension
if (!file.getPath().endsWith(".xml")) {
file = new File(file.getPath() + ".xml");
}
mainApp.savePersonDataToFile(file);
}
}
/**
* Opens an about dialog.
*/
@FXML
private void handleAbout() {
Dialogs.create()
.title("AddressApp")
.masthead("About")
.message("Author: Marco Jakob\nWebsite: http://code.makery.ch")
.showInformation();
}
/**
* Closes the application.
*/
@FXML
private void handleExit() {
System.exit(0);
}
}
FileChooser
Fjate en los mtodos que usan la clase FileChooser dentro de RootLayoutController.
Primero, se crea una nueva instancia de la claseFileChooser. A continuacin, se le aade
un filtro de extensin para que slo se muestren los archivos terminados en .xml.
Finalmente, el objeto FileChooser se muestra justo encima de la escena principal.
* Initializes the root layout and tries to load the last opened
* person file.
*/
public void initRootLayout() {
try {
// Load root layout from fxml file.
FXMLLoader loader = new FXMLLoader();
loader.setLocation(MainApp.class
.getResource("view/RootLayout.fxml"));
rootLayout = (BorderPane) loader.load();
primaryStage.show();
} catch (IOException e) {
e.printStackTrace();
}
Fjate en los 2 cambios introducidos: Las lneas que dan acceso a MainApp" y las ltimas
tres lneas para cargar el ltimo archivo abierto*.
Pruebas
Si pruebas ahora tu aplicacin deberas ser capaz de usar los mens para grabar los datos de
los contactos en un archivo XML.
Si abres el archivo XML resultante en un editor, notars que la fecha de nacimiento no se
guarda correctamente, aparece una etiqueta<birthday/> vaca. La razn es que JAXB no
sabe como convertir LocalDate a XML. Debemos proporcionar un adaptador a medida
para realizar esta conversin.
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
* String representation of the date such as '2012-12-03'.
*
* @author Marco Jakob
*/
public class LocalDateAdapter extends XmlAdapter<String, LocalDate> {
@Override
public LocalDate unmarshal(String v) throws Exception {
return LocalDate.parse(v);
}
@Override
public String marshal(LocalDate v) throws Exception {
return v.toString();
}
}
Ahora prueba a guardar los datos de nuevo y abre el archivo XML otra vez. Debera abrir
automticamente el ltimo archivo abierto durante la ejecucin previa.
Como funciona
Ahora veamos como funciona todo junto
1. La aplicacin se inicia con la ejecucin del mtodo main(...) de la
clase MainApp.
2. El constructor public MainApp() es invocado y aade algunos datos de
ejemplo.
3. El mtodo start(...) de la clase MainApp es invocado, el cual a su vez
invoca a initRootLayout() para inicializar la vista principal utilizando el
archivo RootLayout.fxml. El archivo FSML tiene informacin sobre qu
controlador utilizar y enlaza la vista con su
controlador RootLayoutController.
4. MainApp obtiene el controlador RootLayoutController del cargador FXML y
le pasa a ese controlador una referencia a s mismo. con esta referencia el
controlador podr despus acceder a los mtodos (pblicos) de MainApp.
Contenidos en Parte 6
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;
/**
* The controller for the birthday statistics view.
*
* @author Marco Jakob
*/
public class BirthdayStatisticsController {
@FXML
private BarChart<String, Integer> barChart;
@FXML
private CategoryAxis xAxis;
/**
* Initializes the controller class. This method is automatically called
* after the fxml file has been loaded.
*/
@FXML
private void initialize() {
// Get an array with the English month names.
String[] months = DateFormatSymbols.getInstance(Locale.ENGLISH).getM
onths();
// Convert it to a list and add it to our ObservableList of months.
monthNames.addAll(Arrays.asList(months));
/**
* Sets the persons to show the statistics for.
*
* @param persons
*/
public void setPersonData(List<Person> persons) {
// Count the number of people having their birthday in a specific mo
nth.
int[] monthCounter = new int[12];
for (Person p : persons) {
int month = p.getBirthday().getMonthValue() - 1;
monthCounter[month]++;
}
barChart.getData().add(series);
}
}
dialogStage.show();
} catch (IOException e) {
e.printStackTrace();
}
}
Todo est dispuesto, pero todava no tenemos nada para invocar al nuevo
mtodo showBirthdayStatistics(). Afortunadamente ya tenemos un men en en la
vista RootLayout.fxml que puede ser usado para estos fines.
Ahora, abre RootLayout.fxml en Scene Builder. Crea el men Statistics con un tem
denominado Show Statistics:
He pensado escribir una ltima parte de este tutorial para mostrar como desplegar (es decir
empaquetar y publicar) la aplicacin de libreta de direcciones.
Contenidos en Parte 7
Qu es el despliegue
La carpeta app contiene los datos de nuestar aplicacin y el runtime de Java especfico de
la plataforma.
Para hacrselo incluso ms fcil al usuario, vamos a proporcionar un instalador:
3. Como Packaging Format elige entre exe para Windows, dmg para MacOS,
o rpm para Linux.
YourAppTitle.ico
YourAppTitle-setup-icon.bmp
YourAppTitle.icns
Ya podemos ejecutar build.xml con Ant. Esto generar un jar ejecutable del proyecto.
Pero queremos ir un paso ms all y crear un prctico instalador.
Con el programa Inno Setup podemos crear un instalador Windows de nuestra aplicacin
como un nico archivo .exe. El archivo resultante realizar una instalacin a nivel de
usuario (no se requieren permisos de administrador). Adems se crear un enlace (men de
inicio o escritorio).
1. Descarga Inno Setup 5 o posterior. Instala Inno Setup en tu computadora. Nuestro
script Ant lo usar automticamente para generar el instalador.
2. Publica la ruta donde est instalado Inno Setup (e.g. C:\Program Files
(x86)\Inno Setup 5). Para hacerlo, aade esa ruta a la variablePath en tus
variables de entorno de Windows. Si no sabes donde encontrarlas, leeHow to set the
path and environment variables in Windows.
3. Reinicia Eclipse y contina con el Paso 5.
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.
Basic Usage
Using the DatePicker is straight forward:
// Create the DatePicker.
DatePicker datePicker = new DatePicker();
Using FXML
I usually prefer keeping as much of the view in fxml instead of instantiating the controls in
Java code as above. WithScene Builder 2.0 and above you can even drag-and-drop the
DatePicker into your fxml.
Options
Hide Week Numbers
We can set the showWeekNumbersProperty to true/false to show/hide a column showing
week numbers. Note: The default value depends on the country of the current locale.
datePicker.setShowWeekNumbers(false);
Date Converter
The date in the text field is automatically converted to the local format (in my case,
it's dd.MM.yyyy). By setting aStringConverter we can change this, for example, to yyyyMM-dd.
datePicker.setPromptText(pattern.toLowerCase());
datePicker.setConverter(new StringConverter<LocalDate>() {
DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern(pattern);
@Override
public String toString(LocalDate date) {
if (date != null) {
return dateFormatter.format(date);
} else {
return "";
}
}
@Override
public LocalDate fromString(String string) {
if (string != null && !string.isEmpty()) {
return LocalDate.parse(string, dateFormatter);
} else {
return null;
}
}
});
// Hijrah calendar.
datePicker.setChronology(HijrahChronology.INSTANCE);
// Minguo calendar.
datePicker.setChronology(MinguoChronology.INSTANCE);
// Buddhist calendar.
datePicker.setChronology(ThaiBuddhistChronology.INSTANCE);