10. [TD]: Implementierung der Schicht [ui] mit einer Swing-Schnittstellen
Stichworte: mehrschichtige Architektur, Spring, Dependency Injection, Swing-Komponentenbibliothek.
![]() |
10.1. Support
![]() |
In der Schicht [UI] soll eine Swing-Benutzeroberfläche erstellt werden. NetBeans verfügt über ein Tool [Matisse] zum Erstellen dieser Swing-Oberflächen, das den Möglichkeiten von Eclipse überlegen ist. Swing-Oberflächen werden zunehmend durch JavaFx-Oberflächen ersetzt. NetBeans und Eclipse verwenden dasselbe Tool, um letztere zu erstellen. Wenn man also JavaFx-Oberflächen erstellt, kann man Eclipse durchgehend in der Schichtenarchitektur beibehalten.
NetBeans kann jedes Maven-Projekt öffnen. Wir werden daher das vorherige Maven-Projekt verwenden und ihm eine Swing-Oberfläche hinzufügen. In [2] laden wir (Datei / Projekt öffnen) die Maven-Projekte der drei Schichten, die wir mit Eclipse erstellt haben. Anschließend erstellen wir deren Binärdateien [3]. Die Optionen [Build] und [Clean and Build] erstellen die Binärdatei des Projekts, auf das sie angewendet werden. Diese Binärdateien werden im Ordner [target] [4-5] des Projekts abgelegt:
![]() |
Die Option [Clean] löscht diesen Ordner [target]. Die Option [Build] erstellt ihn neu. Die Erfahrung zeigt, dass man bei unerwarteten Problemen als Erstes einen Befehl [Clean and Build] für das Projekt ausführen sollte, um sicherzustellen, dass man tatsächlich mit der neuesten Version des Projekts arbeitet. Dies ist insbesondere dann erforderlich, wenn Konfigurationsdateien vorhanden sind, deren Änderung bei der Ausführung des Projekts keine automatische Neukompilierung auslöst. Man muss diese Neukompilierung dann durch einen [Clean and Build] erzwingen, damit die neuen Versionen im Ordner [target] installiert werden.
10.2. Funktionsweise der Anwendung
Kehren wir zur Gesamtarchitektur der Anwendung [Elections] zurück:
![]() |
Wir befassen uns nun mit einer neuen Implementierung der Schicht [ui]. Die einzige bisher realisierte Implementierung ist eine Konsolenoberfläche. Wir erstellen nun eine grafische Benutzeroberfläche.
Dem Benutzer steht die folgende Benutzeroberfläche zur Interaktion mit der Anwendung [Elections] zur Verfügung:
![]() |
![]() |
Die grafische Benutzeroberfläche befindet sich in der Schicht [ui]. Sie ist für die Interaktion mit dem Benutzer zuständig.
- Beim Start instanziiert die Konsolenanwendung [main] mithilfe von Spring die drei Schichten der Anwendung. Dies geschieht, noch bevor die grafische Benutzeroberfläche sichtbar ist. Ebenfalls in dieser Initialisierungsphase werden die für die Wahl charakteristischen Informationen (Anzahl der zu besetzenden Sitze, Wahlhürde, teilnehmende Listen) von der Schicht [dao] angefordert. Sollte diese Initialisierungsphase fehlschlagen (z. B. weil kein Zugriff auf die Daten möglich ist), wird eine Fehlermeldung auf der Konsole angezeigt und die grafische Benutzeroberfläche wird nicht angezeigt.
- Wenn das Einlesen der Daten erfolgreich war, wird die grafische Benutzeroberfläche mit den folgenden Informationen angezeigt (siehe Screenshot oben):
- die Anzahl der zu besetzenden Sitze in (2)
- die Wahlhürde in (3)
- die Kennungen und Namen der Kandidatenlisten in (4)
- Der Benutzer weist dann jeder Kandidatenliste mithilfe der Felder 4 (ID – Name), 5 (Stimmen) und 6 (Hinzufügen) die entsprechende Stimmenzahl zu.

- Anschließend kann man den Link (10) verwenden, um die Sitze zu berechnen:

- Über den Link [Enregistrer] (12) können die Ergebnisse in der Datenquelle gespeichert werden.
10.3. Die Implementierungsklasse [ElectionsSwing] der Schicht [ui]
10.3.1. Das NetBeans-Projekt
Hinweis: In Abschnitt 22.4 wird beschrieben, wie Sie NetBeans beziehen können.
Das endgültige NetBeans-Projekt der Anwendung hat die folgende Form: [1]. Erstellen Sie es, indem Sie die Schritte unter [2-5] befolgen:
![]() |
![]() |
Stellen Sie sicher, dass das Projekt so konfiguriert ist, dass es mit JDK 1.8 und [1-6] kompiliert werden kann:
![]() |
10.3.2. Maven-Konfiguration
Das neue Projekt [elections-swing-metier-dao-jdbc] baut auf dem vorherigen Projekt [elections-console-metier-dao-jdbc] auf. Dazu fügen wir wie folgt eine Maven-Abhängigkeit hinzu: [1-3]:
![]() |
![]() |
10.3.3. Erstellung der grafischen Benutzeroberfläche
Um die grafische Benutzeroberfläche zu erstellen, können wir wie folgt vorgehen:
![]() |
![]() |
- [1]: Hinzufügen eines Objekts zum Paket [elections.ui.service]
- [2]: Auswahl der Option [JFrame Form] in der Kategorie [Swing GUI Forms]
![]() |
- [4]: Benennung der Klasse
- [5]: Das Paket der Klasse.
- Beenden Sie den Assistenten.
- [6]: Die generierte Klasse
![]() |
- [7]: Die Klasse [AbstractElectionsSwing] im Modus [Design]
- [8]: Die Registerkarte [Navigator], die den Baum [9] der Komponenten des Fensters anzeigt
- [10]: Die Registerkarte [Properties], die die Eigenschaften der in [9] ausgewählten Komponente [jFrame] anzeigt
![]() |
- [11]: [JFrame] ist ein Komponenten-Container. Diese Komponenten können nach verschiedenen Positionierungsregeln, die als layouts bezeichnet werden, im Container angeordnet werden. Hier wählen wir die Option layout [Free Design] [14], mit der Komponenten frei im Container positioniert werden können.
Wir finden die Komponenten in der Symbolleiste mit der Bezeichnung Palette:
![]() |
- [1]: die Palette
- [2]: Eine Komponente JLabel wird in den Komponenten-Container abgelegt
- Mit einem Rechtsklick darauf erhält man Zugriff auf verschiedene Eigenschaften: ihren Namen [4], ihren Text [3] oder ihre Ereignisbehandler [5]. Wir verwenden [3], um den Text [6] festzulegen.
![]() |
- [1]: Über die Registerkarte [Properties] der Komponente [JLabel] kann auf deren Eigenschaften zugegriffen werden: ihre horizontale Ausrichtung [2], die vertikale Ausrichtung [3], die Schriftart des Textes [4] sowie den Text [5].
Wenn eine Komponente auf der grafischen Benutzeroberfläche abgelegt und konfiguriert wird und die geleistete Arbeit gespeichert wird (Strg-S), wird Code in der Klasse [AbstractElectionsSwing] generiert:
![]() |
![]() |
Dieser ausgegraute Code darf nicht geändert werden, da er beim nächsten Speichern gelöscht und neu generiert wird. Vorgenommene Änderungen gingen dann verloren.
Eine Anleitung zur Erstellung einer grafischen Benutzeroberfläche mit NetBeans finden Sie unter URL und [https://netbeans.org/kb/docs/java/quickstart-gui.html?print=yes#design] (November 2015).
Wir erstellen nun die folgende Benutzeroberfläche:
![]() |
Die Komponenten der Benutzeroberfläche sind folgende:
Nr. | Typ | Name | Rolle |
JMenuBar | jMenuBar1 | ein Menü | |
JLabel | jLabelSAP | die Anzahl der zu besetzenden Sitze | |
JLabel | jLabelSE | die Wahlhürde | |
JComboBox | jComboBoxNomsListes | Liste der Namen der teilnehmenden Listen | |
JTextField | jTextFieldVoixListe | die Anzahl der Stimmen einer Liste | |
JLabel | jLabelAjouter | um eine Liste zu (8) hinzuzufügen | |
(JScrollPane, JList) | jListNomsVoix | die Namen und Stimmen der Listen | |
JLabel | jLabelSupprimer | um die in (8) ausgewählte Liste aus (8) zu löschen | |
JLabel | jLabelCalculer | um die Wahlergebnisse zu berechnen | |
JLabel | jLabelEffacer | zum Löschen der Wahlergebnisse | |
JLabel | jLabelEnregistrer | um die Wahlergebnisse zu speichern | |
(JScrollPane, JList) | jListResultats | zur Anzeige der Wahlergebnisse | |
(JScrollPane, JTextPane) | jTextPaneMessages | um Follow-up-Meldungen anzuzeigen |
Die Anmerkung (JScrollPane, JList) [13-14] dient dazu, anzuzeigen, dass beim Ablegen einer Komponente [JList] im Fenster diese automatisch in eine Komponente [JScrollPane] eingefügt wird, die das Scrollen durch die Liste ermöglicht. Die Komponente [JScrollPane] ermöglicht es, alle Elemente der Liste anzuzeigen, während zu einem bestimmten Zeitpunkt nur eine begrenzte Anzahl davon sichtbar ist. Gleiches gilt für die Komponenten [JTextPane] und [15-16].
Das Menü kann wie folgt erstellt werden:
![]() |
- [1, 2]: Eine Komponente [Menu Bar] wird im Fenster abgelegt
- [3]: Das standardmäßig generierte Menü, wie es auf der Registerkarte [Navigator] dargestellt ist
- [4,5,6]: Durch einen Rechtsklick auf eine Menüoption kann man:
- den Text [4] und den Namen [5] ändern
- die zugehörigen Ereignisse verwalten [6]
- [7]: Das gewünschte Menü
Das gewünschte Menü sieht wie folgt aus:
Ebene 1 | Ebene 2 |
Wahlen | |
Quitter | |
Listen | |
Ajouter | |
Supprimer | |
Ergebnisse | |
Calculer | |
Effacer | |
Enregistrer | |
Über |
Die grafische Benutzeroberfläche kann jederzeit getestet werden:
![]() |
Beim Erstellen der Benutzeroberfläche muss bestimmten Beschriftungen und Menüs ein Ereignis-Handler zugewiesen werden: [Ajouter, Effacer, ...]. So geht’s:
![]() |
- [1]: Klicken Sie mit der rechten Maustaste auf die Komponente, für die Sie ein Ereignis verwalten möchten
- [2]: Wählen Sie die Option [Events]
- [3]: Wählen Sie eine Ereigniskategorie aus
- [4]: Wählen Sie das Ereignis aus, das Sie verwalten möchten
Der durch diesen Vorgang generierte Java-Code lautet wie folgt:
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 Fügen Sie hier Ihren Bearbeitungscode ein:
}
- Zeilen 1–5: Der Komponente jLabelCalculer wird ein Ereignis-Handler hinzugefügt. Die Methode addMouseListener erwartet als Parameter eine Klasse, die die folgende Schnittstelle MouseListener implementiert:
![]() |
Die Schnittstelle MouseListener wird von verschiedenen Klassen implementiert, darunter die Klasse MouseAdapter. Diese implementiert die fünf Methoden der Schnittstelle MouseListener, doch diese Methoden führen keine Aktion aus. Daher muss diese Klasse abgeleitet werden, um die gewünschte(n) Methode(n) zu implementieren. Dies geschieht im obigen Code und wird im Folgenden wiedergegeben:
jLabelCalculer.addMouseListener(new java.awt.event.MouseAdapter() {
public void mouseClicked(java.awt.event.MouseEvent evt) {
jLabelCalculerMouseClicked(evt);
}
});
Der obige Code nutzt die Technik der anonymen Klasse, die in Abschnitt 2.5 des Kurses [ref1] behandelt wird.
In Zeile 1 ist der Parameter der Methode addMouseListener eine anonyme Klasse, die dynamisch definiert wird. Es handelt sich um eine Instanz einer Klasse, die von der Klasse MouseAdapter (Zeile 1) abgeleitet ist und deren Methode mouseClicked (Zeilen 2–4) neu definiert wird, damit sie eine bestimmte Aktion ausführt.
Die Methode jLabelCalculerMouseClicked, die als Zeile 3 bezeichnet wird, ist wie folgt definiert:
private void jLabelCalculerMouseClicked(java.awt.event.MouseEvent evt) {
// TODO Fügen Sie hier Ihren Bearbeitungscode ein:
}
Der Entwickler verarbeitet das Ereignis „MouseClicked“, indem er Code in diese Methode einfügt.
Alle Ereignisbehandler werden von NetBeans auf diese Weise generiert. Der Entwickler kann die von NetBeans generierten Codezeilen ignorieren, um einer Komponente eine Methode zuzuordnen. Er kann seinen Code einfach in Zeile 2 oben einfügen. Hier ein Beispiel:
private void jLabelCalculerMouseClicked(java.awt.event.MouseEvent evt) {
System.out.println("Mouse Clicked");
}
Wenn man die grafische Benutzeroberfläche ausführt und auf den Link „[Calculer]“ klickt, erscheint folgende Meldung in der Konsole:
![]() |
- [1]: Man klickt zweimal auf das Label [Calculer]
- [2]: Der Handler für dieses Ereignis wurde ausgeführt und hat die Meldungen mouseClicked in der NetBeans-Konsole erzeugt.
Die Komponenten [jComboBoxNomsListes, jListNomsVoix, jListResultats] sind wie folgt deklariert:
protected javax.swing.JComboBox jComboBoxNomsListes;
protected javax.swing.JList jListNomsVoix;
protected javax.swing.JList jListResultats;
Diese Komponenten sind Listen, die normalerweise durch einen Typ T parametrisiert werden: den Typ der Elemente des Modells, das von den Komponenten angezeigt wird. Dieser Typ T kann beliebig sein. Der in der Listenkomponente angezeigte Wert ist vom Typ [String]. Standardmäßig wird dann die Methode [T.toString()] für die Anzeige verwendet. Um besser steuern zu können, was angezeigt wird, soll der Typ T hier der Typ „String“ sein. Die korrekte Deklaration unserer Listen lautet daher wie folgt:
protected javax.swing.JComboBox<String> jComboBoxNomsListes;
protected javax.swing.JList<String> jListNomsVoix;
protected javax.swing.JList<String> jListResultats;
Dieses Ergebnis erhält man, indem man eine der Eigenschaften der Komponente ändert:
![]() |
10.3.4. Trennung des Codes
Kehren wir zur Struktur unserer Anwendung zurück:
![]() |
Die Klasse [AbstractElectionsSwing] muss die oben genannte Schicht [ui] implementieren. Der von NetBeans generierte Code enthält derzeit nur Code zur Fensterverwaltung und Ereignisbehandler, die vorerst noch keine Funktion haben. Oben ist zu sehen, dass die Klasse [AbstractElectionsSwing] den Datenaustausch mit der Schicht [métier] verwalten muss. Diese Verwaltung erfolgt in den Ereignisbehandlungsroutinen. Um die Struktur des Codes zu verdeutlichen, beschließen wir, ihn in zwei Klassen aufzuteilen:
- [AbstractElectionsSwing], die bis auf wenige Details so bleibt, wie sie von NetBeans generiert wurde. Diese Klasse wird selbst keine Ereignisse verarbeiten. Die Ereignisbehandler sind leer und als abstrakt deklariert. Sie werden von einer von [AbstractElectionsSwing] abgeleiteten Klasse implementiert.
- [ElectionsSwing], eine von [AbstractElectionsSwing] abgeleitete Klasse, die alle Ereignisbehandler implementieren wird.
Diese Art der Trennung ist nicht ungewöhnlich. Sie findet sich beispielsweise in den Webseiten ASP.NET (nicht in der Version MVC). Das NetBeans-Projekt entwickelt sich wie folgt:
![]() |
Der Code der Klasse [AbstractElectionsSwing] entwickelt sich hingegen wie folgt:
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();
}
}
....
// Ereignisbehandler
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();
...
}
- Zeile 1: Die Klasse wird als abstrakt deklariert
- Zeilen 3–5: Verarbeitung des Klicks auf die Menüoption [jMenuItemCalculer]. Man sieht, dass die Ereignisbehandlung an die Methode doCalculer in Zeile 19 delegiert wird. Diese Methode ist nicht implementiert und als abstrakt deklariert. Sie wird von der abgeleiteten Klasse [ElectionsSwing] implementiert;
- Zeilen 9–13: der Ereignis-Handler clic für das Label [jLabelCalculer]. Ein Klick löst immer ein Ereignis aus, unabhängig davon, ob die Komponente [jLabel] aktiv (enabled=true) oder inaktiv (enabled=false) ist. Hier wird sichergestellt, dass sie tatsächlich aktiv ist, um das Ereignis zu verarbeiten;
- Zeilen 15 ff.: Diese Technik der Delegation der Ereignisverarbeitung an eine abstrakte Methode wird auf alle Ereignisbehandler angewendet.
Die von [AbstractElectionsSwing] abgeleitete Klasse [ElectionsSwing] implementiert alle Ereignisbehandler, die nicht von [AbstractElectionsSwing] implementiert werden:
package elections.ui.service;;
...
public class ElectionsSwing extends AbstractElectionsSwing {
// Ereignisbehandler
@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() {
...
}
}
- Zeile 3: [ElectionsSwing] leitet sich von [AbstractElectionsSwing] ab
- Zeilen 7–50: die Ereignisbehandler des Grafikfensters
Die Methoden der abgeleiteten Klasse [ElectionsSwing] werden die Komponenten der übergeordneten Klasse [AbstractElectionsSwing] bearbeiten. Derzeit haben diese Komponenten den Gültigkeitsbereich private, wodurch der Unterklasse [ElectionsSwing] der Zugriff darauf verwehrt wird:
private JMenuItem jMenuItemAPropos = null;
private JLabel jLabelAjouter = null;
Um dieses Problem zu beheben, wird der Geltungsbereich der Komponenten der grafischen Benutzeroberfläche auf [protected] festgelegt:
![]() |
- das Attribut [protected] auf [3] setzen;
10.3.5. Implementierung der Schnittstelle [IElectionsUI]
Kehren wir zur Struktur unserer Anwendung zurück:
![]() |
Oben muss die Schicht [ui] dem Objekt [main] die Schnittstelle [IElectionsUI] bereitstellen:
package elections.ui.service;
public interface IElectionsUI {
/**
* lance le dialogue avec l'utilisateur
*/
public void run();
}
Diese Schnittstelle wurde im Projekt [elections-console-metier-dao-jdbc] definiert und in Abschnitt 9.4 beschrieben. Da dieses Projekt eine Abhängigkeit des Projekts [swing] ist, ist diese Schnittstelle bekannt.
Da die Klasse [AbstractElectionsSwing] nun abstrakt ist, kann sie von Spring nicht mehr instanziiert werden. Stattdessen muss nun die Klasse [ElectionsSwing] instanziiert werden. Die Klasse [ElectionsSwing] muss die Schnittstelle [IElectionsUI] implementieren. Ihr Code ändert sich daher wie folgt:
public class ElectionsSwing extends AbstractElectionsSwing implements IElectionsUI {
// Run-Methode der Schnittstelle [ElectionsUI]
public void run() {
...
}
- Zeile 1: Die Klasse [ElectionsSwing] implementiert die Schnittstelle [IElectionsUI]
- Zeilen 4–6: Die Methode [run] dieser Schnittstelle
Was soll die Methode run tun? Das Grafikfenster anzeigen. Wie macht man das? Man kann sich an der Methode [main] orientieren, die NetBeans in der Klasse [AbstractElectionsSwing] generiert hat und die das Gewünschte tut:
public static void main(String args[]) {
/* Nimbus-Look-and-Feel festlegen */
//<editor-fold defaultstate="collapsed" desc="Code zur Einstellung des Look-and-Feel (optional)">
/* Wenn Nimbus (eingeführt in Java SE 6) nicht verfügbar ist, bleibt das Standard-Look-and-Feel erhalten.
* 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>
/* Formular erstellen und anzeigen */
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
new NewJFrame().setVisible(true);
}
});
}
Der in Zeile 28 verwendete Konstruktor [AbstractElectionsSwing] lautet wie folgt:
public AbstractElectionsSwing() {
initComponents();
}
- Zeile 2: Die Methode [initComponents] ist eine private Methode, die vom GUI-Generator generiert wurde. Ihr Code kann nicht geändert werden.
Die Methode [run] der Klasse [ElectionsSwing] könnte dann wie folgt lauten:
@Override
public void run() {
// Die grafische Benutzeroberfläche wird angezeigt
SwingUtilities.invokeLater(new Runnable() {
public void run() {
init();
setVisible(true);
}
});
}
- Zeile 6: Die grafische Benutzeroberfläche wird mithilfe der Methode [init] initialisiert. Hier möchte man die Methode [initComponents] der übergeordneten Klasse aufrufen, diese ist jedoch privat. Daher fügen wir in der übergeordneten Klasse [AbstractElectionsSwing] die folgende Methode [init] hinzu:
protected void init(){
initComponents();
}
- (Fortsetzung)
- Da sie sich in der Klasse [AbstractElectionsSwing] befindet, hat die Methode [init] Zugriff auf die private Methode [initComponents] derselben Klasse;
- da sie das Attribut [protected] besitzt, ist sie in der untergeordneten Klasse [ElectionsSwing] sichtbar;
- Zeile 7: Die grafische Benutzeroberfläche wird sichtbar gemacht;
Hinweis: Nachdem die Methode [run] in die Klasse [ElectionsSwing] geschrieben wurde, kann die Methode [main] der abstrakten Klasse [AbstractElectionsSwing] gelöscht werden.
10.3.6. Die ausführbare Klasse
Kehren wir zur Struktur unserer Anwendung zurück:
![]() |
Wir möchten, dass Spring die Schicht [ui] instanziiert, so wie es der Fall war, als diese noch durch eine Anwendung console implementiert wurde. Dazu muss die Implementierungsklasse [ElectionsSwing] eine Referenz auf die Schicht [métier] enthalten:
@Component
public class ElectionsSwing extends AbstractElectionsSwing implements IElectionsUI{
// Referenz zur Schicht [métier]
@Autowired
private IElectionsMetier metier;
...
- Zeile 1: Die Klasse [ElectionsSwing] ist eine Spring-Komponente;
- Zeilen 5–6: Spring-Injektion einer Referenz auf die Schicht [métier];
Die grafische Benutzeroberfläche wird durch die Ausführung der folgenden Klasse [BootElectionsSwing] gestartet:
![]() |
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);
}
}
Einen ähnlichen Code haben wir bereits in Abschnitt 9.5 erläutert, als wir den Code der Klassen [AbstractBootElections] und [BootElectionsConsole] erklärt haben. In Zeile 12 wird die Bean mit dem Namen [electionsSwing] abgerufen, die dem Spring-Standardnamen für die Klasse [ElectionsSwing] entspricht.
10.3.7. Initialisierung der grafischen Benutzeroberfläche
Wenn die grafische Benutzeroberfläche angezeigt wird, wurden einige ihrer Komponenten initialisiert:

Oben ist zu sehen:
- dass das Kombinationsfeld mit den Namen der Listen gefüllt wurde;
- dass die Anzahl der zu besetzenden Sitze und die Wahlhürde angegeben sind;
- dass einige Links deaktiviert wurden;
- dass am unteren Rand des Fensters eine Erfolgsmeldung angezeigt wird;
Zu welchem Zeitpunkt erfolgen diese Initialisierungen? Sie können erst nach der Initialisierung des Feldes [electionsMetier] der Klasse [ElectionsSwing] erfolgen. Denn die Namen der Listen werden von der Ebene [metier] angefordert. Die Initialisierung dieses Feldes erfolgt durch Spring in der folgenden Reihenfolge:
- Verwendung des Konstruktors ohne Parameter der Klasse [ElectionsSwing];
- Einbindung der Abhängigkeiten, in diesem Fall die Referenz der Schicht [métier];
- Ausführung der Methode [run] der Klasse [ElectionsSwing]:
@Override
public void run() {
// Die grafische Benutzeroberfläche wird angezeigt
SwingUtilities.invokeLater(new Runnable() {
public void run() {
init();
setVisible(true);
}
});
}
- In Zeile 12 haben wir festgelegt, dass die Methode [init] der übergeordneten Klasse aufgerufen wird, die die Komponenten der grafischen Benutzeroberfläche zeichnet. Wir werden diese Methode lokal in der Klasse [ElectionsSwing] neu definieren. In dieser Methode werden wir die Komponenten des Fensters (Kombinationsfeld, Beschriftungen) initialisieren, diesmal mit Daten:
Die lokale Methode [init] könnte folgendes Grundgerüst haben:
public class ElectionsSwing extends AbstractElectionsSwing implements IElectionsUI {
...
// Referenz zur Schicht [métier]
@Autowired
private IElectionsMetier metier;
// Initialisierungen
public void init() {
// Generierung der Komponenten durch die übergeordnete Klasse
super.init();
// die Listen werden von der Schicht [metier] angefordert
...
// Die Namen der Listen werden mit dem Kombinationsfeld jComboBoxNomsListes verknüpft
...
// sowie die Wahlparameter
...
// werden die mit diesen beiden Informationen verbundenen Beschriftungen initialisiert
...
// Erfolgsmeldung
...
// Initialisierung des Status bestimmter Formularkomponenten
...
}
Beachten Sie in Zeile 11 den Aufruf der Methode [init] aus der übergeordneten Klasse.
10.3.8. Die Klasse [Utilitaires]
Eine Reihe statischer Hilfsmethoden wurde in der Klasse [Utilitaires] zusammengefasst:
![]() |
Die Klasse [Utilitaires] sieht wie folgt aus:
package istia.st.elections.ui;
import javax.swing.JLabel;
import javax.swing.JMenuItem;
//Hilfsklasse
class Utilitaires {
// Verwaltung des Zustands eines Labels-Arrays
public static void setEnabled(JLabel[] labels, boolean value) {
for (int i = 0; i < labels.length; i++) {
labels[i].setEnabled(value);
}
}
// Status eines Menüoptionen-Arrays verwalten
public static void setEnabled(JMenuItem[] menuItems, boolean value) {
...
}
}
- Zeile 9: Die Methode setEnabled legt den Status der in einem Array definierten Komponenten JLabel fest. Die Methode setEnabled einer Komponente JLabel ermöglicht es, die Komponente JLabel zu aktivieren oder zu deaktivieren.
Aufgabe: Schreiben Sie in Anlehnung an das Beispiel der Methode setEnabled in Zeile 9 (,) die Methode setEnabled in Zeile 16, die dasselbe mit den Komponenten JMenuItem bewirkt.
10.3.9. Der Code der Klasse [ElectionsSwing]
Zur Erinnerung: Die allgemeine Struktur der Klasse [ElectionsSwing] lautet wie folgt:
package istia.st.elections.ui;
...
public class ElectionsSwing extends AbstractElectionsSwing implements IElectionsUI {
// Referenz zur Schicht [métier]
@Autowired
private IElectionsMetier metier;
// Initialisierungen
public void init() {
...
}
// Ereignisbehandler
@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() {
...
}
}
Wir werden die Methoden der Klasse nacheinander untersuchen.
10.3.9.1. Die Methode [init]
Kehren wir zur grafischen Benutzeroberfläche zurück:
![]() |
Die Methode [init] hat folgende Ziele:
- die Kombination [4] mit den Kennungen und Namen der Listen im Format [id - nom] zu füllen
- eine Erfolgsmeldung in [15] anzuzeigen
- die Beschriftungen [2] und [3] zu initialisieren
- bestimmte Links zu deaktivieren
Das Grundgerüst der Methode [init] könnte wie folgt aussehen:
@Override
protected void init() {
// Generierung der Komponenten durch die übergeordnete Klasse
super.init();
// lokale Initialisierungen
modèleNomsVoix = new DefaultListModel<>();
jListNomsVoix.setModel(modèleNomsVoix);
modèleRésultats = new DefaultListModel<>();
jListResultats.setModel(modèleRésultats);
String info;
try {
// Die Listen werden von der Schicht [métier] angefordert
listes = ...
// Die Namen der Listen werden dem Kombinationsfeld zugeordnet jComboBoxNomsListes
...
// sowie die Wahlparameter
int nbSiegesAPourvoir = ...
double seuilElectoral = ...
// werden die mit diesen beiden Informationen verbundenen Beschriftungen initialisiert
...
// Erfolgsmeldung
info = "Source de données lue avec succès";
} catch (ElectionsException ex1) {
// Der Fehler wird protokolliert
info = getInfoForException("Les erreurs suivantes se sont produites :", ex1);
} catch (RuntimeException ex2) {
// Der Fehler wird protokolliert
info = getInfoForException("Les erreurs suivantes se sont produites :", ex2);
}
// Die Information wird angezeigt
jTextPaneMessages.setText(info);
jTextPaneMessages.setCaretPosition(0);
// Formularstatus
Utilitaires.setEnabled(new JLabel[] { jLabelAjouter, jLabelCalculer, jLabelEnregistrer, jLabelSupprimer }, false);
Utilitaires.setEnabled(
new JMenuItem[] { jMenuItemAjouter, jMenuItemCalculer, jMenuItemEnregistrer, jMenuItemSupprimer }, false);
// Fenster zentrieren
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) {
// Meldung anzeigen
StringBuffer info = new StringBuffer(String.format("%s -------------\n", message));
info.append(String.format("Code erreur : %d\n", ex.getCode()));
// Fehler werden angezeigt
for (String erreur : ex.getErreurs()) {
info.append(String.format("-- %s\n", erreur));
}
return info.toString();
}
private String getInfoForException(String message, RuntimeException ex) {
// Meldung anzeigen
StringBuffer info = new StringBuffer(String.format("%s -------------\n", message));
// Ausnahmestapel anzeigen
Throwable cause = ex;
while (cause != null) {
info.append(String.format("-- %s\n", cause.getMessage()));
cause = cause.getCause();
}
return info.toString();
}
Aufgabe: Vervollständigen Sie den Code der Methode [init].
Im Kurs zu lesen: Komponenten JTextField, JLabel
Wissenswertes:
Eine Komponente vom Typ JList zeigt die in einer Vorlage enthaltenen Daten an. Standardmäßig ist diese Vorlage vom Typ DefaultListModel (Zeilen 2 und 3). Ein Objekt vom Typ DefaultListModel verhält sich ähnlich wie ein Objekt vom Typ ArrayList:
- Um ein Objekt vom Typ o in die Vorlage einzufügen:
[DefaultListModel].addElement(Object o);
In dieser Anwendung ist das Objekt o immer vom Typ String.
- Um das Element Nr. i aus der Vorlage zu entfernen:
[DefaultListModel].remove(int i);
- Um das Element Nr. i aus der Vorlage zu erhalten:
[DefaultListModel].elementAt(int i);
Um ein Element zur Combo-Box [jComboBoxNomsListes] hinzuzufügen, wird die Methode [addItem] verwendet:
jComboBoxNomsListes.addItem(chaîne de caractères)
Eine Komponente JTextPane verfügt über die Methoden getText() und setText() zum Lesen bzw. Schreiben des angezeigten Textes.
10.3.9.2. Status des Links [Ajouter] verwalten
Der Link [Ajouter] [6] ist nur aktiv, wenn das Feld [5] der Stimmen nicht leer ist. In der Klasse [AbstractElectionsSwing] lautet der Handler, der die Cursorbewegungen im Feld [5] verfolgt, wie folgt:
private void jTextFieldVoixListeCaretUpdate(javax.swing.event.CaretEvent evt) {
doMajLabelAjouter()
}
Zeile 2 ruft die Methode [doMajLabelAjouter] der Klasse [ElectionsSwing] auf.
protected void doMajLabelAjouter() {
// Der Status des Labels wird festgelegt [jLabelAjouter]
...
// Der Status des Menüs wird festgelegt: [jMenuItemAjouter]
...
}
Aufgabe: Vervollständigen Sie den Code der Methode [doMajLabelAjouter].
10.3.9.3. Stimmen den einzelnen Listen zuweisen
Für jede Kandidatenliste aus (4) wird wie folgt vorgegangen:
- Auswahl einer Liste aus (4)
- Eingabe der Stimmenzahl in (5)
- Bestätigung durch Klicken auf den Link [Ajouter]
Eingabefehler werden wie im folgenden Beispiel angezeigt:

Ist die Stimmenzahl korrekt, wird die Liste in die Komponente (8) aufgenommen, die Stimmenzahl gelöscht und der Link [Ajouter] deaktiviert:
![]() | ![]() |
In der Klasse [AbstractElectionsSwing] ist der Handler, der den Klick auf den Link [Ajouter] verarbeitet, folgender:
private void jLabelAjouterMouseClicked(java.awt.event.MouseEvent evt) {
if (jLabelAjouter.isEnabled()) {
doAjouter();
}
}
Zeile 3 ruft die Methode [doAjouter] der Klasse [ElectionsSwing] auf:
// Vorlagen für die Listen JList
private DefaultListModel<String> modèleNomsVoix = null;
private DefaultListModel<String> modèleRésultats = null;
// die Listen im Wettbewerb
private ListeElectorale[] listes;
// vom Benutzer eingegebene Listen
private final List<ListeElectorale> listesSaisies = new ArrayList<>();
private ListeElectorale[] tListesSaisies;
...
@Override
protected void doAjouter() {
// Ist die Stimmenzahl korrekt?
...
// Falls ein Fehler vorliegt, wird dieser gemeldet
if (erreur) {
JOptionPane.showMessageDialog(null, "Nombre de voix incorrect", "Elections : erreur",
JOptionPane.INFORMATION_MESSAGE);
jTextFieldVoixListe.requestFocus();
// Zurück zur grafischen Benutzeroberfläche
return;
}
// Kein Fehler – die Liste wird gespeichert
listesSaisies.add(...);
modèleNomsVoix.addElement(...);
// Die Anzahl der Stimmen wird bereinigt
jTextFieldVoixListe.setText("");
// Formularstatus (Menüs, Beschriftungen)
...
}
- Zeile 25: Jedes Mal, wenn der Benutzer Stimmen zu einer Liste hinzufügt und seine Auswahl bestätigt, wird diese Liste in das Feld [listesSaisies] in Zeile 9 geschrieben. Dort wird die Liste mit den Informationen aus [id, version, nom, voix] gespeichert. Die ersten drei Informationen stammen aus den ursprünglich in der Tabelle in Zeile 6 gespeicherten Listen. Mit der Methode [getSelectedIndex] des Kombinationsfelds lässt sich der Index der ausgewählten Liste ermitteln;
Aufgabe: Vervollständigen Sie den Code der Methode [doAjouter].
10.3.9.4. Verwalten des Status des Links [Supprimer]
Der Link [Supprimer] [9] ist nur aktiv, wenn ein Element in [8] ausgewählt ist.
In der Klasse [AbstractElectionsSwing] ist der Handler, der auf einen Klick auf ein Element der Liste [8] reagiert, der folgende:
private void jListNomsVoixValueChanged(javax.swing.event.ListSelectionEvent evt) {
doMajLabelSupprimer();
}
Zeile 2 ruft die Methode [doMajLabelSupprimer] der Klasse [ElectionsSwing] auf.
@Override
protected void doMajLabelSupprimer() {
// Die Beschriftung [jLabelSupprimer] und die entsprechende Menüoption werden aktiviert
...
}
Aufgabe: Vervollständigen Sie den Code der Methode [doMajLabelSupprimer].
10.3.9.5. Eine Kandidatenliste löschen
Über den Link [Supprimer] [9] kann das in (8) ausgewählte Paar (Name, Stimme) gelöscht werden. Nach dem Löschen wird der Link [Supprimer] deaktiviert. Er wird erst wieder aktiviert, wenn in (8) eine neue Liste ausgewählt wird.
![]() | ![]() |
In der Klasse [AbstractElectionsSwing] ist der Handler, der auf den Klick auf den Link [Supprimer] reagiert, der folgende:
private void jLabelSupprimerMouseClicked(java.awt.event.MouseEvent evt) {
if(jLabelSupprimer.isEnabled()){
doSupprimer();
}
}
Zeile 3 ruft die Methode [doSupprimer] der Klasse [ElectionsSwing] auf.
@Override
protected void doSupprimer() {
// Löschen der ausgewählten Liste, der Vorlage modèleNomsVoix und der eingegebenen Listen
...
// Aktualisierung des Status der Bezeichnungen und Menüoptionen des Formulars
Utilitaires.setEnabled(...);
...
}
Aufgabe: Vervollständigen Sie den Code der Methode [doSupprimer].
10.3.9.6. Status des Links [Calculer] verwalten
Die Verknüpfung [Calculer] [10] ist nur aktiv, wenn mindestens ein Element in [8] vorhanden ist.
Aufgabe: Fügen Sie den für die Verwaltung dieser Verknüpfung erforderlichen Code in die zuvor geschriebenen Methoden [doAjouter] und [doSupprimer] ein. Die entsprechende Menüoption wird ebenfalls verwaltet.
Hinweis: Die Anzahl der Elemente eines Elements vom Typ DefaultListModel wird mit der Methode size() ermittelt.
10.3.9.7. Sitze berechnen
Über den Link [Calculer] [10] kann die Sitzberechnung gestartet und die Ergebnisse in (14) angezeigt werden. Im Falle eines Fehlers (alle Listen wurden ausgeschlossen) wird in [15] eine Fehlermeldung angezeigt. In jedem Fall wird nach der Berechnung der Link [Calculer] [10] deaktiviert.
In der Klasse [AbstractElectionsSwing] ist der Handler, der auf den Klick auf den Link [Calculer] reagiert, der folgende:
private void jLabelCalculerMouseClicked(java.awt.event.MouseEvent evt) {
if(jLabelCalculer.isEnabled()){
doCalculer();
}
}
Zeile 3 ruft die Methode [doCalculer] der Klasse [ElectionsSwing] auf.
// Vom Benutzer eingegebene Listen
private final List<ListeElectorale> listesSaisies = new ArrayList<>();
private ListeElectorale[] tListesSaisies;
...
@Override
protected void doCalculer() {
tListesSaisies = listesSaisies.toArray(new ListeElectorale[0]);
// Berechnung der Sitze
try {
...
} catch (ElectionsException ex) {
// Die Ausnahme wird angezeigt
...
return;
}
// Anzeige der Ergebnisse
...
// Formularstatus aktualisieren
Utilitaires.setEnabled(...);
}
Aufgabe: Vervollständigen Sie den Code der Methode [doCalculer].
10.3.9.8. Ergebnisse in der Datenquelle speichern
Über die Verknüpfung [Enregistrer] (12) können die Ergebnisse der Sitzberechnung in der Datenquelle gespeichert werden. Nach erfolgreicher Speicherung wird der Link [Enregistrer] deaktiviert. Bei einem Fehler wird in [15] eine Fehlermeldung angezeigt. In jedem Fall wird anschließend der Link [Enregistrer] deaktiviert.
In der Klasse [AbstractElectionsSwing] ist der Handler, der den Klick auf das Label [Enregistrer] verarbeitet, der folgende:
private void jLabelEnregistrerMouseClicked(java.awt.event.MouseEvent evt) {
if(jLabelEnregistrer.isEnabled()){
doEnregistrer();
}
}
Zeile 3 ruft die Methode [doEnregistrer] der Klasse [ElectionsSwing] auf:
@Override
protected void doEnregistrer() {
// Anforderung der Speicherung an die Geschäftsebene
try {
...
} catch (ElectionsException ex) {
// Die Ausnahme wird angezeigt
...
// Rückkehr zur grafischen Benutzeroberfläche
return;
}
// Formular aktualisieren
Utilitaires.setEnabled(...);
...
}
Aufgabe: Vervollständigen Sie den Code der Methode [doEnregistrer].
10.3.9.9. Ergebnisse löschen
Über den Link [Effacer] (11) können die in (14) angezeigten Ergebnisse gelöscht werden.
In der Klasse [AbstractElectionsSwing] ist der Handler, der den Klick auf das Label [Effacer] verarbeitet, folgender:
private void jLabelEffacerMouseClicked(java.awt.event.MouseEvent evt) {
if(jLabelEffacer.isEnabled()){
doEffacer();
}
}
Zeile 3 ruft die Methode [doEffacer] der Klasse [ElectionsSwing] auf:
@Override
protected void doEffacer() {
// Die Ergebnisliste wird geleert
....
// Formular aktualisieren
Utilitaires.setEnabled(...);
}
Aufgabe: Vervollständigen Sie den Code der Methode [doEffacer].
Hinweis: Die Klasse DefaultListModel verfügt über eine Methode clear(), die alle ihre Elemente löscht.
10.3.10. Verbesserungen
Die bisherige grafische Benutzeroberfläche lässt sich auf verschiedene Weise verbessern: Der Benutzer könnte versehentlich nicht alle Stimmen aus allen im Kombinationsfeld vorhandenen Listen eingeben oder fälschlicherweise Stimmen aus derselben Liste mehrfach eingeben.
Aufgabe: Verbessern Sie den Algorithmus so, dass diese beiden Fälle nicht auftreten können. Eine einfache Lösung besteht darin, ein Wörterbuch der eingegebenen Listen zu verwalten, dessen Schlüssel die Elemente des Kombinationsfelds sind. Außerdem soll sichergestellt werden, dass der Link [Calculer] erst dann aktiviert wird, wenn alle Listen eingegeben wurden.
Zu lesen im Kurs [ref1]: die Klasse HashTable in Abschnitt 3.8.






































