Imprimir

Edición de datos en la misma ventana

Hasta ahora se han presentado los datos contenidos en una tabla de la base de datos como una lista, sin posibilidad de modificar su contenido. A continuación se va a mostrar cómo se podrían editar al menos algunos de los datos, mostrando en la misma ventana varios campos de texto editables con el contenido del registro seleccionado en la lista. Con el fin de mostrar este proceso de manera sencilla, sólo se va a mostrar la manera de editar datos de tipo String.

Diseño de la sección de edición en la ventana

Abre en Scene Builder el archivo FXML del diseño de la ventana que se ha creado hasta el momento siguiendo los pasos anteriores de este tutorial.

El diseño de la ventana podría hacerse creando bajo la lista de personas una zona donde se puedan editar algunos de los datos. El diseño final podría quedar como el de la siguiente imagen, donde vamos a posibilitar la edición del nombre y los apellidos de la persona seleccionada en la lista.

Captura de pantalla de 2017 04 18 12 36 16 2e971

Así que, en primer lugar, se ha creado un contenedor de tipo VBox (vertical) para almacenar en la primera posición la lista de personas, y debajo se diseñará el espacio para editar los datos usando un contenedor GridPane (rejilla). En la siguiente imagen puedes ver en la parte izquierda la estructura de los elementos, y en la parte derecha se han destacado en colores cada contenedor. A continuación podrás ver cómo ajustar los diferentes tamaños de los elementos.

Captura de pantalla de 2017 04 18 12 57 10 ca5b7

Para que los elementos contenidos dentro del VBox queden separados entre sí, en la sección Layout del panel derecho, indica un valor en su propiedad Spacing (recuerda tener seleccionado previamente el elemento VBox), y para que haya un margen de separación con el borde de la ventana espefica el valor del margen en la propiedad Padding.

Captura de pantalla de 2017 04 18 13 18 59 203c3

Si necesitas cambiar el número de filas o columnas del GridPane, debes seleccionarlo en primer lugar el GridPane, a continuación seleccionar alguna fila o columna y abre el menú contextual sobre el número de la fila o columna. Ahí podrás seleccionar si quieres añadir filas o columnas, y si se quiere antes (Row above o Column before) o después de la seleccionada (Row below o Column alfter). Observa que también puedes eliminar (Delete) o mover (Move) la fila o columna seleccionada.

Captura de pantalla de 2017 04 18 13 01 41 fc1d4

Puedes establecer el tamaño de cada columna arrastrando con el ratón el separador de columnas.

Captura de pantalla de 2017 04 18 13 12 39 b7e3c

Para separar entre sí los elementos contenidos dentro del GridPane, debes indicar el valor deseado en las propiedades Hgap y Vgap que se encuentran en el panel Layout de la derecha.

Captura de pantalla de 2017 04 18 13 22 34 657d1

Como has podido ver en imágenes anteriores, los elementos que se han introducido en el GridPane son de tipo Label, TextField y Button. Arrastra los elementos necesarios al GridPane para que la ventana ofrezca la posibilidad de editar los datos que desees, por ejemplo, el Nombre y Apellidos como en este ejemplo. En la propiedad Text de los Label y del Button puedes indicar el texto que deseas que muestren.

Para que desde el código Java de la clase controladora se pueda acceder a la información que se introduzca en los TextField, se les debe indicar a cada uno de ellos un identificador en la propiedad fx:id de la sección Code de la parte derecha.

Captura de pantalla de 2017 04 18 13 29 51 baaf1

Así mismo, para poder asignarle una acción al botón Guardar desde el código Java, selecciona dicho botón, y en la propiedad onAction de la sección Code, escribe el nombre que desees para el método que se deberá ejecutar cuando el usuario accione dicho botón.

Captura de pantalla de 2017 04 18 13 38 24 cd636 

Una vez indicador dichos identificadores, recuerda guardar los cambios y usar la opción Make Controller sobre el archivo FXML (en Netbeans) para que se añadan dichos identificadores a la clase controladora.

@FXML
private TextField textFieldNombre;
@FXML
private TextField textFieldApellidos;

Además se habrá creado automáticamente la declaración del método que se ejecutará cuando se acción el botón Guardar, con el nombre que hubieras indicando antes. Dentro de este método habrá que indicar posteriormente el código Java correspondiente a las acciones a realizar cuando se pulse el botón, es decir, guardar los datos que haya introcido el usuario en los TextField anteriores.

@FXML
private void onActionButtonGuardar(ActionEvent event) {
}

Código para mostrar los datos del registro seleccionado

Cuando el usuario seleccione una persona de la lista, se deberán mostrar en los TextField de la parte inferior los datos correspondientes a dicha persona. En este caso, se mostrarán únicamente su nombre y apellidos. Si el usuario modifica alguno de esos valores, y usa el botón Guardar, los nuevos valores deberán actualizarse en la lista y en la base de datos, de manera que si se cierra la aplicación y se vuelve a abrir, los nuevos valores permanecerán.

Para poder realizar todas estas acciones, conviene guardar en una variable el objeto correspondiente a la persona que se haya seleccionado en la lista. Ya que ese objeto deberá utilizarse en varios métodos del código, se declarará como una propiedad de la clase. Ten en cuenta que todas estas operaciones se deben realizar en la clase controladora asociada a esta ventana.

private Persona personaSeleccionada;

En el método initialize se va a añadir el código necesario para que en esa variable se almacene el objeto que se seleccione en el TableView. Este código se debe ejecutar cada vez que se cambie la selección en la lista, por lo que se añade usando un método addListener (añadir un oyente) que ofrecen los elementos que seleccionan en un TableView. Los Listener se ejecutan cuando se produce un determinado evento, en este caso cuando se cambie la selección en el TableView. Por ello, aunque el método initialize se ejecutará sólo cuando se crea el TableView, el código que se va a indicar a continuación, se ejecutará cada vez que se cambia la selección en el TableView. El parámetro newValue que aparece en el código contiene el nuevo elemento (objeto Persona en este caso) que haya el usuario, por lo que es ese valor el que se almacenará en la propiedad personaSeleccionada.

tableViewContactos.getSelectionModel().selectedItemProperty().addListener(
        (observable, oldValue, newValue) -> {
            personaSeleccionada = newValue;
        });

Además de guardar el objeto seleccionado, cuando el usuario seleccione un nuevo registro en el TableView, queremos que se muestren en los TextField los valores del nombre y apellidos de la persona seleccionada. Para ello, debes usar el método setText de la clase TextField que permite cambiar el texto mostrado en ellos. En la clase entidad Persona, que se había generado automáticamente, se han creado los métodos get necesarios para obtener el valor que contenga cualquiera de sus propiedades. Por tanto, usa los métodos getNombre() y getApellidos() que retornarán los valores de las propiedades relacionadas. Esos valores obtenidos son los que se pasan como texto a los TextField. Así que finalmente el código anterior quedará como:

tableViewContactos.getSelectionModel().selectedItemProperty().addListener(
        (observable, oldValue, newValue) -> {
            personaSeleccionada = newValue;
            if (personaSeleccionada != null) {
                textFieldNombre.setText(personaSeleccionada.getNombre());
                textFieldApellidos.setText(personaSeleccionada.getApellidos());
            } else {
                textFieldNombre.setText("");
                textFieldApellidos.setText("");
            }
        });

Observa que en el código anterior se ha añadido un control por si no hay ninguna persona seleccionada (null) en el TableView, en cuyo caso no se mostrará ningún valor en los TextField

Código para guardar los cambios introducidos por el usuario

Al botón Guardar que se ha introducido en el diseño de la ventana se le ha asociado el método onActionButtonGuardar para que se ejecute su contenido cuando el usuario lo accione. Por tanto, en el código de ese método se debe indicar que se obtengan los valores que el usuario ha introducido para el nombre y apellidos. A continuación que esos valores se almacenen en las propiedades nombre y apellidos del objeto Persona que está seleccionado, y finalmente que dicho objeto Persona con sus nuevos valores se almacene en la base de datos.

En primer lugar se debe comprobar que haya algún registro seleccionado en el TableView, ya que si por algún motivo el usuario no haya podido seleccionar ninguno, no debe realizar ninguna acción este botón. Eso se puede detectar comprobando si en la variable personaSeleccionada se encuentra almacenada algún objeto (no sea null).

if (personaSeleccionada != null) {

Para actualizar los valores de las propiedades nombre y apellidos del objeto persona, asignándole los valores que haya en la ventana, se usarán los métodos set correspondientes, indicando como parámetros los valores recogidos desde los TextField:

personaSeleccionada.setNombre(textFieldNombre.getText());
personaSeleccionada.setApellidos(textFieldApellidos.getText());

Ya se explicó en otra parte de este tutorial la manera de actualizar un objeto que ya se encontraba en la base de datos. En este caso se desea actualizar el objeto personaSeleccionada:

entityManager.getTransaction().begin();
entityManager.merge(personaSeleccionada);
entityManager.getTransaction().commit();

Por último, se indicará que se actualicen en el TableView los nuevos valores del objeto. En primer lugar se obtiene el número de la fila seleccionada en el TableView y luego se vuelve a asignar el mismo objeto a esa fila, de manera que se mostrarán los nuevos valores que contenga el objeto.

int numFilaSeleccionada = tableViewContactos.getSelectionModel().getSelectedIndex();
tableViewContactos.getItems().set(numFilaSeleccionada, personaSeleccionada);

Si no se indicara nada más, el foco de la ventana permanecería en el botón Guardar, que es el último elemento que se ha usado. Es conveniente que el foco vuelva al TableView para que el usuario pueda seguir moviéndose por su contenido usando el teclado, si lo desea. Para ello se indica:

TablePosition pos = new TablePosition(tableViewContactos, numFilaSeleccionada, null);
tableViewContactos.getFocusModel().focus(pos);
tableViewContactos.requestFocus();

Código completo de la clase controladora

import es.javiergarciaescobedo.agendacontactos.entities.Persona;
import java.net.URL;
import java.util.List;
import java.util.ResourceBundle;
import javafx.beans.property.SimpleStringProperty;
import javafx.collections.FXCollections;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TablePosition;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;
import javafx.scene.control.cell.PropertyValueFactory;
import javax.persistence.EntityManager;
import javax.persistence.Query;

public class ContactosViewController implements Initializable {

    private EntityManager entityManager;
    private Persona personaSeleccionada;
    @FXML
    private TableView<Persona> tableViewContactos;
    @FXML
    private TableColumn<Persona, String> columnNombre;
    @FXML
    private TableColumn<Persona, String> columnApellidos;
    @FXML
    private TableColumn<Persona, String> columnEmail;
    @FXML
    private TableColumn<Persona, String> columnProvincia;
    @FXML
    private TextField textFieldNombre;
    @FXML
    private TextField textFieldApellidos;

    @Override
    public void initialize(URL url, ResourceBundle rb) {
        columnNombre.setCellValueFactory(new PropertyValueFactory<>("nombre"));
        columnApellidos.setCellValueFactory(new PropertyValueFactory<>("apellidos"));
        columnEmail.setCellValueFactory(new PropertyValueFactory<>("email"));
        columnProvincia.setCellValueFactory(
                cellData -> {
                    SimpleStringProperty property = new SimpleStringProperty();
                    if (cellData.getValue().getProvincia() != null) {
                        property.setValue(cellData.getValue().getProvincia().getNombre());
                    }
                    return property;
                });
        tableViewContactos.getSelectionModel().selectedItemProperty().addListener(
                (observable, oldValue, newValue) -> {
                    personaSeleccionada = newValue;
                    if (personaSeleccionada != null) {
                        textFieldNombre.setText(personaSeleccionada.getNombre());
                        textFieldApellidos.setText(personaSeleccionada.getApellidos());
                    } else {
                        textFieldNombre.setText("");
                        textFieldApellidos.setText("");
                    }
                });
    }

    public void setEntityManager(EntityManager entityManager) {
        this.entityManager = entityManager;
    }

    public void cargarTodasPersonas() {
        Query queryPersonaFindAll = entityManager.createNamedQuery("Persona.findAll");
        List<Persona> listPersona = queryPersonaFindAll.getResultList();
        tableViewContactos.setItems(FXCollections.observableArrayList(listPersona));
    }

    @FXML
    private void onActionButtonGuardar(ActionEvent event) {
        if (personaSeleccionada != null) {
            personaSeleccionada.setNombre(textFieldNombre.getText());
            personaSeleccionada.setApellidos(textFieldApellidos.getText());
            entityManager.getTransaction().begin();
            entityManager.merge(personaSeleccionada);
            entityManager.getTransaction().commit();

            int numFilaSeleccionada = tableViewContactos.getSelectionModel().getSelectedIndex();
            tableViewContactos.getItems().set(numFilaSeleccionada, personaSeleccionada);
            TablePosition pos = new TablePosition(tableViewContactos, numFilaSeleccionada, null);
            tableViewContactos.getFocusModel().focus(pos);
            tableViewContactos.requestFocus();
        }
    }

}