Formulario de detalle en una nueva ventana

En el artículo anterior de este tutorial se había explicado la manera de editar datos del registro (Persona) seleccionado, situando los campos de edición en la misma ventana bajo la lista. En esta ocasión vas a poder conocer cómo ofrecer al usuario la posibilidad de editar la información en una nueva ventana, lo cual es ventajoso cuando se trata de un buen número de datos, como puede ser este caso.

Las aplicaciones de escritorio tradicionales normalmente han tendido a abrir este tipo de ventanas secundarias como una ventana emergente sutuada por encima de la anterior. Con la tendencia de la programación para dispositivos móviles, que no ofrecen la posicbilidad de abrir ventana emergentes de la misma manera, puede ser interesante programar esa ventana secundaria de manera que aparezca integrada en la misma ventana que la de la lista, como si fuera una capa que se sitúa sobre ella cubriéndola totalmente, y que al terminar la edición vuelva a aparecer la anterior vista de la lista.

Es decir algo similar a la siguiente animación:

FormularioEdicionMismaVentana 5a3d0

Diseño del formulario de detalle

Puedes empezar el desarrollo de esta funcionalidad diseñando el formulario del detalle de los datos de una determinada persona. Crea otro archivo FXML y realiza con Scene Builder un diseño similar al siguiente, donde se han añadido distintos tipos de controles según el tipo de información que permiten editar:

Screen Shot 2017 04 19 at 19.38.45 908fa

Igual que ya se ha hecho anteriormente, se debe asignar un ID a cada elemento con el que haya que interacturar desde el código Java, y actualizar (Make Controller) la clase controladora para que conozca esos identificadores. En este caso se han creado los siguientes:

@FXML
private TextField textFieldNombre;
@FXML
private TextField textFieldApellidos;
@FXML
private TextField textFieldTelefono;
@FXML
private TextField textFieldEMail;
@FXML
private TextField textFieldNumHijos;
@FXML
private TextField textFieldSalario;
@FXML
private ComboBox<?> comboBoxProvincia;
@FXML
private CheckBox checkBoxJubilado;
@FXML
private RadioButton radioButtonSoltero;
@FXML
private RadioButton radioButtonCasado;
@FXML
private RadioButton radioButtonViudo;
@FXML
private DatePicker datePickerFechaNacimiento;
@FXML
private ImageView imageViewFoto;

A los botones añadidos para Guardar y Cancelar se les has asignado también unos métodos que deberá ejecutarse cuando el usuario los acciones. Como es habitual debes indicarlo en la propiedad On Action, igual que en ocasiones anteriores. En la clase controladora ha quedado así:

@FXML
private void onActionButtonGuardar(ActionEvent event) {        
}

@FXML
private void onActionButtonCancelar(ActionEvent event) {
}

Por otro lado, en el diseño de la vista principal, la lista de personas, se han añadido también botones que permitirán realizar las acciones de Insertar, Editar y Suprimir registros de personas. Se ha realizado añadiendo un contenedor HBox en la parte superior como puedes ver:

Screen Shot 2017 04 19 at 20.05.30 b3ebb

Como es habitual ya, a cada botón se le habrá asignado el nombre de un método en la propiedad On Action, de manera que el código contenido en ese método será el que se ejecute al accionar el botón. Al actualizar la clase controladora de la ventana principal (la de la lista de personas) se deben haber generado las declaraciones de dichos métodos. En este ejemplo se les ha asignado los siguientes nombres: onActionButtonNuevoonActionButtonEditar y onActionButtonSuprimir.

@FXML
private void onActionButtonNuevo(ActionEvent event) {
}

@FXML
private void onActionButtonEditar(ActionEvent event) {
}

@FXML
private void onActionButtonSuprimir(ActionEvent event) {
}

Carga de vista de detalle sobre vista de la lista

Hasta ahora, la vista que se ha creado para la lista de personas (ContactosView.fxml) es que se cargada exclusivamente en la escena (Scene) de la aplicación. Pero ahora se desea que tanto esa vista como el formulario de detalle de carguen en la misma escena, apareciendo una u otra. Inicialmente deberá aparecer la vista de la lista, pero al insertar o editar un registro deseamos que aparezca sobre ella la vista de detalle, como se podía apreciar en la animación inicial de este artículo.

Por tanto, hay que cambiar el código para que se utilice un esquema como el siguiente, donde se muestra en la izquierda la situación hasta ahora, y en la derecha lo que deseamos hacer. Es decir, se va a crear un nuevo contenedor de tipo StackPane (que se almancerá en una variable llamada rootMain), que permite apilar paneles o vistas una sobre otra, y sobre él se colocarán la vista de la lista (ContactosView.fxml) y cuando se desee mostrar el detalle de un registro se colocará encima la vista de detalle (PersonaDetalleView.fxml).

esquema paneles mater detail 2 45a36

Por tanto, se va a sustituir el código de la clase principal de la aplicación en la parte que hasta ahora cargaba el contenido de la vista ContactosView.fxml y se asignaba a la escena:

FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("ContactosView.fxml"));
Parent root = fxmlLoader.load();
// Carga del EntityManager, etc ...
Scene scene = new Scene(root, 650, 400);

Cambiando dichas lineas por las siguientes, donde a la escena se le asigna como contenedor principal un nuevo StackPane (que llamaremos rootMain), al que se le añade la vista ContactowView.fxml (que llamaremos rootContactosView). Se usa la clase Pane (panel), ya que engloba a todas las clase de los contenedores AnchorPane, GridPane, HBox, VBox, StackPane, etc, y así se podría usar cualquiera de ellos como elemento principal de la vista (en el diseño de la vista se ha dejado AnchorPane como contenedor principal).

StackPane rootMain = new StackPane();

FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("ContactosView.fxml"));
Pane rootContactosView = fxmlLoader.load();
rootMain.getChildren().add(rootContactosView);
// Carga del EntityManager, etc ...
Scene scene = new Scene(rootMain, 650, 400);

Volviendo al código de la clase controladora de la vista de la lista, editaremos el código de los botones Nuevo y Editar para que cuando el usuario accione cualquiera de ellos, sobre la vista de la lista se cargue la vista de detalle, y se oculte la vista de la lista.

Para poder ocultar la vista de la lista, necesitamos tener una variable que haga referencia al contenedor principal de esta vista, es decir, al elemento raíz que se encuentre en su archivo FXML. Necesitaremos asignarle un identificador (en la propiedad fx:id) a ese elemento, que lo podemos hacer desde Scene Builder, y tras actualizar el controlador con Make Controller tendremos la variable que haga referencia a dicho elemento raíz. Posteriormente se utilizará esa variable para hacerle setVisible(false). En la siguiente imagen se ve seleccionado el contenedor principal (AnchorPane) y que se le asigna el identificador rootContactosView..

Captura de pantalla de 2017 04 21 13 20 44 36d8b

Hay que tener en cuenta que el método load() de FXMLLoader, que carga los archivos FXML obliga a que se controle la excepción IOException, por lo que todo el código que haga esta operación deberá encerrarse entre las sentencias try-catch que controlen dicha excepción. 

try {
    // Cargar la vista de detalle
    FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("PersonaDetalleView.fxml"));
    Parent rootDetalleView = fxmlLoader.load();     

    // Ocultar la vista de la lista
    rootContactosView.setVisible(false);
	
    // Añadir la vista de detalle al StackPane principal para que se muestre
    StackPane rootMain = (StackPane)rootContactosView.getScene().getRoot();
    rootMain.getChildren().add(rootDetalleView);
} catch (IOException ex) {
    Logger.getLogger(ContactosViewController.class.getName()).log(Level.SEVERE, null, ex);
}

Una vez que aparezca la vista de detalle, el usuario realizará las acciones que desee para finalmente utilizar el botón Guardar o el botón Cancelar que hemos puesto en el diseño. Entre otras cosas, ambos botones deberán eliminar la vista de detalle y volver a hacer visible la lista de contactos. Para ello, la clase controladora de la vista de detalle debe tener acceso al contenedor principal de la lista (que hemos llamado rootContactosView) para poder hacerlo visible de nuevo. Ya que esa variable (rootContactosView) se encuentra en la clase controladora de la lista (ContactosViewController) y hace falta tenerla en la clase controladora del detalle (PersonaDetalleController), se debe crear en esta última clase una propiedad y su correspondiente método set para pasar dicho objeto de una clase a otra.

esquema paneles mostrar master aa5f0

Así que en la clase controladora del detalle se debe añadir la siguiente propiedad (de tipo Pane genérico o AnchorPane que es el utilizado):

private Pane rootContactosView;

Con su correspondiente método set para poder asignarle el objeto correspondiente desde la otra clase controladora:

public void setRootContactosView(Pane rootContactosView) {
    this.rootContactosView = rootContactosView;
}

En la clase controladora de la lista, dentro los métodos On Action de los botones Nuevo y Editar que antes también hemos editado, se hará uso de este método para pasar la vista de lista al detalle (debe colocarse después cargar la vista de detalle con el fxmlLoader). Como siempre que se quiera hacer uso de un método que se encuentre en una clase controladora, hay que obtener previamente el objeto que se crea al cargar el archivo FXML, para lo que se usa la llamada fxmlLoader.getController().

PersonaDetalleViewController personaDetalleViewController = (PersonaDetalleViewController) fxmlLoader.getController();  
personaDetalleViewController.setRootContactosView(rootContactosView);

Ahora ya sí se podrá indicar en el código de los botones Guardar y Cancelar de la controladora de detalle, que se vuelva a hacer visible el contenedor de la lista de personas, y previamente se eliminará del StackPane principal la vista del detalle. Como puedes observar, se utiliza una llamada a getScene().getRoot(), lo cual permite obtener el objeto correspondiente al contenedor principal de la aplicación. A partir de ese contenedor principal se obtiene su lista de hijos (getChildren()) y de ella se elimina la vista de detalle para ocultarla y dejar de usarla.

StackPane rootMain = (StackPane)rootPersonaDetalleView.getScene().getRoot();
rootMain.getChildren().remove(rootPersonaDetalleView);      
rootContactosView.setVisible(true);

En el siguiente artículo se verá cómo cargar en la vista de detalle los datos de la persona seleccionada, pero ya debería mostrarse con los campos vacíos y volver a la lista principal cuando se haga uso de los botones Guardar o Cancelar.