📋 Introducción
En esta práctica vas a desarrollar una aplicación JavaFX que gestiona datos demográficos de personas. La aplicación permitirá añadir personas con su nombre, edad y localidad, y visualizar estadísticas mediante gráficos de barras.
src/dam/
1 Capa Vista (FXML)
📄 root.fxml
Contenedor principal con menú y pestañas
- Elemento raíz: BorderPane con styleClass="root" y enlaza root.css
- Asocia el controlador: RootController
- En la región top: coloca un MenuBar
- Dentro del MenuBar crea un Menu "Archivo"
- Dentro del menú añade un MenuItem "Cerrar" que ejecute el método cerrarApp()
- En la región center: coloca un TabPane con fx:id="tabPane"
- Dentro del TabPane crea dos pestañas:
- Tab "Datos" (fx:id="tabDatos", closable=false) con un AnchorPane vacío
- Tab "Gráfica" (fx:id="tabGrafica") con un AnchorPane vacío
📄 Datos.fxml
Formulario para dar de alta personas
- Elemento raíz: VBox con spacing=12, styleClass="panel" y enlaza datos.css
- Asocia el controlador: DatosController
- Añade padding de 12 en todos los lados
- Dentro del VBox:
- Un Label con styleClass="title" y texto "Alta de personas"
- Un GridPane (hgap=10, vgap=10) con:
- Fila 0: Label "Nombre:" y TextField (fx:id="txtNombre", promptText="Introduce el nombre")
- Fila 1: Label "Localidad:" y ChoiceBox (fx:id="chLocalidad")
- Fila 2: Label "Edad:" y TextField (fx:id="txtEdad", promptText="Número")
- Fila 3: HBox con spacing=10, alignment=CENTER, y columnSpan=2 conteniendo:
- Button "Añadir" (styleClass="btn-primary", onAction="#addPersona")
- Button "Vaciar" (styleClass="btn-danger", onAction="#vaciar")
📄 grafica.fxml
Visualización de gráficos estadísticos
- Elemento raíz: VBox con spacing=12, styleClass="panel" y enlaza grafica.css
- Asocia el controlador: GraficaController
- Añade padding de 12 en todos los lados
- Dentro del VBox:
- Un Label con styleClass="title" y texto "Gráfico"
- Un Label explicativo con wrapText=true y texto "Selecciona los datos a mostrar en el gráfico"
- Un HBox (alignment=CENTER_LEFT, spacing=10) con dos RadioButtons:
- RadioButton "Edad" (fx:id="rbEdad", onAction="#eventMostrarEdad")
- RadioButton "Localidad" (fx:id="rbLocalidad", onAction="#eventMostrarLocalidad")
- Un Separator
- Un BarChart (fx:id="grafico", title="Número de personas por localidad/edad") con:
- xAxis: CategoryAxis (fx:id="xAxis", side="BOTTOM")
- yAxis: NumberAxis (fx:id="yAxis", side="LEFT")
2 Clase Principal
📄 UD03T02_GestorDatosDemográficos.java
Objetivo: Punto de entrada de la aplicación JavaFX.
- La clase debe extender Application de JavaFX
- Método estático que recibe String[] args
- Dentro del método, simplemente llama a launch(args)
Este método se ejecuta cuando JavaFX está listo. Debe:
- Crear un FXMLLoader apuntando a "/dam/vista/root.fxml"
- Crear una Scene cargando el FXML con loader.load(), estableciendo dimensiones de 1000x680
- Establecer el título de la ventana
- Asignar la escena al stage
- Mostrar la ventana
3 Estilos CSS
🎨 root.css
Estilos para el contenedor principal y navegación
Para .menu-bar:
Para .menu-bar .label:
Para .menu-item .label:
Para .tab-pane:
Para .tab-pane .tab-header-area:
Para .tab-pane .tab-header-area .tab:
Para .tab-pane .tab-header-area .tab:selected:
Para .tab-pane .tab-label:
Para .tab-pane .tab-content-area:
🎨 datos.css
Estilos estilo Bootstrap para el formulario
Estado normal (.text-field):
Estado enfocado (.text-field:focused):
Estado normal (.choice-box):
Estado enfocado (.choice-box:focused):
Estado normal:
Estado hover (.btn-primary:hover):
Estado pressed (.btn-primary:pressed):
Estado normal:
Estado hover (.btn-danger:hover):
Estado pressed (.btn-danger:pressed):
🎨 grafica.css
Estilos Bootstrap-like para gráficos y controles
Para .radio-button:
Para .radio-button:selected .radio .dot:
Para .chart:
Para .chart-title:
Para .chart-plot-background:
Para .axis-label:
Para .axis-tick-label:
Para .chart-bar:
4 Capa Controlador
📄 RootController.java
Objetivo: Coordinar la aplicación principal y cargar dinámicamente las vistas.
- TabPane tabPane - referencia al contenedor de pestañas
- Tab tabDatos - referencia a la pestaña de datos
- Tab tabGrafica - referencia a la pestaña de gráfica
- Crea una instancia final de GestorDatosModel llamada "modelo"
Este método se ejecuta automáticamente al cargar el FXML. Aquí debes:
1. Cargar la vista Datos.fxml:
- Crea un FXMLLoader apuntando a "/dam/vista/Datos.fxml"
- Carga el layout con load() en una variable Parent
- Obtén el controlador con getController() (tipo DatosController) (
DatosController datosController = loaderDatos.getController();) - Llama a setModelo(modelo) en el controlador (
datosController.setModelo(modelo);) - Asigna el layout como contenido de tabDatos
2. Cargar la vista grafica.fxml:
- Crea otro FXMLLoader apuntando a "/dam/vista/grafica.fxml"
- Carga el layout con load()
- Obtén el controlador (tipo GraficaController)
- Llama a setModelo(modelo) en el controlador
- Asigna el layout como contenido de tabGrafica
3. Manejo de excepciones:
- Envuelve todo en un bloque try-catch para IOException
Este método se ejecuta al hacer clic en "Cerrar" del menú:
- Crea un Alert de tipo CONFIRMATION
- Establece el título "Salir"
- Establece el texto del encabezado "¿Deseas salir de la aplicación?"
- Establece el contenido "Se perderán los datos no guardados."
- Muestra el diálogo con showAndWait() y captura el resultado (
Optional<ButtonType> result = alert.showAndWait();) - Si el usuario pulsa OK, ejecuta Platform.exit()
📄 DatosController.java
Objetivo: Gestionar el formulario de alta de personas.
- ChoiceBox<String> chLocalidad - selector de localidad
- TextField txtEdad - campo para la edad
- TextField txtNombre - campo para el nombre
- GestorDatosModel modelo - referencia al modelo (sin inicializar)
Puede estar vacío o puedes dejarlo sin implementación relevante (se llama automáticamente).
Llamado desde RootController después de cargar el FXML:
- Asigna el parámetro al atributo this.modelo
- Vincula las localidades que se encuentran en el modelo (en un
ObservableList) al ChoiceBox
Se ejecuta al pulsar el botón "Añadir":
1. Obtener y validar datos:
- Obtén el texto de txtNombre (si es null conviértelo a "", luego aplica trim())
- Obtén el texto de txtEdad (si es null conviértelo a "", luego aplica trim())
- Obtén el valor seleccionado de chLocalidad
2. Validaciones (si fallan, llama a aviso() y termina):
- Si nombre está vacío → aviso("Nombre vacío", "Introduce un nombre.")
- Si localidad es null o está en blanco → aviso("Localidad no seleccionada", "Selecciona una localidad.")
- Intenta convertir edadTxt a int, si falla (NumberFormatException) → aviso("Edad inválida", "La edad debe ser un número entero.")
- Si edad < 0 o edad > 120 → aviso("Edad fuera de rango", "Introduce una edad entre 0 y 120.")
3. Guardar en modelo:
- Llama a al método del modelo que te permita crear una nueva persona
4. Limpiar formulario:
- Limpia txtNombre
- Limpia txtEdad
- Deselecciona el ChoiceBox
- Pone el foco en txtNombre
Se ejecuta al pulsar el botón "Vaciar":
- Limpia txtNombre
- Limpia txtEdad
- Deselecciona el chLocalidad
Método auxiliar para mostrar alertas:
- Crea un Alert de tipo WARNING
- Establece el título recibido
- Establece headerText a null
- Establece el contenido con el mensaje recibido
- Muestra el diálogo con showAndWait()
📄 GraficaController.java
Objetivo: Gestionar la visualización de gráficos estadísticos.
- BarChart<String, Number> grafico - el gráfico de barras
- RadioButton rbEdad - opción para mostrar por edad
- RadioButton rbLocalidad - opción para mostrar por localidad
- NumberAxis yAxis - eje Y (numérico)
- CategoryAxis xAxis - eje X (categorías)
- GestorDatosModel modelo - referencia al modelo
- ToggleGroup grupo (final) - inicializado con new ToggleGroup()
Se ejecuta automáticamente al cargar el FXML:
- Asigna rbEdad al ToggleGroup
- Asigna rbLocalidad al ToggleGroup
- Limpia el gráfico con grafico.getData().clear()
- Asigna el parámetro al atributo this.modelo
Se ejecuta al seleccionar el RadioButton "Edad":
- Llama a actualizarGrafico()
Se ejecuta al seleccionar el RadioButton "Localidad":
- Llama a actualizarGrafico()
Actualiza el contenido del gráfico según la opción seleccionada:
1. Validaciones iniciales:
- Si modelo es null, termina (return)
- Limpia el gráfico
- Si no hay selección en el grupo termina
2. Si está seleccionado rbLocalidad:
- Añade al gráfico los datos con grafico.getData().addAll(modelo.getPersonasLocalidad())
- Establece el título del gráfico: "Número de personas por localidad"
- Establece la etiqueta del yAxis: "Localidad"
- Establece la etiqueta del xAxis: "Nº personas"
3. Si está seleccionado rbEdad:
- Añade al gráfico los datos con grafico.getData().addAll(modelo.getPersonasEdad())
- Establece el título del gráfico: "Número de personas por edad"
- Establece la etiqueta del yAxis: "Edad"
- Establece la etiqueta del xAxis: "Nº personas"
5 Capa Modelo
📄 Clase Persona.java
Objetivo: Representar a una persona con sus datos básicos.
- Crea tres atributos privados y finales: nombre (String), edad (int), localidad (String)
- Implementa un constructor que reciba estos tres parámetros
- Crea métodos getter para los tres atributos (getNombre, getEdad, getLocalidad)
📄 Clase GestorDatosModel.java
Objetivo: Gestionar la lógica de negocio y los datos de la aplicación.
- Crea una ObservableList de Persona llamada "personas" (inicialízala vacía)
- Crea una ObservableList de String llamada "localidades" con las 4 provincias gallegas: A Coruña, Lugo, Ourense, Pontevedra
- Define una enumeración GrupoEdad con 4 valores: MENORES_18, DE_18_A_30, DE_31_A_50, MAYORES_50
- getPersonas(): devuelve la lista observable de personas
- getLocalidades(): devuelve la lista observable de localidades
- addPersona(String nombre, int edad, String localidad): añade una nueva persona a la lista
Este método clasifica a una persona según su edad:
- Si edad < 18 → devuelve "MENORES_18"
- Si edad entre 18 y 30 → devuelve "DE_18_A_30"
- Si edad entre 31 y 50 → devuelve "DE_31_A_50"
- Si edad > 50 → devuelve "MAYORES_50"
Genera datos para el gráfico de barras por grupos de edad:
- Crea una XYChart.Series llamada "personaEdad"
- Recorre todos los valores del enum GrupoEdad
- Para cada grupo, cuenta cuántas personas pertenecen a ese rango (usa analizarRango)
- Añade un XYChart.Data con el nombre del grupo y el contador
- Asigna el nombre "Edad" a la serie
- Devuelve una lista con esta única serie encapsulada dentro de un objeto de tipo Lista (
List.of(personaEdad);)
Genera datos para el gráfico de barras por localidades:
- Crea una XYChart.Series llamada "personaLocalidad"
- Recorre todas las localidades
- Para cada localidad, cuenta cuántas personas son de esa localidad
- Añade un XYChart.Data con el nombre de la localidad y el contador
- Asigna el nombre "Localidad" a la serie
- Devuelve una lista con esta única serie
6 Pruebas y Verificación
✅ Lista de verificación
- ☑️ La aplicación inicia sin errores
- ☑️ Se muestran las dos pestañas: "Datos" y "Gráfica"
- ☑️ El menú "Archivo > Cerrar" funciona y muestra confirmación
- ☑️ El ChoiceBox de localidades está cargado con las 4 provincias
- ☑️ Se puede escribir un nombre
- ☑️ Se puede seleccionar una localidad
- ☑️ Se puede introducir una edad numérica
- ☑️ El botón "Añadir" valida correctamente los campos
- ☑️ Aparecen alertas cuando faltan datos o la edad es inválida
- ☑️ El botón "Vaciar" limpia todos los campos
- ☑️ Después de añadir, el formulario se limpia automáticamente
- ☑️ Al inicio el gráfico está vacío
- ☑️ Al seleccionar "Localidad" aparece el gráfico por localidades
- ☑️ Al seleccionar "Edad" aparece el gráfico por grupos de edad
- ☑️ Los datos del gráfico se actualizan al cambiar de opción
- ☑️ Los ejes muestran las etiquetas correctas según la opción
- ☑️ Los paneles tienen sombra y bordes redondeados
- ☑️ Los botones tienen colores Bootstrap (azul y rojo)
- ☑️ Los campos de texto muestran efecto de enfoque (borde azul)
- ☑️ Las pestañas se resaltan cuando están seleccionadas
- ☑️ El gráfico tiene fondo gris claro y barras azules
🧪 Casos de prueba sugeridos
- Intenta añadir sin completar ningún campo → debe aparecer alerta "Nombre vacío"
- Escribe un nombre pero no selecciones localidad → debe aparecer alerta "Localidad no seleccionada"
- Introduce texto en edad → debe aparecer alerta "Edad inválida"
- Introduce -5 en edad → debe aparecer alerta "Edad fuera de rango"
- Introduce 150 en edad → debe aparecer alerta "Edad fuera de rango"
- Añade una persona de 15 años de A Coruña
- Añade una persona de 25 años de Lugo
- Añade una persona de 40 años de Ourense
- Añade una persona de 60 años de Pontevedra
- Ve a la pestaña "Gráfica"
- Selecciona "Localidad" → debe mostrar 1 persona por cada provincia
- Selecciona "Edad" → debe mostrar 1 en MENORES_18, 1 en DE_18_A_30, 1 en DE_31_A_50, 1 en MAYORES_50
🎓 Conclusión
¡Enhorabuena! Has completado la implementación de una aplicación JavaFX completa usando el patrón MVC. Has aprendido a:
- ✅ Separar la lógica de negocio (Modelo) de la presentación (Vista) y control (Controlador)
- ✅ Trabajar con FXML para diseñar interfaces de usuario declarativas
- ✅ Aplicar estilos CSS personalizados a componentes JavaFX
- ✅ Utilizar ObservableList para mantener datos reactivos
- ✅ Crear gráficos dinámicos con BarChart
- ✅ Implementar validaciones y manejo de errores
- ✅ Coordinar múltiples controladores desde un controlador principal