10. [TD]: Implementazione del livello [ui] con un' 'interfaccia Swing
Parole chiave: architettura a più livelli, Spring, iniezione di dipendenze, libreria di componenti Swing.
![]() |
10.1. Support
![]() |
Nel livello [UI], si desidera creare un'interfaccia grafica Swing. NetBeans dispone di uno strumento [Matisse] per la creazione di queste interfacce Swing, che è superiore a quanto offerto da Eclipse. Le interfacce Swing tendono ad essere sostituite da interfacce JavaFx. NetBeans ed Eclipse utilizzano lo stesso strumento per la creazione di queste ultime. Pertanto, se si realizzano interfacce JavaFx, è possibile mantenere Eclipse dall’inizio alla fine dell’architettura a livelli.
NetBeans è in grado di aprire qualsiasi progetto Maven. Utilizzeremo quindi il progetto Maven precedente e vi aggiungeremo un’interfaccia Swing. In [2], carichiamo (File / Apri progetto) i progetti Maven dei tre livelli che abbiamo creato con Eclipse. Successivamente, generiamo i relativi binari [3]. Le opzioni [Build] e [Clean and Build] generano il binario del progetto a cui sono applicate. Questi binari vengono inseriti nella cartella [target] [4-5] del progetto:
![]() |
L'opzione [Clean] elimina questa cartella [target]. L'opzione [Build] la ricostruisce. L’esperienza dimostra che, quando si verificano problemi imprevisti, la prima cosa da fare è eseguire un [Clean and Build] sul progetto per assicurarsi di lavorare con l’ultima versione dello stesso. Ciò è particolarmente necessario quando si hanno file di configurazione che, se modificati, non provocano una ricompilazione automatica all’esecuzione del progetto. È quindi necessario forzare questa ricompilazione tramite un comando [Clean and Build] affinché le nuove versioni vengano installate nella cartella [target].
10.2. Funzionamento dell’applicazione
Torniamo all’architettura generale dell’applicazione [Elections]:
![]() |
Ci concentriamo ora su una nuova implementazione del livello [ui]. L’unica implementazione realizzata finora è un’interfaccia da console. Ora creiamo un’interfaccia grafica.
L’utente disporrà della seguente interfaccia per interagire con l’applicazione [Elections]:
![]() |
![]() |
L'interfaccia grafica si trova nel livello [ui]. È questa che interagisce con l'utente.
- All’avvio, l’applicazione console [main] istanzia i tre livelli dell’applicazione tramite Spring. Ciò avviene prima ancora che l’interfaccia grafica sia visibile. Sempre in questa fase di inizializzazione, le informazioni relative alle elezioni (numero di seggi da assegnare, soglia elettorale, liste in lizza) vengono richieste al livello [dao]. Se questa fase di inizializzazione fallisce (ad esempio, impossibilità di accedere ai dati), viene visualizzato un messaggio di errore sulla console e l’interfaccia grafica non viene visualizzata.
- Se la lettura dei dati è avvenuta correttamente, viene visualizzata l’interfaccia grafica con le seguenti informazioni (cfr. screenshot sopra):
- il numero di posti da assegnare in (2)
- la soglia elettorale di cui al punto (3)
- gli identificativi e i nomi delle liste candidate in (4)
- l'utente assegna quindi a ciascuna lista candidata il numero di voti utilizzando i campi 4 (ID - nome), 5 (voti), 6 (per aggiungere).

- È quindi possibile utilizzare il link (10) per calcolare i seggi:

- il link [Enregistrer] (12) consente di salvare i risultati nella fonte dati.
10.3. La classe di implementazione [ElectionsSwing] del livello [ui]
10.3.1. Il progetto NetBeans
Nota: il paragrafo 22.4 indica come procurarsi NetBeans.
Il progetto NetBeans finale dell'applicazione avrà la seguente forma: [1]. Compilatelo seguendo i passaggi [2-5]:
![]() |
![]() |
Assicurarsi che il progetto sia configurato per essere compilato da un JDK 1.8 [1-6]:
![]() |
10.3.2. Configurazione Maven
Il nuovo progetto [elections-swing-metier-dao-jdbc] si baserà sul precedente progetto [elections-console-metier-dao-jdbc]. A tal fine, si aggiunge una dipendenza Maven come segue [1-3]:
![]() |
![]() |
10.3.3. Realizzazione dell’interfaccia grafica
Per creare l’interfaccia grafica, possiamo procedere come segue:
![]() |
![]() |
- [1]: aggiunta di un oggetto al pacchetto [elections.ui.service]
- [2]: selezione dell'opzione [JFrame Form] nella categoria [Swing GUI Forms]
![]() |
- [4]: assegnazione di un nome alla classe
- [5]: il pacchetto della classe.
- Completare la procedura guidata.
- [6]: la classe generata
![]() |
- [7]: la classe [AbstractElectionsSwing] in modalità [Design]
- [8]: la scheda [Navigator] che mostra l’albero [9] dei componenti della finestra
- [10]: la scheda [Properties] che mostra le proprietà del componente [jFrame] selezionato in [9]
![]() |
- [11]: [JFrame] è un contenitore di componenti. Questi possono essere disposti nel contenitore secondo diverse regole di posizionamento denominate layouts. In questo caso, scegliamo layout [Free Design] [14], che consente di posizionare liberamente i componenti all’interno del contenitore.
Troviamo i componenti nella barra degli strumenti denominata Palette:
![]() |
- [1]: la tavolozza
- [2]: un componente JLabel viene inserito nel contenitore dei componenti
- facendo clic con il tasto destro del mouse su di esso, si ha accesso a diverse proprietà: il suo nome [4], il suo testo [3] o i suoi gestori di eventi [5]. Utilizziamo [3] per impostare il testo [6].
![]() |
- [1]: la scheda [Properties] del componente [JLabel] consente di accedere alle proprietà di quest’ultimo: il suo posizionamento orizzontale [2], verticale [3], il carattere del testo [4], il testo [5].
Quando un componente viene trascinato e configurato nell’interfaccia grafica e si salva (Ctrl-S) il lavoro svolto, viene generato del codice nella classe [AbstractElectionsSwing]:
![]() |
![]() |
Non è necessario modificare questo codice in grigio poiché viene eliminato e rigenerato al successivo salvataggio. Le modifiche apportate andrebbero quindi perse.
È disponibile un tutorial sulla creazione di un'interfaccia grafica con NetBeans all'indirizzo URL [https://netbeans.org/kb/docs/java/quickstart-gui.html?print=yes#design] (novembre 2015).
Ora realizziamo la seguente interfaccia:
![]() |
I componenti dell’interfaccia sono i seguenti:
n. | tipo | nome | ruolo |
JMenuBar | jMenuBar1 | un menu | |
JLabel | jLabelSAP | il numero di seggi da assegnare | |
JLabel | jLabelSE | la soglia elettorale | |
JComboBox | jComboBoxNomsListes | elenco dei nomi delle liste in lizza | |
JTextField | jTextFieldVoixListe | il numero di voti di una lista | |
JLabel | jLabelAjouter | per aggiungere una lista a (8) | |
(JScrollPane, JList) | jListNomsVoix | i nomi e le voci delle liste | |
JLabel | jLabelSupprimer | per rimuovere dalla lista selezionata in (8) | |
JLabel | jLabelCalculer | per calcolare i risultati delle elezioni | |
JLabel | jLabelEffacer | per cancellare i risultati delle elezioni | |
JLabel | jLabelEnregistrer | per registrare i risultati delle elezioni | |
(JScrollPane, JList) | jListResultats | per visualizzare i risultati delle elezioni | |
(JScrollPane, JTextPane) | jTextPaneMessages | per visualizzare i messaggi di follow-up |
L'annotazione (JScrollPane, JList) [13-14] serve a indicare che quando si trascina un componente [JList] nella finestra, esso viene automaticamente inserito in un componente [JScrollPane] che consente lo scorrimento dell'elenco. È il componente [JScrollPane] che consente di visualizzare tutti gli elementi dell'elenco, mentre in un dato momento ne è visibile solo un numero limitato. Lo stesso vale per i componenti [JTextPane] e [15-16].
Il menu potrà essere creato nel modo seguente:
![]() |
- [1, 2]: un componente [Menu Bar] viene inserito nella finestra
- [3]: il menu generato di default come mostrato nella scheda [Navigator]
- [4,5,6]: cliccando con il tasto destro su un'opzione del menu, è possibile:
- modificarne il testo [4], il nome [5]
- gestirne gli eventi [6]
- [7]: il menu desiderato
Il menu desiderato è il seguente:
livello 1 | livello 2 |
Elezioni | |
Quitter | |
Liste | |
Ajouter | |
Supprimer | |
Risultati | |
Calculer | |
Effacer | |
Enregistrer | |
Informazioni |
È possibile provare l'interfaccia grafica in qualsiasi momento:
![]() |
Durante la creazione dell'interfaccia, è necessario associare un gestore di eventi ad alcune etichette e menu [Ajouter, Effacer, ...]. Ecco come procedere:
![]() |
- [1]: fare clic con il tasto destro del mouse sul componente di cui si desidera gestire un evento
- [2]: selezionare l’opzione [Events]
- [3]: selezionare una categoria di eventi
- [4]: selezionare l'evento che si desidera gestire
Il codice Java generato da questa operazione è il seguente:
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 inserisci qui il tuo codice di gestione:
}
- righe 1-5: viene aggiunto un gestore di eventi al componente jLabelCalculer. Il metodo addMouseListener richiede come parametro una classe che implementi la seguente interfaccia MouseListener:
![]() |
L'interfaccia MouseListener è implementata da diverse classi, tra cui la classe MouseAdapter. Quest'ultima implementa i cinque metodi dell'interfaccia MouseListener, ma tali metodi non eseguono alcuna operazione. È quindi necessario derivare questa classe per implementare il metodo o i metodi desiderati. Ciò viene fatto nel codice sopra riportato e ripreso di seguito:
jLabelCalculer.addMouseListener(new java.awt.event.MouseAdapter() {
public void mouseClicked(java.awt.event.MouseEvent evt) {
jLabelCalculerMouseClicked(evt);
}
});
Il codice sopra riportato utilizza la tecnica della classe anonima illustrata nel paragrafo 2.5 del corso [ref1].
Riga 1: il parametro del metodo addMouseListener è una classe anonima, definita al volo. Si tratta di un'istanza di una classe derivata dalla classe MouseAdapter (riga 1), di cui si ridefinisce il metodo mouseClicked (righe 2-4) affinché esegua un'operazione.
Il metodo jLabelCalculerMouseClicked, denominato “riga 3”, è definito come segue:
private void jLabelCalculerMouseClicked(java.awt.event.MouseEvent evt) {
// TODO inserisci qui il tuo codice di gestione:
}
Lo sviluppatore gestisce l'evento "MouseClicked" inserendo del codice in questo metodo.
Tutti i gestori di eventi vengono generati da NetBeans in questo modo. Lo sviluppatore può ignorare le righe di codice generate da NetBeans per associare un metodo a un evento di un componente. Può semplicemente inserire il proprio codice nella riga 2 sopra riportata. Ecco un esempio:
private void jLabelCalculerMouseClicked(java.awt.event.MouseEvent evt) {
System.out.println("Mouse Clicked");
}
Se si esegue l’interfaccia grafica e si fa clic sul collegamento [Calculer], viene visualizzato un messaggio sulla console:
![]() |
- [1]: si fa doppio clic sull’etichetta [Calculer]
- [2]: il gestore di questo evento è stato eseguito e ha generato i messaggi mouseClicked nella console di NetBeans.
I componenti [jComboBoxNomsListes, jListNomsVoix, jListResultats] sono dichiarati come segue:
protected javax.swing.JComboBox jComboBoxNomsListes;
protected javax.swing.JList jListNomsVoix;
protected javax.swing.JList jListResultats;
Questi componenti sono elenchi che normalmente sono configurati con un tipo T: il tipo degli elementi del modello visualizzato dai componenti. Questo tipo T può essere qualsiasi. Il valore visualizzato nel componente elenco è di tipo [String]. Per impostazione predefinita, per la visualizzazione viene quindi utilizzato il metodo [T.toString()]. Per avere un maggiore controllo su ciò che verrà visualizzato, il tipo T sarà in questo caso il tipo String. Pertanto, la dichiarazione corretta dei nostri elenchi è la seguente:
protected javax.swing.JComboBox<String> jComboBoxNomsListes;
protected javax.swing.JList<String> jListNomsVoix;
protected javax.swing.JList<String> jListResultats;
Si ottiene questo risultato modificando una delle proprietà del componente:
![]() |
10.3.4. Separazione del codice
Torniamo alla struttura della nostra applicazione:
![]() |
La classe [AbstractElectionsSwing] deve implementare il livello [ui] sopra indicato. Il suo codice generato da NetBeans contiene per ora solo codice di gestione della finestra e gestori di eventi che, al momento, non svolgono alcuna funzione. Come si vede sopra, la classe [AbstractElectionsSwing] dovrà gestire gli scambi con il livello [métier]. Tale gestione avverrà all’interno dei gestori di eventi. Per chiarire la struttura del codice, si decide di suddividerlo in due classi:
- [AbstractElectionsSwing], che rimarrà così come è stata generata da NetBeans, salvo qualche piccolo dettaglio. Questa classe non gestirà direttamente alcun evento. I gestori di eventi saranno vuoti e dichiarati astratti. Saranno implementati da una classe derivata da [AbstractElectionsSwing].
- [ElectionsSwing], classe derivata da [AbstractElectionsSwing], che implementerà tutti i gestori di eventi.
Questo tipo di separazione non è insolito. Lo si trova, ad esempio, nelle pagine web ASP.NET (versione non MVC). Il progetto NetBeans si evolve come segue:
![]() |
Il codice della classe [AbstractElectionsSwing], invece, si evolve come segue:
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();
}
}
....
// gestori di eventi
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();
...
}
- riga 1: la classe è dichiarata astratta
- righe 3-5: gestione del clic sull'opzione di menu [jMenuItemCalculer]. Si nota che la gestione dell’evento viene delegata al metodo doCalculer alla riga 19. Questo metodo non è implementato ed è dichiarato astratto. Sarà la classe derivata [ElectionsSwing] a implementarlo;
- righe 9-13: il gestore dell’evento clic sull’etichetta [jLabelCalculer]. Il clic genera sempre un evento, indipendentemente dal fatto che il componente [jLabel] sia attivo (enabled=true) o inattivo (enabled=false). Qui ci si assicura che sia effettivamente attivo per gestire l’evento;
- righe 15 e successive: questa tecnica di delega della gestione degli eventi a un metodo astratto viene applicata a tutti i gestori di eventi.
La classe [ElectionsSwing], derivata da [AbstractElectionsSwing], implementa tutti i gestori di eventi non implementati da [AbstractElectionsSwing]:
package elections.ui.service;;
...
public class ElectionsSwing extends AbstractElectionsSwing {
// gestori di eventi
@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() {
...
}
}
- riga 3: [ElectionsSwing] deriva da [AbstractElectionsSwing]
- righe 7-50: i gestori degli eventi della finestra grafica
I metodi della classe derivata [ElectionsSwing] gestiranno i componenti della classe padre [AbstractElectionsSwing]. Attualmente questi componenti hanno un ambito private, impedendo alla classe figlia [ElectionsSwing] di accedervi:
private JMenuItem jMenuItemAPropos = null;
private JLabel jLabelAjouter = null;
Per risolvere questo problema, faremo in modo che l’ambito dei componenti dell’interfaccia grafica sia [protected]:
![]() |
- impostare l'attributo [protected] su [3];
10.3.5. Implementazione dell'interfaccia [IElectionsUI]
Torniamo alla struttura della nostra applicazione:
![]() |
Come illustrato sopra, il livello [ui] deve presentare l’interfaccia [IElectionsUI] all’oggetto [main]:
package elections.ui.service;
public interface IElectionsUI {
/**
* lance le dialogue avec l'utilisateur
*/
public void run();
}
Tale interfaccia è stata definita nel progetto [elections-console-metier-dao-jdbc] e descritta al paragrafo 9.4. Poiché questo progetto è una dipendenza del progetto [swing], tale interfaccia è nota.
Poiché la classe [AbstractElectionsSwing] è diventata astratta, non può più essere istanziata da Spring. D'ora in poi dovrà essere istanziata la classe [ElectionsSwing]. La classe [ElectionsSwing] deve implementare l’interfaccia [IElectionsUI]. Il suo codice viene quindi modificato come segue:
public class ElectionsSwing extends AbstractElectionsSwing implements IElectionsUI {
// metodo run dell'interfaccia [ElectionsUI]
public void run() {
...
}
- riga 1: la classe [ElectionsSwing] implementa l'interfaccia [IElectionsUI]
- righe 4-6: il metodo [run] di questa interfaccia
Cosa deve fare il metodo run? Visualizzare la finestra grafica. Come si fa? Possiamo prendere spunto dal metodo [main] generato da NetBeans nella classe [AbstractElectionsSwing], che fa proprio ciò che desideriamo:
public static void main(String args[]) {
/* Imposta l'aspetto grafico di Nimbus */
//<editor-fold defaultstate="collapsed" desc="Codice di configurazione dell'aspetto grafico (facoltativo)">
/* Se Nimbus (introdotto in Java SE 6) non è disponibile, mantenere l'aspetto predefinito.
* 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>
/* Creazione e visualizzazione del modulo */
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
new NewJFrame().setVisible(true);
}
});
}
Il costruttore [AbstractElectionsSwing] utilizzato alla riga 28 è il seguente:
public AbstractElectionsSwing() {
initComponents();
}
- riga 2: il metodo [initComponents] è un metodo privato generato dal generatore dell'interfaccia grafica. Non è possibile modificarne il codice.
Il metodo [run] della classe [ElectionsSwing] potrebbe quindi essere il seguente:
@Override
public void run() {
// visualizzazione dell'interfaccia grafica
SwingUtilities.invokeLater(new Runnable() {
public void run() {
init();
setVisible(true);
}
});
}
- riga 6: l'interfaccia grafica viene inizializzata tramite il metodo [init]. In questo caso, si vorrebbe chiamare il metodo [initComponents] della classe padre, ma questo è privato. Si aggiunge quindi alla classe padre [AbstractElectionsSwing] il seguente metodo [init]:
protected void init(){
initComponents();
}
- (continua)
- poiché si trova nella classe [AbstractElectionsSwing], il metodo [init] ha accesso al metodo privato [initComponents] della stessa classe;
- poiché possiede l'attributo [protected], è visibile nella classe figlia [ElectionsSwing];
- riga 7: l'interfaccia grafica viene resa visibile;
Nota: una volta che il metodo [run] è stato scritto nella classe [ElectionsSwing], il metodo [main] della classe astratta [AbstractElectionsSwing] può essere eliminato.
10.3.6. La classe eseguibile
Torniamo alla struttura della nostra applicazione:
![]() |
Vorremmo che Spring istanziasse il livello [ui] come avveniva quando era implementato dall’applicazione console. A tal fine, è necessario che la classe di implementazione [ElectionsSwing] contenga un riferimento al livello [métier]:
@Component
public class ElectionsSwing extends AbstractElectionsSwing implements IElectionsUI{
// riferimento al livello [métier]
@Autowired
private IElectionsMetier metier;
...
- riga 1: la classe [ElectionsSwing] è un componente Spring;
- righe 5-6: iniezione da parte di Spring di un riferimento al livello [métier];
L'interfaccia grafica viene avviata dall'esecuzione della seguente classe [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);
}
}
Abbiamo illustrato un codice analogo nel paragrafo 9.5, quando abbiamo spiegato il codice delle classi [AbstractBootElections] e [BootElectionsConsole]. Alla riga 12, viene recuperato il bean denominato [electionsSwing], che corrisponde al nome standard Spring per la classe [ElectionsSwing].
10.3.7. Inizializzazione dell’interfaccia grafica
Quando viene visualizzata l’interfaccia grafica, alcuni dei suoi componenti sono già stati inizializzati:

Come si vede sopra:
- che il menu a tendina è stato popolato con i nomi delle liste;
- che il numero di seggi da assegnare e la soglia elettorale sono indicati;
- che alcuni link sono stati disattivati;
- che nella parte inferiore della finestra viene visualizzato un messaggio di conferma;
Quando avverranno queste inizializzazioni? Possono avvenire solo dopo l’inizializzazione del campo [electionsMetier] della classe [ElectionsSwing]. Infatti, i nomi delle liste verranno richiesti al livello [metier]. L'inizializzazione di questo campo verrà effettuata da Spring nel seguente ordine:
- utilizzo del costruttore senza parametri della classe [ElectionsSwing];
- iniezione delle dipendenze, in questo caso il riferimento al livello [métier];
- esecuzione del metodo [run] della classe [ElectionsSwing]:
@Override
public void run() {
// viene visualizzata l'interfaccia grafica
SwingUtilities.invokeLater(new Runnable() {
public void run() {
init();
setVisible(true);
}
});
}
- alla riga 12, abbiamo specificato di richiamare il metodo [init] della classe padre che disegnerà i componenti dell'interfaccia grafica. Ridefiniremo questo metodo, a livello locale, nella classe [ElectionsSwing]. È in questo metodo che inizializzeremo, questa volta con dei dati, i componenti della finestra (combo box, etichette):
Il metodo locale [init] potrebbe avere la seguente struttura:
public class ElectionsSwing extends AbstractElectionsSwing implements IElectionsUI {
...
// riferimento al livello [métier]
@Autowired
private IElectionsMetier metier;
// inizializzazioni
public void init() {
// generazione dei componenti da parte della classe padre
super.init();
// vengono richieste le liste al livello [metier]
...
// si associano i nomi delle liste al menu a tendina jComboBoxNomsListes
...
// insieme ai parametri dell'elezione
...
// si inizializzano le etichette relative a queste due informazioni
...
// messaggio di esito positivo
...
// inizializzazione dello stato di alcuni componenti del modulo
...
}
Si noti, alla riga 11, la chiamata al metodo [init] della classe padre.
10.3.8. La classe [Utilitaires]
Nella classe [Utilitaires] sono stati raggruppati alcuni metodi di utilità statici:
![]() |
La classe [Utilitaires] è la seguente:
package istia.st.elections.ui;
import javax.swing.JLabel;
import javax.swing.JMenuItem;
//classe di utilità
class Utilitaires {
// gestire lo stato di un array di etichette
public static void setEnabled(JLabel[] labels, boolean value) {
for (int i = 0; i < labels.length; i++) {
labels[i].setEnabled(value);
}
}
// gestire lo stato di un array di opzioni di menu
public static void setEnabled(JMenuItem[] menuItems, boolean value) {
...
}
}
- riga 9: il metodo setEnabled imposta lo stato dei componenti JLabel definiti in un array. Il metodo setEnabled di un componente JLabel consente di attivare o disattivare il JLabel.
Compito da svolgere: seguendo l'esempio del metodo setEnabled della riga 9 (,), scrivi il metodo setEnabled della riga 16 che svolge la stessa funzione con i componenti JMenuItem.
10.3.9. Il codice della classe [ElectionsSwing]
Ricordiamo la struttura generale della classe [ElectionsSwing]:
package istia.st.elections.ui;
...
public class ElectionsSwing extends AbstractElectionsSwing implements IElectionsUI {
// riferimento al livello [métier]
@Autowired
private IElectionsMetier metier;
// inizializzazioni
public void init() {
...
}
// gestori di eventi
@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() {
...
}
}
Esamineremo i metodi della classe uno dopo l’altro.
10.3.9.1. Il metodo [init]
Torniamo all’interfaccia grafica:
![]() |
Il metodo [init] ha i seguenti obiettivi:
- compilare il campo combinato [4] con gli identificativi e i nomi delle liste nel formato [id - nom]
- visualizzare un messaggio di conferma in [15]
- inizializzare le etichette [2] e [3]
- disattivare alcuni link
Lo scheletro del metodo [init] potrebbe essere il seguente:
@Override
protected void init() {
// generazione dei componenti da parte della classe padre
super.init();
// inizializzazioni locali
modèleNomsVoix = new DefaultListModel<>();
jListNomsVoix.setModel(modèleNomsVoix);
modèleRésultats = new DefaultListModel<>();
jListResultats.setModel(modèleRésultats);
String info;
try {
// si richiedono gli elenchi al livello [métier]
listes = ...
// si associano i nomi delle liste al menu a tendina jComboBoxNomsListes
...
// insieme ai parametri dell'elezione
int nbSiegesAPourvoir = ...
double seuilElectoral = ...
// si inizializzano le etichette relative a queste due informazioni
...
// messaggio di esito positivo
info = "Source de données lue avec succès";
} catch (ElectionsException ex1) {
// viene segnalato l'errore
info = getInfoForException("Les erreurs suivantes se sont produites :", ex1);
} catch (RuntimeException ex2) {
// si registra l'errore
info = getInfoForException("Les erreurs suivantes se sont produites :", ex2);
}
// viene visualizzata l'informazione
jTextPaneMessages.setText(info);
jTextPaneMessages.setCaretPosition(0);
// stato del modulo
Utilitaires.setEnabled(new JLabel[] { jLabelAjouter, jLabelCalculer, jLabelEnregistrer, jLabelSupprimer }, false);
Utilitaires.setEnabled(
new JMenuItem[] { jMenuItemAjouter, jMenuItemCalculer, jMenuItemEnregistrer, jMenuItemSupprimer }, false);
// centrare la finestra
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) {
// visualizza il messaggio
StringBuffer info = new StringBuffer(String.format("%s -------------\n", message));
info.append(String.format("Code erreur : %d\n", ex.getCode()));
// vengono visualizzati gli errori
for (String erreur : ex.getErreurs()) {
info.append(String.format("-- %s\n", erreur));
}
return info.toString();
}
private String getInfoForException(String message, RuntimeException ex) {
// visualizza il messaggio
StringBuffer info = new StringBuffer(String.format("%s -------------\n", message));
// visualizza la pila delle eccezioni
Throwable cause = ex;
while (cause != null) {
info.append(String.format("-- %s\n", cause.getMessage()));
cause = cause.getCause();
}
return info.toString();
}
Compito da svolgere: completare il codice del metodo [init].
Da leggere nel corso: componenti JTextField, JLabel
Da sapere:
Un componente JList visualizza i dati presenti in un modello. Per impostazione predefinita, questo modello è di tipo DefaultListModel (righe 2 e 3). Un oggetto di tipo DefaultListModel si comporta in modo simile a un oggetto di tipo ArrayList:
- per aggiungere un oggetto o nel modello:
[DefaultListModel].addElement(Object o);
In questa applicazione, l'oggetto o sarà sempre di tipo String.
- per rimuovere l'elemento n. i dal modello:
[DefaultListModel].remove(int i);
- Per ottenere l'elemento n. i del modello:
[DefaultListModel].elementAt(int i);
Per aggiungere un elemento al combo [jComboBoxNomsListes], si utilizzerà il metodo [addItem]:
jComboBoxNomsListes.addItem(chaîne de caractères)
Un componente JTextPane dispone dei metodi getText() e setText() per leggere/scrivere il testo visualizzato.
10.3.9.2. Gestire lo stato del collegamento [Ajouter]
Il collegamento [Ajouter] [6] è attivo solo quando il campo [5] delle voci non è vuoto. Nella classe [AbstractElectionsSwing], il gestore che segue i movimenti del cursore nel campo [5] è il seguente:
private void jTextFieldVoixListeCaretUpdate(javax.swing.event.CaretEvent evt) {
doMajLabelAjouter()
}
La riga 2 chiama il metodo [doMajLabelAjouter] della classe [ElectionsSwing].
protected void doMajLabelAjouter() {
// imposta lo stato dell'etichetta [jLabelAjouter]
...
// si imposta lo stato del menu [jMenuItemAjouter]
...
}
Compito da svolgere: completare il codice del metodo [doMajLabelAjouter].
10.3.9.3. Assegnare i voti a ciascuna lista
Per ogni elenco candidato di (4), si procede come segue:
- scelta di una lista in (4)
- inserimento del numero di voti in (5)
- conferma cliccando sul link [Ajouter]
Gli errori di inserimento vengono segnalati come illustrato nell’esempio seguente:

Se il numero di voti è corretto, la lista viene aggiunta nel componente (8), il numero di voti viene cancellato e il link [Ajouter] viene disattivato:
![]() | ![]() |
Nella classe [AbstractElectionsSwing], il gestore che gestisce il clic sul link [Ajouter] è il seguente:
private void jLabelAjouterMouseClicked(java.awt.event.MouseEvent evt) {
if (jLabelAjouter.isEnabled()) {
doAjouter();
}
}
La riga 3 chiama il metodo [doAjouter] della classe [ElectionsSwing]:
// modelli delle liste JList
private DefaultListModel<String> modèleNomsVoix = null;
private DefaultListModel<String> modèleRésultats = null;
// gli elenchi in competizione
private ListeElectorale[] listes;
// liste inserite dall'utente
private final List<ListeElectorale> listesSaisies = new ArrayList<>();
private ListeElectorale[] tListesSaisies;
...
@Override
protected void doAjouter() {
// il numero di voti è corretto?
...
// in caso di errore, lo si segnala
if (erreur) {
JOptionPane.showMessageDialog(null, "Nombre de voix incorrect", "Elections : erreur",
JOptionPane.INFORMATION_MESSAGE);
jTextFieldVoixListe.requestFocus();
// ritorno all'interfaccia grafica
return;
}
// nessun errore - si salva l'elenco
listesSaisies.add(...);
modèleNomsVoix.addElement(...);
// si azzera il numero di voti
jTextFieldVoixListe.setText("");
// stato del modulo (menu, etichette)
...
}
- riga 25: ogni volta che l’utente aggiunge voci a un elenco e conferma la propria scelta, tale elenco viene inserito nel campo [listesSaisies] della riga 9. Qui verrà registrato l’elenco con le informazioni [id, version, nom, voix]. Le prime tre informazioni provengono dagli elenchi inizialmente memorizzati nella tabella della riga 6. Il metodo [getSelectedIndex] del menu a tendina consente di conoscere l'indice dell'elenco selezionato;
Compito da svolgere: completare il codice del metodo [doAjouter].
10.3.9.4. Gestire lo stato del collegamento [Supprimer]
Il collegamento [Supprimer] [9] è attivo solo quando viene selezionato un elemento in [8].
Nella classe [AbstractElectionsSwing], il gestore che risponde al clic su un elemento dell'elenco [8] è il seguente:
private void jListNomsVoixValueChanged(javax.swing.event.ListSelectionEvent evt) {
doMajLabelSupprimer();
}
La riga 2 chiama il metodo [doMajLabelSupprimer] della classe [ElectionsSwing].
@Override
protected void doMajLabelSupprimer() {
// si attiva l'etichetta [jLabelSupprimer] e la corrispondente opzione di menu
...
}
Compito da svolgere: completare il codice del metodo [doMajLabelSupprimer].
10.3.9.5. Eliminare un elenco di candidati
Il collegamento [Supprimer] [9] consente di eliminare la coppia (nome, voce) selezionata in (8). Una volta effettuata l’eliminazione, il collegamento [Supprimer] viene disattivato. Verrà riattivato solo dopo aver selezionato un nuovo elenco in (8).
![]() | ![]() |
Nella classe [AbstractElectionsSwing], il gestore che risponde al clic sul collegamento [Supprimer] è il seguente:
private void jLabelSupprimerMouseClicked(java.awt.event.MouseEvent evt) {
if(jLabelSupprimer.isEnabled()){
doSupprimer();
}
}
La riga 3 chiama il metodo [doSupprimer] della classe [ElectionsSwing].
@Override
protected void doSupprimer() {
// eliminazione dell'elenco selezionato, del modello modèleNomsVoix e degli elenchi inseriti
...
// aggiornamento dello stato delle etichette e delle opzioni di menu del modulo
Utilitaires.setEnabled(...);
...
}
Compito da svolgere: completare il codice del metodo [doSupprimer].
10.3.9.6. Gestire lo stato del collegamento [Calculer]
Il collegamento [Calculer] [10] è attivo solo quando esiste almeno un elemento in [8].
Compito da svolgere: aggiungere il codice necessario alla gestione di questo collegamento nei metodi [doAjouter] e [doSupprimer] scritti in precedenza. Verrà gestita anche la relativa opzione di menu.
Nota: il numero di elementi di un elemento di tipo DefaultListModel si ottiene con il metodo size().
10.3.9.7. Calcolo dei seggi
Il collegamento [Calculer] [10] consente di avviare il calcolo dei seggi e di visualizzare i risultati in (14). In caso di errore (tutte le liste sono state eliminate), viene visualizzato un messaggio di errore in [15]. In ogni caso, al termine del calcolo, il collegamento [Calculer] [10] viene disattivato.
Nella classe [AbstractElectionsSwing], il gestore che risponde al clic sul collegamento [Calculer] è il seguente:
private void jLabelCalculerMouseClicked(java.awt.event.MouseEvent evt) {
if(jLabelCalculer.isEnabled()){
doCalculer();
}
}
La riga 3 chiama il metodo [doCalculer] della classe [ElectionsSwing].
// elenchi inseriti dall'utente
private final List<ListeElectorale> listesSaisies = new ArrayList<>();
private ListeElectorale[] tListesSaisies;
...
@Override
protected void doCalculer() {
tListesSaisies = listesSaisies.toArray(new ListeElectorale[0]);
// calcolo dei seggi
try {
...
} catch (ElectionsException ex) {
// viene visualizzata l'eccezione
...
return;
}
// visualizzazione dei risultati
...
// aggiornamento dello stato del modulo
Utilitaires.setEnabled(...);
}
Compito da svolgere: completare il codice del metodo [doCalculer].
10.3.9.8. Salvare i risultati nella fonte dati
Il collegamento [Enregistrer] (12) consente di salvare i risultati del calcolo dei posti nella fonte dati. Una volta completata con successo la registrazione, il collegamento [Enregistrer] viene disattivato. In caso di errore, viene visualizzato un messaggio di errore in [15]. In ogni caso, il collegamento [Enregistrer] viene successivamente disattivato.
Nella classe [AbstractElectionsSwing], il gestore che gestisce il clic sull’etichetta [Enregistrer] è il seguente:
private void jLabelEnregistrerMouseClicked(java.awt.event.MouseEvent evt) {
if(jLabelEnregistrer.isEnabled()){
doEnregistrer();
}
}
La riga 3 richiama il metodo [doEnregistrer] della classe [ElectionsSwing]:
@Override
protected void doEnregistrer() {
// richiesta di salvataggio al livello applicativo
try {
...
} catch (ElectionsException ex) {
// viene visualizzata l'eccezione
...
// ritorno all'interfaccia grafica
return;
}
// aggiornamento del modulo
Utilitaires.setEnabled(...);
...
}
Compito da svolgere: completare il codice del metodo [doEnregistrer].
10.3.9.9. Cancella i risultati
Il collegamento [Effacer] (11) consente di cancellare i risultati visualizzati in (14).
Nella classe [AbstractElectionsSwing], il gestore che gestisce il clic sull'etichetta [Effacer] è il seguente:
private void jLabelEffacerMouseClicked(java.awt.event.MouseEvent evt) {
if(jLabelEffacer.isEnabled()){
doEffacer();
}
}
La riga 3 richiama il metodo [doEffacer] della classe [ElectionsSwing]:
@Override
protected void doEffacer() {
// si svuota l'elenco dei risultati
....
// aggiornamento del modulo
Utilitaires.setEnabled(...);
}
Compito da svolgere: completare il codice del metodo [doEffacer].
Nota: la classe DefaultListModel dispone di un metodo clear() che elimina tutti i suoi elementi.
10.3.10. Miglioramenti
L'interfaccia grafica precedente può essere migliorata in vari modi: l'utente potrebbe, per dimenticanza, non inserire le voci di tutti gli elenchi presenti nel menu a tendina e, inoltre, potrebbe, per errore, inserire più volte le voci dello stesso elenco.
Lavoro da svolgere: migliorare l’algoritmo in modo che questi due casi non possano verificarsi. Una soluzione semplice consiste nel gestire un dizionario delle liste inserite, le cui chiavi sarebbero gli elementi del menu a tendina. Si farà inoltre in modo che il collegamento [Calculer] sia attivo solo quando tutte le liste sono state inserite.
Da leggere nel corso [ref1]: la classe HashTable al paragrafo 3.8.






































