Contenidos en Parte 2
- Creación de una clase para el modelo
- Uso del modelo en una ObservableList
- Visualización del modelo mediante TableView y Controladores
Crea la clase para el Modelo
Neceistamos un modelo para contener la información sobre los contactos de nuestra agenda. Añade una nueva clase al paquete encargado de contener los modelos (ch.makery.address.model
) denominada Person
. La clase Person
tendrá atributos (instancias de clase) para el nombre, la dirección y la fecha de nacimiento. Añade el código siguiente a la clase. Explicaré detalles de JavaFX después del código.
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 { private final StringProperty firstName; private final StringProperty lastName; private final StringProperty street; private final IntegerProperty postalCode; private final StringProperty city; private final ObjectProperty<LocalDate> birthday; /** * 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); // Some initial dummy data, just for convenient testing. this.street = new SimpleStringProperty("some street"); this.postalCode = new SimpleIntegerProperty(1234); this.city = new SimpleStringProperty("some city"); this.birthday = new SimpleObjectProperty<LocalDate>(LocalDate.of(1999, 2, 21)); } public String getFirstName() { return firstName.get(); } public void setFirstName(String firstName) { this.firstName.set(firstName); } public StringProperty firstNameProperty() { return firstName; } public String getLastName() { return lastName.get(); } public void setLastName(String lastName) { this.lastName.set(lastName); } public StringProperty lastNameProperty() { return lastName; } public String getStreet() { return street.get(); } public void setStreet(String street) { this.street.set(street); } public StringProperty streetProperty() { return street; } public int getPostalCode() { return postalCode.get(); } public void setPostalCode(int postalCode) { this.postalCode.set(postalCode); } public IntegerProperty postalCodeProperty() { return postalCode; } public String getCity() { return city.get(); } public void setCity(String city) { this.city.set(city); } public StringProperty cityProperty() { return city; } public LocalDate getBirthday() { return birthday.get(); } public void setBirthday(LocalDate birthday) { this.birthday.set(birthday); } public ObjectProperty<LocalDate> birthdayProperty() { return birthday; } }
Explicación del código
- Con JavaFX es habitual usar
Propiedades
para todos los atributos de un clase usada como modelo. UnaPropiedad
permite, entre otras cosas, recibir notificaciones automáticamente cuando el valor de una variable cambia (por ejemplo si cambialastName
. Esto ayuda a mantener sincronizados la vista y los datos. Para aprender más sobrePropiedades
lee Using JavaFX Properties and Binding. LocalDate
, el tipo que usamos para especificar la fecha de nacimiento (birthday
) es parte de la nueva API de JDK 8 para la fecha y la hora.
Una lista de personas
Los principales datos que maneja nuestra aplicación son una colección de personas. Vamos a crear una lista de objetos de tipo Person
dentro de la clase principal MainApp
. El resto de controladores obtendrá luego acceso a esa lista central dentro de MainApp
.
Lista observable (ObservableList)
Estamos clases gráficas de JavaFX que necesitan ser informadas sobre los cambios en la lista de personas. Esto es importante, pues de otro modo la vista no estará sincronizada con los datos. Para estos fines, JavaFX ha introducido nuevas clases de colección.
De esas colecciones, necesitamos la denominada ObservableList
. Para crear una nueva ObservableList
, añade el código siguiente al principio de la clase MainApp
. También añadiremos un constructor para crear datos de ejemplo y un método de consulta (get) público:
MainApp.java
// ... AFTER THE OTHER VARIABLES ... /** * The data as an observable list of Persons. */ private ObservableList<Person> personData = FXCollections.observableArrayList(); /** * 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")); personData.add(new Person("Stefan", "Meier")); personData.add(new Person("Martin", "Mueller")); } /** * Returns the data as an observable list of Persons. * @return */ public ObservableList<Person> getPersonData() { return personData; } // ... THE REST OF THE CLASS ...
PersonOverviewController
Finalmente vamos a añadir datos a nuestra table. Para ello necesitaremos un controlador específico para la vista PersonOverview.fxml
.
- Crea una clase normal dentro del paquete view denominado
PersonOverviewController.java
. (Debemos ponerlo en el mismo paquete quePersonOverview.fxml
o el Scene Builder no lo encontrará, al menos no en la versión actual). - Añadiremos algunos atributos para acceder a la tabla y las etiquetas de la vista. Estos atributos irán precedidos por la anotación
@FXML
. Esto es necesario para que la vista tenga acceso a los atributos y métodos del controlador, incluso aunque sean privados. Una vez definida la vista en fxml, la aplicación se encargará de rellenar automáticamente estos atributos al cargar el fxml. Así pues, añade el código siguiente:
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; public class PersonOverviewController { @FXML private TableView<Person> personTable; @FXML private TableColumn<Person, String> firstNameColumn; @FXML private TableColumn<Person, String> lastNameColumn; @FXML private Label firstNameLabel; @FXML private Label lastNameLabel; @FXML private Label streetLabel; @FXML private Label postalCodeLabel; @FXML private Label cityLabel; @FXML private Label birthdayLabel; // Reference to the main application. private MainApp mainApp; /** * 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().lastNameProperty()); } /** * Is called by the main application to give a reference back to itself. * * @param mainApp */ public void setMainApp(MainApp mainApp) { this.mainApp = mainApp; // Add observable list data to the table 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 clasePerson
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).
Conexión de MainApp con PersonOverviewController
El método setMainApp(...)
debe ser invocado desde la clase MainApp
. Esto nos da la oportunidad de acceder al objeto MainApp
para obtener la lista de Person
y otras cosas. Sustituye el método showPersonOverview()
con el código siguiente, el cual contiene dos líneas adicionales:
MainApp.java - nuevo método showPersonOverview()s
/** * 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/PersonOverview.fxml")); AnchorPane personOverview = (AnchorPane) loader.load(); // Set person overview into the center of root layout. rootLayout.setCenter(personOverview); // Give the controller access to the main app. PersonOverviewController controller = loader.getController(); controller.setMainApp(this); } catch (IOException e) { e.printStackTrace(); } }
Vincular la vista al controlador
¡Ya casi lo tenemos! Pero falta un detalle: no le hemos indicado a la vista declarada en PersonOverview.fxml
cuál es su controlador y que elemento hacer corresponder to cada uno de los atributos en el controlador.
-
Abre
PersonOverview.fxml
en SceneBuilder. -
Abre la sección Controller en el lado izquierdo y selecciona
PersonOverviewController
como controlador.
-
Selecciona
TableView
en la sección Hierarchy y en el apartado Code escribepersonTable
en la propiedad fx:id.
-
Haz lo mismo para las columnas, poniendo
firstNameColumn
ylastNameColumn
como sus fx:id respectivamente. -
Para cada etiqueta en la segunda columna, introduce el fx:id que corresponda.
-
Importante: En Eclipse refresca el projecto AddressApp (tecla F5). Esto es necesario porque a veces Eclipse no se da cuenta de los cambios realizados desde el Scene Builder.
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.