He tenido algo de tiempo para añadir la funcionalidad de Crear un Evento.
Al igual que con la creación de usuarios, usamos un JDialog con el formulario necesario para introducir los datos.
Pero esta vez es un poco más difícil.
Los eventos tienen una serie de atributos comunes, pero luego hay otros que son propios para cada tipo de evento.
Para no hacer tres formularios distintos, he hecho uno dividido en dos partes.
Una parte contiene los campos para los datos comunes. Son los que rodeo de amarillo en la imagen de abajo.
La otra parte, es un cardLayout que irá alternando entre tres formularios distintos, según el tipo de evento que señalemos en un ComboBox.
Es la parte que señalo en rojo:
Así con una sola clase podemos crear eventos de los tres tipos, pero esto implica que la clase va a tener bastante código debido a la gran cantidad de componentes Swing que intervienen.
Fijémonos en la parte amarilla, los atributos comunes.
Tiene el campo con el ID de evento. Este id no es editable, lo va a generar automáticamente el Gestor de Eventos con el formato EV000000, EV000001, EV000002.., y así sucesivamente
Tiene un campo de texto para el título un area de texto para la descripción.
Luego tiene un panel con tres Spinner para la fecha. Este panel es una clase que he escrito por separado y me ha quedado bastante bien, aunque en realidad lo más fácil habría sido poner un campo de texto y dar por hecho que el usuario va a introducir fechas correctas que podamos leer como simples String.
Pero en el enunciado he visto que para algunas acciones habrá que comparar fechas, así que no he querido dejar este campo sin asegurar "su calidad", es decir, que vamos a tener fechas válidas, nada de 45/23/2020 o alguna burrada similar.
Para esto, he escrito una clase que valiéndose de la clase LocalDate, comprueba en todo momento el año y mes que se está seleccionando, para decidir en tiempo real cuál es el valor límite para el Spinner del día.
Por ejemplo, si se marca mes valor 2 (Febrero) el límite será 28, pero si el año marcado es bisiesto, entonces el límite sería 29.
Es menos difícil de lo que parece, solo hay que manejar LocalDate (que es muy sencilla) y asignarle unos ChangeListener a los Spinner del mes y del año para que actúen sobre día cuando estos reciban algún cambio en su valor.
public class PanelFecha extends JPanel{
private JSpinner dia;
private SpinnerNumberModel modeloDia;
private JSpinner mes;
private SpinnerNumberModel modeloMes;
private JSpinner anio;
private SpinnerNumberModel modeloAnio;
private Font fuentePlana = new Font("Verdana", Font.PLAIN, 20);
private Font fuenteNegrita = new Font("Verdana", Font.PLAIN, 20);
private LocalDate fechaSelec;
public PanelFecha() {
iniciarFecha();
add(new PanelSpinner("Dia: ", dia));
add(new PanelSpinner("Mes: ", mes));
add(new PanelSpinner("Año: ", anio));
TitledBorder titulo = BorderFactory.createTitledBorder("Fecha");
titulo.setTitleFont(fuentePlana);
setBorder(titulo);
}
/**
* Retorna la fecha actualmente seleccionada
* en este panel.
* @return Fecha seleccionada.
*/
public LocalDate getFecha() {
int diaSel = (int) dia.getValue();
int mesSel = (int) mes.getValue();
int anioSel = (int) anio.getValue();
return LocalDate.of(anioSel, mesSel, diaSel);
}
/**
* El panel de fecha se inicia con la fecha actual
*/
private void iniciarFecha() {
fechaSelec = LocalDate.now();
//Año
anio = new JSpinner();
anio.setFont(fuentePlana);
modeloAnio = new SpinnerNumberModel();
modeloAnio.setMinimum(fechaSelec.getYear());
modeloAnio.setValue(fechaSelec.getYear());
anio.setModel(modeloAnio);
anio.addChangeListener(new ActualizaModeloDia());
//Mes
mes = new JSpinner();
mes.setFont(fuentePlana);
modeloMes = new SpinnerNumberModel();
modeloMes.setMinimum(1);
modeloMes.setMaximum(12);
modeloMes.setValue(fechaSelec.getMonthValue());
mes.setModel(modeloMes);
mes.addChangeListener(new ActualizaModeloDia());
//Dia
dia = new JSpinner();
dia.setFont(fuentePlana);
modeloDia = new SpinnerNumberModel();
modeloDia.setMinimum(1);
modeloDia.setMaximum(calcularMaximoDia());
modeloDia.setValue(fechaSelec.getDayOfMonth());
dia.setModel(modeloDia);
}
/**
* Calcula el máximo valor seleccionable
* para el campo dia.
* Este valor puede ser 31, 30, 28 ó 29 según
* el mes seleccionado y según si el año es
* bisiesto o no.
*/
private int calcularMaximoDia() {
switch((int)mes.getValue()) {
case 1:
case 3:
case 5:
case 7:
case 8:
case 10:
case 12:
return 31;
case 2: //Febrero, depende de si es año bisiesto
int year = (int) anio.getValue();
if ((year % 4 == 0) && ((year % 100 != 0) || (year % 400 == 0)))
return 29;
else
return 28;
default:
return 30;
}
}
private class PanelSpinner extends JPanel {
public PanelSpinner(String texto, JSpinner spinner) {
JLabel etiq = new JLabel(texto);
etiq.setFont(fuenteNegrita);
add(etiq);
add(spinner);
setBorder(BorderFactory.createRaisedSoftBevelBorder());
}
}
/**
* Este Listener se activa cuando se cambian la fecha
* y ajusta el modelo del Spinner del dia según los valores actuales.
* Este modelo ha de cambiar según si selecciona un año bisiesto
* y/o el valor del mes
*/
private class ActualizaModeloDia implements ChangeListener {
@Override
public void stateChanged(ChangeEvent e) {
modeloDia.setMaximum(calcularMaximoDia());
/*
* Tras actualizar valor máximo para dia, comprobamos su
* valor actual por si ha de ser corregido
*/
if ((int) modeloDia.getValue() > (int)modeloDia.getMaximum())
modeloDia.setValue(modeloDia.getMaximum());
}
}
}
Ahora fijémonos en la parte en rojo de la imagen anterior.
Como he dicho, ese panel es un cardLayout que irá cambiando según el tipo de evento seleccionado.
Para el deportivo, tenemos los dos nombres de los equipos, un valor para el aforo y un JComboBox para el tipo de deporte.
Este JComboBox, se construye usando el enum TipoDeporte que escribimos al principio de todo.
tipoDeporte = new JComboBox<TipoDeporte>(TipoDeporte.values());
Si seleccionamos el tipo Musical, la parte inferior cambia y nos muestra tres campos distintos.
El aforo máximo, un ComboBox para el tipo de musica (también construido a partir del enum TipoMusica) y un campo que muestra el valor del seguro que se ha de pagar como extra.
Este campo no es editable y se autocalcula según el valor del campo renta.
El enunciado dice que es el 30% del importe de la renta, así que cada vez que el usuario cambia el valor del campo renta, este campo se actualiza con el nuevo cálculo.
Este valor es informativo, no se guardará cuando vayamos a crear el evento, porque la clase evento
Musical ya la escribimos con un método que calcula por su propia cuenta el importe de este seguro.
Por eso le pusimos un valor constante llamado CUOTA_SEGURO, con valor 30 (30%) para hacer los cálculos con dicha constante.
De hecho, esta constante es pública y estática:
public final class Musical extends Evento{
//Constante que establece el limite de personas que pueden asistir a este evento
private final int AFORO_MAXIMO = 25000;
//Constante que establece el % de cuota de seguro que se cobra sobre la renta acordada
//Es publica y estática para que la interfaz gráfica conozca el valor de esta cuota
public static final double CUOTA_SEGURO = 30d; //30%
Así, en este formulario, podemos acceder a esta constante para que se autocalcule el valor del seguro en pantalla.
Esto lo conseguimos con un FocusListener.
Cuando el campo renta pierde "el foco", es decir, hemos escrito algo y nos hemos pasado a otro campo, entonces se recuperar lo que se haya escrito y se calcula el importe del seguro.
Así siempre está actualizado.
Marco en negrita la línea donde llamamos a la constante de la clase
Musical para saber el porcentaje de cuota que ha de pagarse por el seguro.
Además controlamos una posible excepción que ocurriría en el caso de que el campo renta estuviera vacío o tuviera letras en lugar de números y por tanto fallaría la conversión a double.
private class AccionCalcularSeguroMusical implements FocusListener {
@Override
public void focusGained(FocusEvent e) {
//Nada que hacer aquí
}
@Override
public void focusLost(FocusEvent e) {
/*
* Cuando el campo renta pierde el foco,
* se calcula el importe del seguro para
* el evento musical en base al valor de la renta
*/
try {
double renta = Double.parseDouble(campoRenta.getText());
double importe = renta * Musical.CUOTA_SEGURO / 100;
campoSeguroMusical.setText(String.format("%.2f", importe));
}
catch(Exception ex) {
//Si renta no tiene valor numérico, se produce excepcion
campoSeguroMusical.setText(null);
}
}
}
Para los eventos religiosos, tenemos solo dos campos:
Un Spinner para el aforo y un campo de texto para el importe del seguro. En los eventos religiosos, este importe es siempre el mismo, independientemente de la renta.
En su clase, figura también como una constante pública y estática.
public final class Religioso extends Evento{
//Constante que establece el limite de personas que pueden asistir a este evento
private final int AFORO_MAXIMO = 30000;
//Constante que establece el importe fijado como seguro por desgaste de la grama
public static final int CUOTA_SEGURO = 2000; //Public y estático, no será necesario usar getter para acceder a este valor
La intención de hacer constantes públicas para estos valores es porque, si esto fuera un programa real, en caso de que estas cuotas quisieran ser modificadas bastaría con cambiar el valor solo dentro de sus clases para que los cambios automáticamente se replicaran en el resto de clases.
Si pusiéramos "a mano" estos valores en los formularios, habría que recorrer las clases que usen estos valores para actualizarlos las nuevas cuotas.
En un programa real, donde pueden haber decenas de clases y formularios, esto daría mucho trabajo y pie a errores si algunas clases no fueran actualizadas respecto a las demás.
(Sigue a continuación...)