10. [TD]: Implementación de la capa [ui] con una interfaz Swing de tipo « »
Palabras clave: arquitectura multicapa, Spring, inyección de dependencias, biblioteca de componentes Swing.
![]() |
10.1. Support
![]() |
En la capa [UI], queremos crear una interfaz gráfica de usuario Swing. NetBeans cuenta con una herramienta [Matisse] para crear estas interfaces Swing, que es superior a lo que ofrece Eclipse. Las interfaces Swing tienden a ser sustituidas por interfaces JavaFx. NetBeans y Eclipse utilizan la misma herramienta para crear estas últimas. Por lo tanto, si creamos interfaces JavaFx, podemos mantener Eclipse en toda la arquitectura por capas.
NetBeans puede abrir cualquier proyecto Maven. Por lo tanto, vamos a utilizar el proyecto Maven anterior y añadirle una interfaz Swing. En [2], cargamos (Archivo / Abrir proyecto) los proyectos Maven de las tres capas que hemos creado con Eclipse. A continuación, generamos sus binarios [3]. Las opciones [Build] y [Clean and Build] compilan el binario del proyecto al que se aplican. Estos binarios se colocan en la carpeta [target] [4-5] del proyecto:
![]() |
La opción [Clean] elimina esta carpeta [target]. La opción [Build] la reconstruye. La experiencia demuestra que, cuando surgen problemas inesperados, lo primero que hay que hacer es ejecutar un [Clean and Build] en el proyecto para asegurarse de que se está trabajando con la última versión del mismo. Esto es especialmente necesario cuando se tienen archivos de configuración que, si se modifican, no provocan una recompilación automática al ejecutar el proyecto. En ese caso, hay que forzar dicha recompilación mediante un [Clean and Build] para que sus nuevas versiones se instalen en la carpeta [target].
10.2. Funcionamiento de la aplicación
Volvamos a la arquitectura general de la aplicación [Elections]:
![]() |
Ahora nos centramos en una nueva implementación de la capa [ui]. La única implementación realizada hasta el momento es una interfaz de consola. Ahora crearemos una interfaz gráfica.
El usuario dispondrá de la siguiente interfaz para interactuar con la aplicación [Elections]:
![]() |
![]() |
La interfaz gráfica se encuentra en la capa [ui]. Es esta la que interactúa con el usuario.
- Al iniciarse, la aplicación de consola [main] crea instancias de las tres capas de la aplicación mediante Spring. Esto se realiza incluso antes de que la interfaz gráfica sea visible. También en esta fase de inicialización, se solicitan a la capa [dao] los datos que caracterizan la elección (número de escaños a cubrir, umbral electoral, listas que se presentan). Si esta fase de inicialización falla (por ejemplo, si no se puede acceder a los datos), se muestra un mensaje de error en la consola y no se muestra la interfaz gráfica.
- Si la lectura de los datos se ha realizado correctamente, se muestra la interfaz gráfica con la siguiente información (véase la captura de pantalla anterior):
- el número de plazas disponibles en (2)
- el umbral electoral en (3)
- los identificadores y nombres de las listas candidatas en (4)
- A continuación, el usuario asigna a cada lista candidata su número de votos mediante los campos 4 (id - nombre), 5 (votos) y 6 (para añadir).

- A continuación, se puede utilizar el enlace (10) para calcular los escaños:

- El enlace [Enregistrer] (12) permite guardar los resultados en la fuente de datos.
10.3. La clase de implementación [ElectionsSwing] de la capa [ui]
10.3.1. El proyecto NetBeans
Nota: En el apartado 22.4 se indica cómo obtener NetBeans.
El proyecto final de NetBeans de la aplicación tendrá el siguiente formato: [1]. Compílalo siguiendo los pasos de [2-5]:
![]() |
![]() |
Asegúrate de que el proyecto esté configurado para ser compilado por un JDK 1.8 [1-6]:
![]() |
10.3.2. Configuración de Maven
El nuevo proyecto [elections-swing-metier-dao-jdbc] se basará en el proyecto anterior [elections-console-metier-dao-jdbc]. Para ello, se añade una dependencia de Maven de la siguiente manera [1-3]:
![]() |
![]() |
10.3.3. Creación de la interfaz gráfica
Para crear la interfaz gráfica, podemos proceder de la siguiente manera:
![]() |
![]() |
- [1]: añadir un objeto al paquete [elections.ui.service]
- [2]: selección de la opción [JFrame Form] en la categoría [Swing GUI Forms]
![]() |
- [4]: asignar un nombre a la clase
- [5]: el paquete de la clase.
- Finalizar el asistente.
- [6]: la clase generada
![]() |
- [7]: la clase [AbstractElectionsSwing] en modo [Design]
- [8]: la pestaña [Navigator] que muestra el árbol [9] de los componentes de la ventana
- [10]: la pestaña [Properties], que muestra las propiedades del componente [jFrame] seleccionado en [9]
![]() |
- [11]: [JFrame] es un contenedor de componentes. Estos pueden colocarse en el contenedor siguiendo diversas reglas de posicionamiento denominadas layouts. En este caso, elegimos layout, [Free Design] y [14], que permiten colocar los componentes libremente dentro del contenedor.
Encontramos los componentes en la barra de herramientas denominada Palette:
![]() |
- [1]: la paleta
- [2]: se coloca un componente JLabel en el contenedor de componentes
- ; al hacer clic con el botón derecho sobre él, se accede a diversas propiedades: su nombre [4], su texto [3] o sus gestores de eventos [5]. Utilizamos [3] para establecer el texto [6].
![]() |
- [1]: la pestaña [Properties] del componente [JLabel] permite acceder a sus propiedades: su posición horizontal [2], la posición vertical [3], el tipo de letra del texto [4] y el texto [5].
Cuando se coloca y se configura un componente en la interfaz gráfica y se guarda (Ctrl-S) el trabajo realizado, se genera código en la clase [AbstractElectionsSwing]:
![]() |
![]() |
No se debe modificar este código en gris, ya que se elimina y se vuelve a generar al guardar por siguiente vez. De lo contrario, se perderían los cambios realizados.
Encontrarás un tutorial sobre cómo crear una interfaz gráfica con NetBeans en URL y [https://netbeans.org/kb/docs/java/quickstart-gui.html?print=yes#design] (noviembre de 2015).
Ahora vamos a crear la siguiente interfaz:
![]() |
Los componentes de la interfaz son los siguientes:
n.º | tipo | nombre | función |
JMenuBar | jMenuBar1 | un menú | |
JLabel | jLabelSAP | el número de plazas disponibles | |
JLabel | jLabelSE | el umbral electoral | |
JComboBox | jComboBoxNomsListes | Lista de nombres de las listas que se presentan a las elecciones | |
JTextField | jTextFieldVoixListe | el número de votos de una lista | |
JLabel | jLabelAjouter | Para añadir una lista a (8) | |
(JScrollPane, JList) | jListNomsVoix | los nombres y las voces de las listas | |
JLabel | jLabelSupprimer | para eliminar de (8) la lista seleccionada en (8) | |
JLabel | jLabelCalculer | para calcular los resultados de las elecciones | |
JLabel | jLabelEffacer | para borrar los resultados de la elección | |
JLabel | jLabelEnregistrer | para registrar los resultados de las elecciones | |
(JScrollPane, JList) | jListResultats | para visualizar los resultados de la elección | |
(JScrollPane, JTextPane) | jTextPaneMessages | para ver los mensajes de seguimiento |
La anotación (JScrollPane, JList) [13-14] sirve para indicar que, al soltar un componente [JList] en la ventana, este se inserta automáticamente en un componente [JScrollPane] que permite desplazarse por la lista. El componente [JScrollPane] permite ver todos los elementos de la lista, aunque en un momento dado solo sea visible un número limitado de ellos. Lo mismo ocurre con los componentes [JTextPane] y [15-16].
El menú se puede crear de la siguiente manera:
![]() |
- [1, 2]: se coloca un componente [Menu Bar] en la ventana
- [3]: el menú generado por defecto tal y como se muestra en la pestaña [Navigator]
- [4,5,6]: al hacer clic con el botón derecho del ratón sobre una opción del menú, se puede:
- cambiar su texto ([4]) y su nombre ([5])
- gestionar sus eventos [6]
- [7]: el menú deseado
El menú deseado es el siguiente:
nivel 1 | nivel 2 |
Elecciones | |
Quitter | |
Listas | |
Ajouter | |
Supprimer | |
Resultados | |
Calculer | |
Effacer | |
Enregistrer | |
Acerca de |
Se puede probar la interfaz gráfica en cualquier momento:
![]() |
Al crear la interfaz, hay que asociar un gestor de eventos a determinadas etiquetas y menús [Ajouter, Effacer, ...]. A continuación se explica cómo hacerlo:
![]() |
- [1]: haz clic con el botón derecho del ratón sobre el componente cuyo evento quieras gestionar
- [2]: selecciona la opción [Events]
- [3]: selecciona una categoría de eventos
- [4]: seleccionar el evento que se desea gestionar
El código Java generado por esta operación es el siguiente:
jLabelCalculer.addMouseListener(new java.awt.event.MouseAdapter() {
public void mouseClicked(java.awt.event.MouseEvent evt) {
jLabelCalculerMouseClicked(evt);
}
});
...
private void jLabelCalculerMouseClicked(java.awt.event.MouseEvent evt) {
// TODO añade aquí tu código de gestión:
}
- líneas 1-5: se añade un gestor de eventos al componente jLabelCalculer. El método addMouseListener espera como parámetro una clase que implemente la siguiente interfaz MouseListener:
![]() |
La interfaz MouseListener está implementada por diferentes clases, entre ellas la clase MouseAdapter. Esta clase implementa los cinco métodos de la interfaz MouseListener, pero dichos métodos no realizan ninguna acción. Por lo tanto, es necesario derivar esta clase para implementar el método o los métodos que se deseen. Esto es lo que se hace en el código anterior y se repite a continuación:
jLabelCalculer.addMouseListener(new java.awt.event.MouseAdapter() {
public void mouseClicked(java.awt.event.MouseEvent evt) {
jLabelCalculerMouseClicked(evt);
}
});
El código anterior utiliza la técnica de la clase anónima explicada en el apartado 2.5 del curso [ref1].
En la línea 1, el parámetro del método addMouseListener es una clase anónima, definida sobre la marcha. Se trata de una instancia de una clase derivada de la clase MouseAdapter (línea 1), cuyo método mouseClicked (líneas 2-4) se redefine para que realice una acción determinada.
El método jLabelCalculerMouseClicked, denominado «línea 3», se define de la siguiente manera:
private void jLabelCalculerMouseClicked(java.awt.event.MouseEvent evt) {
// TODO añade aquí tu código de gestión:
}
El desarrollador gestiona el evento «MouseClicked» introduciendo código en este método.
Todos los controladores de eventos son generados por NetBeans de esta manera. El desarrollador puede ignorar las líneas de código generadas por NetBeans para asociar un método a un evento de un componente. Puede limitarse a escribir su código en la línea 2 anterior. He aquí un ejemplo:
private void jLabelCalculerMouseClicked(java.awt.event.MouseEvent evt) {
System.out.println("Mouse Clicked");
}
Si ejecutamos la interfaz gráfica y hacemos clic en el enlace [Calculer], aparece un mensaje en la consola:
![]() |
- [1]: hacemos doble clic en la etiqueta [Calculer]
- [2]: se ha ejecutado el gestor de este evento y ha generado los mensajes mouseClicked en la consola de NetBeans.
Los componentes [jComboBoxNomsListes, jListNomsVoix, jListResultats] se declaran de la siguiente manera:
protected javax.swing.JComboBox jComboBoxNomsListes;
protected javax.swing.JList jListNomsVoix;
protected javax.swing.JList jListResultats;
Estos componentes son listas que normalmente se configuran con un tipo T: el tipo de los elementos del modelo que muestran los componentes. Este tipo T puede ser cualquiera. El valor que se muestra en el componente de lista es de tipo [String]. Por defecto, se utiliza el método [T.toString()] para la visualización. Para controlar mejor lo que se mostrará, el tipo T será aquí el tipo String. Por lo tanto, la declaración correcta de nuestras listas es la siguiente:
protected javax.swing.JComboBox<String> jComboBoxNomsListes;
protected javax.swing.JList<String> jListNomsVoix;
protected javax.swing.JList<String> jListResultats;
Se obtiene este resultado modificando una de las propiedades del componente:
![]() |
10.3.4. Separación del código
Volvamos a la estructura de nuestra aplicación:
![]() |
La clase [AbstractElectionsSwing] debe implementar la capa [ui] anterior. Su código, generado por NetBeans, solo contiene por ahora código de gestión de la ventana y controladores de eventos que, de momento, no hacen nada. Como se ve arriba, la clase [AbstractElectionsSwing] deberá gestionar las interacciones con la capa [métier]. Esta gestión se llevará a cabo en los controladores de eventos. Para aclarar la estructura del código, decidimos dividirlo en dos clases:
- [AbstractElectionsSwing], que se mantendrá tal y como la generó NetBeans, salvo por algunos detalles. Esta clase no gestionará ningún evento por sí misma. Los controladores de eventos estarán vacíos y se declararán abstractos. Serán implementados por una clase derivada de [AbstractElectionsSwing].
- [ElectionsSwing], clase derivada de [AbstractElectionsSwing], que implementará todos los controladores de eventos.
Este tipo de separación no es inusual. Se encuentra, por ejemplo, en las páginas web ASP.NET (versión distinta de MVC). El proyecto NetBeans evoluciona de la siguiente manera:
![]() |
Por su parte, el código de la clase [AbstractElectionsSwing] evoluciona de la siguiente manera:
public abstract class AbstractElectionsSwing {
....
private void jMenuItemCalculerActionPerformed(java.awt.event.ActionEvent evt) {
doCalculer();
}
...
private void jLabelCalculerMouseClicked(java.awt.event.MouseEvent evt) {
if (jLabelCalculer.isEnabled()) {
doCalculer();
}
}
....
// gestores de eventos
abstract protected void doSupprimer();
abstract protected void doCalculer();
abstract protected void doQuitter();
abstract protected void doEffacer();
abstract protected void doEnregistrer();
abstract protected void doAjouter();
abstract protected void doInformer();
abstract protected void doMajLabelAjouter();
abstract protected void doMajLabelSupprimer();
...
}
- línea 1: la clase se declara abstracta
- líneas 3-5: gestión del clic en la opción de menú [jMenuItemCalculer]. Se observa que el tratamiento del evento se delega al método doCalculer de la línea 19. Este método no está implementado y se declara abstracto. Será la clase derivada [ElectionsSwing] la que lo implemente;
- líneas 9-13: el controlador del evento clic en la etiqueta [jLabelCalculer]. El clic siempre provoca un evento, tanto si el componente [jLabel] está activo (enabled=true) como si está inactivo (enabled=false). Aquí nos aseguramos de que esté activo para gestionar el evento;
- líneas 15 y siguientes: esta técnica de delegación del tratamiento de eventos a un método abstracto se aplica a todos los controladores de eventos.
La clase [ElectionsSwing], derivada de [AbstractElectionsSwing], implementa todos los controladores de eventos no implementados por [AbstractElectionsSwing]:
package elections.ui.service;;
...
public class ElectionsSwing extends AbstractElectionsSwing {
// gestores de eventos
@Override
protected void doInformer() {
...
}
@Override
protected void doAjouter() {
...
}
@Override
protected void doCalculer() {
...
}
@Override
protected void doEffacer() {
...
}
@Override
protected void doEnregistrer() {
...
}
@Override
protected void doQuitter() {
System.exit(0);
}
@Override
protected void doSupprimer() {
...
}
@Override
protected void doMajLabelAjouter() {
...
}
@Override
protected void doMajLabelSupprimer() {
...
}
}
- línea 3: [ElectionsSwing] deriva de [AbstractElectionsSwing]
- líneas 7-50: los controladores de eventos de la ventana gráfica
Los métodos de la clase derivada [ElectionsSwing] manipularán los componentes de la clase padre [AbstractElectionsSwing]. Actualmente, estos componentes tienen un ámbito private, lo que impide que la clase hija [ElectionsSwing] tenga acceso a ellos:
private JMenuItem jMenuItemAPropos = null;
private JLabel jLabelAjouter = null;
Para resolver este problema, nos aseguraremos de que el ámbito de los componentes de la interfaz gráfica sea [protected]:
![]() |
- establecer en [3] el atributo [protected];
10.3.5. Implementación de la interfaz [IElectionsUI]
Volvamos a la estructura de nuestra aplicación:
![]() |
En el ejemplo anterior, la capa [ui] debe presentar la interfaz [IElectionsUI] al objeto [main]:
package elections.ui.service;
public interface IElectionsUI {
/**
* lance le dialogue avec l'utilisateur
*/
public void run();
}
Esta interfaz se ha definido en el proyecto [elections-console-metier-dao-jdbc] y se describe en el apartado 9.4. Dado que este proyecto es una dependencia del proyecto [swing], esta interfaz es conocida.
Dado que la clase [AbstractElectionsSwing] se ha convertido en abstracta, Spring ya no puede instanciarla. A partir de ahora, es la clase [ElectionsSwing] la que debe ser instanciada. La clase [ElectionsSwing] debe implementar la interfaz [IElectionsUI]. Por lo tanto, su código queda como sigue:
public class ElectionsSwing extends AbstractElectionsSwing implements IElectionsUI {
// método «run» de la interfaz [ElectionsUI]
public void run() {
...
}
- línea 1: la clase [ElectionsSwing] implementa la interfaz [IElectionsUI]
- líneas 4-6: el método [run] de esta interfaz
¿Qué debe hacer el método run? Mostrar la ventana gráfica. ¿Cómo se hace esto? Podemos guiarnos por el método [main] que NetBeans ha generado en la clase [AbstractElectionsSwing] y que hace lo que se desea:
public static void main(String args[]) {
/* Configurar el aspecto de Nimbus */
//<editor-fold defaultstate="collapsed" desc="Código de configuración de la apariencia (opcional)">
/* Si Nimbus (introducido en Java SE 6) no está disponible, mantén la apariencia predeterminada.
* For details see http://download.oracle.com/javase/tutorial/uiswing/lookandfeel/plaf.html
*/
try {
for (javax.swing.UIManager.LookAndFeelInfo info : javax.swing.UIManager.getInstalledLookAndFeels()) {
if ("Nimbus".equals(info.getName())) {
javax.swing.UIManager.setLookAndFeel(info.getClassName());
break;
}
}
} catch (ClassNotFoundException ex) {
java.util.logging.Logger.getLogger(NewJFrame.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
} catch (InstantiationException ex) {
java.util.logging.Logger.getLogger(NewJFrame.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
} catch (IllegalAccessException ex) {
java.util.logging.Logger.getLogger(NewJFrame.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
} catch (javax.swing.UnsupportedLookAndFeelException ex) {
java.util.logging.Logger.getLogger(NewJFrame.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
}
//</editor-fold>
/* Crear y mostrar el formulario */
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
new NewJFrame().setVisible(true);
}
});
}
El constructor [AbstractElectionsSwing] utilizado en la línea 28 es el siguiente:
public AbstractElectionsSwing() {
initComponents();
}
- Línea 2: el método [initComponents] es un método privado generado por el generador de la interfaz gráfica. No se puede modificar su código.
El método [run] de la clase [ElectionsSwing] podría ser entonces el siguiente:
@Override
public void run() {
// Se muestra la interfaz gráfica
SwingUtilities.invokeLater(new Runnable() {
public void run() {
init();
setVisible(true);
}
});
}
- línea 6: la interfaz gráfica se inicializa mediante el método [init]. Aquí querríamos llamar al método [initComponents] de la clase padre, pero este es privado. Por lo tanto, se añade en la clase padre [AbstractElectionsSwing] el siguiente método [init]:
protected void init(){
initComponents();
}
- (continuación)
- como se encuentra en la clase [AbstractElectionsSwing], el método [init] tiene acceso al método privado [initComponents] de la misma clase;
- como tiene el atributo [protected], es visible en la clase hija [ElectionsSwing];
- línea 7: se hace visible la interfaz gráfica;
Nota: una vez que se ha escrito el método [run] en la clase [ElectionsSwing], se puede eliminar el método [main] de la clase abstracta [AbstractElectionsSwing].
10.3.6. La clase ejecutable
Volvamos a la estructura de nuestra aplicación:
![]() |
Nos gustaría que Spring instanciara la capa [ui] tal y como se hacía cuando esta estaba implementada por una aplicación console. Para ello, es necesario que la clase de implementación [ElectionsSwing] tenga una referencia a la capa [métier]:
@Component
public class ElectionsSwing extends AbstractElectionsSwing implements IElectionsUI{
// Referencia sobre la capa [métier]
@Autowired
private IElectionsMetier metier;
...
- línea 1: la clase [ElectionsSwing] es un componente de Spring;
- líneas 5-6: inyección por parte de Spring de una referencia a la capa [métier];
La interfaz gráfica se inicia mediante la ejecución de la siguiente clase [BootElectionsSwing]:
![]() |
package elections.ui.boot;
import elections.ui.service.IElectionsUI;
public class BootElectionsSwing extends AbstractBootElections {
public static void main(String[] arguments) {
new BootElectionsSwing().run();
}
@Override
protected IElectionsUI getUI() {
return ctx.getBean("electionsSwing", IElectionsUI.class);
}
}
Ya explicamos un código similar en el apartado 9.5, al analizar el código de las clases [AbstractBootElections] y [BootElectionsConsole]. En la línea 12, se recupera el bean denominado [electionsSwing], que corresponde al nombre estándar de Spring para la clase [ElectionsSwing].
10.3.7. Inicialización de la interfaz gráfica
Cuando se muestra la interfaz gráfica, algunos de sus componentes ya se han inicializado:

Como se puede ver arriba:
- que el menú desplegable se ha rellenado con los nombres de las listas;
- que se indican el número de escaños a cubrir y el umbral electoral;
- que se han desactivado algunos enlaces;
- que aparece un mensaje de éxito en la parte inferior de la ventana;
¿En qué momento se llevarán a cabo estas inicializaciones? Solo pueden realizarse tras la inicialización del campo [electionsMetier] de la clase [ElectionsSwing]. De hecho, los nombres de las listas se solicitarán a la capa [metier]. Spring llevará a cabo la inicialización de este campo en el siguiente orden:
- uso del constructor sin parámetros de la clase [ElectionsSwing];
- inyección de dependencias, en este caso la referencia de la capa [métier];
- ejecución del método [run] de la clase [ElectionsSwing]:
@Override
public void run() {
// Se muestra la interfaz gráfica
SwingUtilities.invokeLater(new Runnable() {
public void run() {
init();
setVisible(true);
}
});
}
- en la línea 12, hemos indicado que se llama al método [init] de la clase padre, que se encargará de dibujar los componentes de la interfaz gráfica. Vamos a redefinir este método, de forma local, en la clase [ElectionsSwing]. Es en este método donde inicializaremos, esta vez con datos, los componentes de la ventana (lista desplegable, etiquetas):
El método local [init] podría tener la estructura siguiente:
public class ElectionsSwing extends AbstractElectionsSwing implements IElectionsUI {
...
// Referencia a la capa [métier]
@Autowired
private IElectionsMetier metier;
// inicializaciones
public void init() {
// generación de componentes por parte de la clase padre
super.init();
// se solicitan las listas a la capa [metier]
...
//: se asocian los nombres de las listas al cuadro combinado jComboBoxNomsListes
...
// así como los parámetros de la elección
...
// se inicializan las etiquetas relacionadas con estos dos datos
...
// mensaje de éxito
...
// inicialización del estado de determinados componentes del formulario
...
}
Cabe destacar, en la línea 11, la llamada al método [init] de la clase padre.
10.3.8. La clase [Utilitaires]
Se han agrupado varios métodos utilitarios estáticos en la clase [Utilitaires]:
![]() |
La clase [Utilitaires] es la siguiente:
package istia.st.elections.ui;
import javax.swing.JLabel;
import javax.swing.JMenuItem;
//clase de utilidades
class Utilitaires {
// gestionar el estado de una tabla de etiquetas
public static void setEnabled(JLabel[] labels, boolean value) {
for (int i = 0; i < labels.length; i++) {
labels[i].setEnabled(value);
}
}
// gestionar el estado de una matriz de opciones de menú
public static void setEnabled(JMenuItem[] menuItems, boolean value) {
...
}
}
- línea 9: el método setEnabled establece el estado de los componentes JLabel definidos en una matriz. El método setEnabled de un componente JLabel permite activar o desactivar el JLabel.
Tarea a realizar: siguiendo el ejemplo del método setEnabled de la línea 9 (,), escribe el método setEnabled de la línea 16, que realiza la misma acción con los componentes JMenuItem.
10.3.9. El código de la clase [ElectionsSwing]
Recordemos la estructura general de la clase [ElectionsSwing]:
package istia.st.elections.ui;
...
public class ElectionsSwing extends AbstractElectionsSwing implements IElectionsUI {
// Referencia sobre la capa [métier]
@Autowired
private IElectionsMetier metier;
// inicializaciones
public void init() {
...
}
// gestores de eventos
@Override
protected void doInformer() {
...
}
@Override
protected void doAjouter() {
...
}
@Override
protected void doCalculer() {
...
}
@Override
protected void doEffacer() {
...
}
@Override
protected void doEnregistrer() {
...
}
@Override
protected void doQuitter() {
System.exit(0);
}
@Override
protected void doSupprimer() {
...
}
@Override
protected void doMajLabelAjouter() {
...
}
@Override
protected void doMajLabelSupprimer() {
...
}
}
Vamos a estudiar los métodos de la clase uno por uno.
10.3.9.1. El método [init]
Volvamos a la interfaz gráfica:
![]() |
El método [init] tiene como objetivos:
- rellenar el campo combinado [4] con los identificadores y los nombres de las listas en el formato [id - nom]
- mostrar un mensaje de éxito en [15]
- inicializar las etiquetas [2] y [3]
- desactivar determinados enlaces
El esqueleto del método [init] podría ser el siguiente:
@Override
protected void init() {
// generación de componentes por parte de la clase padre
super.init();
// inicializaciones locales
modèleNomsVoix = new DefaultListModel<>();
jListNomsVoix.setModel(modèleNomsVoix);
modèleRésultats = new DefaultListModel<>();
jListResultats.setModel(modèleRésultats);
String info;
try {
// se solicitan las listas a la capa [métier]
listes = ...
// se asocian los nombres de las listas al cuadro combinado jComboBoxNomsListes
...
// así como los parámetros de la elección
int nbSiegesAPourvoir = ...
double seuilElectoral = ...
// se inicializan las etiquetas relacionadas con estos dos datos
...
// mensaje de éxito
info = "Source de données lue avec succès";
} catch (ElectionsException ex1) {
// se registra el error
info = getInfoForException("Les erreurs suivantes se sont produites :", ex1);
} catch (RuntimeException ex2) {
// se registra el error
info = getInfoForException("Les erreurs suivantes se sont produites :", ex2);
}
// se muestra la información
jTextPaneMessages.setText(info);
jTextPaneMessages.setCaretPosition(0);
// estado del formulario
Utilitaires.setEnabled(new JLabel[] { jLabelAjouter, jLabelCalculer, jLabelEnregistrer, jLabelSupprimer }, false);
Utilitaires.setEnabled(
new JMenuItem[] { jMenuItemAjouter, jMenuItemCalculer, jMenuItemEnregistrer, jMenuItemSupprimer }, false);
// centrar la ventana
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
Dimension frameSize = getSize();
if (frameSize.height > screenSize.height) {
frameSize.height = screenSize.height;
}
if (frameSize.width > screenSize.width) {
frameSize.width = screenSize.width;
}
setLocation((screenSize.width - frameSize.width) / 2, (screenSize.height - frameSize.height) / 2);
}
private String getInfoForException(String message, ElectionsException ex) {
// se muestra el mensaje
StringBuffer info = new StringBuffer(String.format("%s -------------\n", message));
info.append(String.format("Code erreur : %d\n", ex.getCode()));
// se muestran los errores
for (String erreur : ex.getErreurs()) {
info.append(String.format("-- %s\n", erreur));
}
return info.toString();
}
private String getInfoForException(String message, RuntimeException ex) {
// se muestra el mensaje
StringBuffer info = new StringBuffer(String.format("%s -------------\n", message));
// se muestra la pila de excepciones
Throwable cause = ex;
while (cause != null) {
info.append(String.format("-- %s\n", cause.getMessage()));
cause = cause.getCause();
}
return info.toString();
}
Tarea: completa el código del método [init].
Lectura recomendada en el curso: componentes JTextField, JLabel
A tener en cuenta:
Un componente JList muestra los datos presentes en una plantilla. Por defecto, esta plantilla es de tipo DefaultListModel (líneas 2 y 3). Un objeto de tipo DefaultListModel se comporta de forma similar a uno de tipo ArrayList:
- para añadir un objeto o a la plantilla:
[DefaultListModel].addElement(Object o);
En esta aplicación, el objeto o siempre será de tipo String.
- Para eliminar el elemento n.º i de la plantilla:
[DefaultListModel].remove(int i);
- Para obtener el elemento n.º i del modelo:
[DefaultListModel].elementAt(int i);
Para añadir un elemento a la lista desplegable [jComboBoxNomsListes], se utilizará el método [addItem]:
jComboBoxNomsListes.addItem(chaîne de caractères)
Un componente JTextPane dispone de los métodos getText() y setText() para leer y escribir el texto mostrado.
10.3.9.2. Gestionar el estado del enlace [Ajouter]
El enlace [Ajouter] [6] solo está activo cuando el campo [5] de las entradas no está vacío. En la clase [AbstractElectionsSwing], el gestor que sigue los movimientos del cursor en el campo [5] es el siguiente:
private void jTextFieldVoixListeCaretUpdate(javax.swing.event.CaretEvent evt) {
doMajLabelAjouter()
}
La línea 2 llama al método [doMajLabelAjouter] de la clase [ElectionsSwing].
protected void doMajLabelAjouter() {
// se establece el estado de la etiqueta [jLabelAjouter]
...
// se establece el estado del menú [jMenuItemAjouter]
...
}
Tarea: completa el código del método [doMajLabelAjouter].
10.3.9.3. Asignar los votos a cada lista
Para cada lista candidata de (4), se procede de la siguiente manera:
- selección de una lista en (4)
- introducir el número de votos en (5)
- validar haciendo clic en el enlace [Ajouter]
Los errores de introducción de datos se señalan tal y como se muestra en el siguiente ejemplo:

Si el número de votos es correcto, la lista se añade al componente (8), se borra el número de votos y se desactiva el enlace [Ajouter]:
![]() | ![]() |
En la clase [AbstractElectionsSwing], el controlador que gestiona el clic en el enlace [Ajouter] es el siguiente:
private void jLabelAjouterMouseClicked(java.awt.event.MouseEvent evt) {
if (jLabelAjouter.isEnabled()) {
doAjouter();
}
}
La línea 3 llama al método [doAjouter] de la clase [ElectionsSwing]:
// plantillas de las listas JList
private DefaultListModel<String> modèleNomsVoix = null;
private DefaultListModel<String> modèleRésultats = null;
// las listas en competición
private ListeElectorale[] listes;
// listas introducidas por el usuario
private final List<ListeElectorale> listesSaisies = new ArrayList<>();
private ListeElectorale[] tListesSaisies;
...
@Override
protected void doAjouter() {
// ¿Es correcto el número de votos?
...
// si hay un error, se señala
if (erreur) {
JOptionPane.showMessageDialog(null, "Nombre de voix incorrect", "Elections : erreur",
JOptionPane.INFORMATION_MESSAGE);
jTextFieldVoixListe.requestFocus();
// volver a la interfaz gráfica
return;
}
// sin errores: se guarda la lista
listesSaisies.add(...);
modèleNomsVoix.addElement(...);
// Se borra el recuento de votos
jTextFieldVoixListe.setText("");
// estado del formulario (menús, etiquetas)
...
}
- línea 25: cada vez que el usuario añade voces a una lista y confirma su elección, dicha lista se introduce en el campo [listesSaisies] de la línea 9. Allí se registrará la lista con la información [id, version, nom, voix]. Los tres primeros datos proceden de las listas registradas inicialmente en la tabla de la línea 6. El método [getSelectedIndex] del cuadro combinado permite conocer el índice de la lista seleccionada;
Tarea: completa el código del método [doAjouter].
10.3.9.4. Gestionar el estado del enlace [Supprimer]
El enlace [Supprimer] [9] solo está activo cuando se selecciona un elemento en [8].
En la clase [AbstractElectionsSwing], el controlador que responde al clic sobre un elemento de la lista [8] es el siguiente:
private void jListNomsVoixValueChanged(javax.swing.event.ListSelectionEvent evt) {
doMajLabelSupprimer();
}
La línea 2 llama al método [doMajLabelSupprimer] de la clase [ElectionsSwing].
@Override
protected void doMajLabelSupprimer() {
// se activa la etiqueta [jLabelSupprimer] y la opción de menú correspondiente
...
}
Tarea: completa el código del método [doMajLabelSupprimer].
10.3.9.5. Eliminar una lista de candidatos
El enlace [Supprimer] [9] permite eliminar el par (nombre, voz) seleccionado en (8). Una vez realizada la eliminación, el enlace [Supprimer] se desactiva. Solo se volverá a activar al seleccionar una nueva lista en (8).
![]() | ![]() |
En la clase [AbstractElectionsSwing], el controlador que responde al hacer clic en el enlace [Supprimer] es el siguiente:
private void jLabelSupprimerMouseClicked(java.awt.event.MouseEvent evt) {
if(jLabelSupprimer.isEnabled()){
doSupprimer();
}
}
La línea 3 llama al método [doSupprimer] de la clase [ElectionsSwing].
@Override
protected void doSupprimer() {
// eliminación de la lista seleccionada, de la plantilla modèleNomsVoix y de las listas introducidas
...
// actualización del estado de las etiquetas y opciones de menú del formulario
Utilitaires.setEnabled(...);
...
}
Tarea: completa el código del método [doSupprimer].
10.3.9.6. Gestionar el estado del enlace [Calculer]
El enlace [Calculer] [10] solo está activo cuando existe al menos un elemento en [8].
Tarea a realizar: añade el código necesario para gestionar este enlace en los métodos [doAjouter] y [doSupprimer] escritos anteriormente. También se gestionará la opción de menú correspondiente.
Nota: el número de elementos de un elemento de tipo DefaultListModel se obtiene con el método size().
10.3.9.7. Calcular los escaños
El enlace [Calculer] [10] permite iniciar el cálculo de escaños y mostrar los resultados en (14). En caso de error (se han descartado todas las listas), se muestra un mensaje de error en [15]. En cualquier caso, tras el cálculo, el enlace [Calculer] [10] queda desactivado.
En la clase [AbstractElectionsSwing], el gestor que responde al hacer clic en el enlace [Calculer] es el siguiente:
private void jLabelCalculerMouseClicked(java.awt.event.MouseEvent evt) {
if(jLabelCalculer.isEnabled()){
doCalculer();
}
}
La línea 3 llama al método [doCalculer] de la clase [ElectionsSwing].
// listas introducidas por el usuario
private final List<ListeElectorale> listesSaisies = new ArrayList<>();
private ListeElectorale[] tListesSaisies;
...
@Override
protected void doCalculer() {
tListesSaisies = listesSaisies.toArray(new ListeElectorale[0]);
// cálculo de los escaños
try {
...
} catch (ElectionsException ex) {
// se muestra la excepción
...
return;
}
// Visualización de los resultados
...
// Actualización del estado del formulario
Utilitaires.setEnabled(...);
}
Tarea: completa el código del método [doCalculer].
10.3.9.8. Guardar los resultados en la fuente de datos
El enlace [Enregistrer] (12) permite guardar los resultados del cálculo de escaños en la fuente de datos. Una vez realizado el registro con éxito, se desactiva el enlace [Enregistrer]. En caso de fallo, se muestra un mensaje de error en [15]. En cualquier caso, a continuación se desactiva el enlace [Enregistrer].
En la clase [AbstractElectionsSwing], el controlador que gestiona el clic en la etiqueta [Enregistrer] es el siguiente:
private void jLabelEnregistrerMouseClicked(java.awt.event.MouseEvent evt) {
if(jLabelEnregistrer.isEnabled()){
doEnregistrer();
}
}
La línea 3 llama al método [doEnregistrer] de la clase [ElectionsSwing]:
@Override
protected void doEnregistrer() {
// se solicita el registro a la capa de negocio
try {
...
} catch (ElectionsException ex) {
// se muestra la excepción
...
// Vuelta a la interfaz gráfica
return;
}
// Actualización del formulario
Utilitaires.setEnabled(...);
...
}
Tarea: completa el código del método [doEnregistrer].
10.3.9.9. Borrar los resultados
El enlace [Effacer] (11) permite borrar los resultados mostrados en (14).
En la clase [AbstractElectionsSwing], el controlador que gestiona el clic en la etiqueta [Effacer] es el siguiente:
private void jLabelEffacerMouseClicked(java.awt.event.MouseEvent evt) {
if(jLabelEffacer.isEnabled()){
doEffacer();
}
}
La línea 3 llama al método [doEffacer] de la clase [ElectionsSwing]:
@Override
protected void doEffacer() {
// Se vacía la lista de resultados
....
// Actualización del formulario
Utilitaires.setEnabled(...);
}
Tarea: completa el código del método [doEffacer].
Nota: la clase DefaultListModel tiene un método clear() que elimina todos sus elementos.
10.3.10. Mejoras
La interfaz gráfica anterior se puede mejorar de diversas formas: el usuario puede, por descuido, no introducir los votos de todas las listas presentes en el menú desplegable y, además, puede, por error, introducir varias veces los votos de una misma lista.
Tarea pendiente: mejora el algoritmo para que estos dos casos no puedan producirse. Una solución sencilla consiste en gestionar un diccionario de las listas introducidas, cuyas claves serían los elementos del menú desplegable. También nos aseguraremos de que el enlace [Calculer] solo esté activo cuando se hayan introducido todas las listas.
Consulte en el curso [ref1]: la clase HashTable en el apartado 3.8.






































