10. [Aufgabe]: Implementierung der [ui]-Schicht unter Verwendung einer Swing-Schnittstelle –
Stichworte: mehrschichtige Architektur, Spring, Dependency Injection, Swing-Komponentenbibliothek.
![]() |
10.1. Support
![]() |
In der [UI]-Schicht möchten wir eine Swing-GUI erstellen. NetBeans verfügt über das [Matisse]-Tool zum Erstellen dieser Swing-Oberflächen, das dem 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 wir also JavaFX-Oberflächen erstellen, können wir Eclipse in der gesamten Schichtenarchitektur verwenden.
NetBeans kann jedes Maven-Projekt öffnen. Wir werden daher das vorherige Maven-Projekt verwenden und eine Swing-Oberfläche hinzufügen. In [2] laden wir (Datei / Projekt öffnen) die Maven-Projekte für die drei Schichten, die wir mit Eclipse erstellt haben. Anschließend erstellen wir deren Binärdateien [3]. Die Optionen [Erstellen] und [Bereinigen und Erstellen] erstellen die Binärdatei für das Projekt, auf das sie angewendet werden. Diese Binärdateien werden im [target]-Ordner des Projekts abgelegt [4-5]:
![]() |
Die Option [Clean] löscht diesen [target]-Ordner. Die Option [Build] erstellt ihn neu. Die Erfahrung zeigt, dass bei unerwarteten Problemen als Erstes ein [Clean and Build] für das Projekt durchgeführt werden sollte, um sicherzustellen, dass Sie mit der neuesten Version arbeiten. Dies ist insbesondere dann erforderlich, wenn Sie Konfigurationsdateien haben, deren Änderung keine automatische Neukompilierung auslöst, wenn das Projekt ausgeführt wird. Sie müssen diese Neukompilierung dann mit einem [Clean and Build] erzwingen, damit die neuen Versionen im [Ziel]-Ordner installiert werden.
10.2. So funktioniert die Anwendung
Kehren wir zur Gesamtarchitektur der Anwendung [Elections] zurück:
![]() |
Wir konzentrieren uns nun auf eine neue Implementierung der [ui]-Schicht. Die einzige derzeit vorhandene Implementierung ist eine Konsolenoberfläche. Wir erstellen nun eine grafische Benutzeroberfläche.
Der Benutzer wird über die folgende Oberfläche verfügen, um mit der Anwendung [Elections] zu interagieren:
![]() |
![]() |
Die grafische Benutzeroberfläche befindet sich in der [ui]-Ebene. Diese Ebene interagiert mit dem Benutzer.
- Beim Start instanziiert die [main]-Konsolenanwendung die drei Schichten der Anwendung mithilfe von Spring. Dies geschieht, noch bevor die grafische Benutzeroberfläche überhaupt sichtbar ist. Ebenfalls während dieser Initialisierungsphase werden Informationen zur Wahl (Anzahl der zu besetzenden Sitze, Wahlhürde, konkurrierende Listen) von der [dao]-Schicht angefordert. Wenn diese Initialisierungsphase fehlschlägt (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 die Daten erfolgreich gelesen wurden, wird die grafische Benutzeroberfläche mit den folgenden Informationen angezeigt (siehe Screenshot oben):
- die Anzahl der zu besetzenden Sitze (2)
- die Wahlhürde in (3)
- die Kennungen und Namen der Kandidatenlisten in (4)
- Der Benutzer weist dann jeder Kandidatenliste die Anzahl der Stimmen zu, indem er die Felder 4 (ID – Name), 5 (Stimmen) und 6 (Hinzufügen) verwendet.

- Anschließend können Sie den Link (10) verwenden, um die Sitze zu berechnen:

- Über den Link [Speichern] (12) können Sie die Ergebnisse in der Datenquelle speichern.
10.3. Die Klasse [ElectionsSwing], die die [ui]-Schicht implementiert
10.3.1. Das NetBeans-Projekt
Hinweis: In Abschnitt 22.4 wird erläutert, wie Sie NetBeans erhalten.
Das fertige NetBeans-Projekt für die Anwendung sieht wie folgt aus [1]. Erstellen Sie es, indem Sie die Schritte [2–5] befolgen:
![]() |
![]() |
Stellen Sie sicher, dass das Projekt so konfiguriert ist, dass es mit einem JDK 1.8 kompiliert werden kann [1-6]:
![]() |
10.3.2. Maven-Konfiguration
Das neue Projekt [elections-swing-business-dao-jdbc] baut auf dem vorherigen Projekt [elections-console-business-dao-jdbc] auf. Fügen Sie dazu wie folgt eine Maven-Abhängigkeit hinzu [1-3]:
![]() |
![]() |
10.3.3. Erstellen der Benutzeroberfläche
Um die Benutzeroberfläche zu erstellen, können wir wie folgt vorgehen:
![]() |
![]() |
- [1]: Fügen Sie dem Paket [elections.ui.service] ein Objekt hinzu
- [2]: Wählen Sie die Option [JFrame-Formular] in der Kategorie [Swing-GUI-Formulare]
![]() |
- [4]: Benennen Sie die Klasse
- [5]: Das Klassenpaket.
- Beenden Sie den Assistenten.
- [6]: Die generierte Klasse
![]() |
- [7]: die Klasse [AbstractElectionsSwing] im [Design]-Modus
- [8]: die Registerkarte [Navigator], auf der die Baumstruktur [9] der Fensterkomponenten angezeigt wird
- [10]: die Registerkarte [Properties], die die Eigenschaften der in [9] ausgewählten [JFrame]-Komponente anzeigt
![]() |
- [11]: [JFrame] ist ein Komponenten-Container. Komponenten können innerhalb des Containers nach verschiedenen Positionierungsregeln, sogenannten Layouts, angeordnet werden. Hier wählen wir das Layout [Freies Design] [14], das eine freie Positionierung der Komponenten innerhalb des Containers ermöglicht.
Die Komponenten finden wir in der Symbolleiste, die als Palette bezeichnet wird:
![]() |
- [1]: die Palette
- [2]: Eine JLabel-Komponente wird in den Komponenten-Container gezogen
- Durch einen Rechtsklick darauf können wir auf verschiedene Eigenschaften zugreifen: ihren Namen [4], ihren Text [3] oder ihre Ereignisbehandler [5]. Wir verwenden [3], um den Text [6] festzulegen.
![]() |
- [1]: Über die Registerkarte [Eigenschaften] der Komponente [JLabel] können Sie auf deren Eigenschaften zugreifen: die horizontale Position [2], die vertikale Position [3], die Schriftart [4] und den Text [5].
Wenn eine Komponente auf die grafische Oberfläche gezogen und dort konfiguriert wird und Sie Ihre Arbeit speichern (Strg-S), wird Code in der Klasse [AbstractElectionsSwing] generiert:
![]() |
![]() |
Ändern Sie diesen ausgegrauten Code nicht, da er beim nächsten Speichern gelöscht und neu generiert wird. Alle vorgenommenen Änderungen gingen dann verloren.
Ein Tutorial zum Erstellen einer grafischen Benutzeroberfläche mit NetBeans finden Sie unter der URL [https://netbeans.org/kb/docs/java/quickstart-gui.html?print=yes#design] (November 2015).
Wir werden nun die folgende Benutzeroberfläche erstellen:
![]() |
Die Komponenten der Schnittstelle sind wie folgt:
Nr. | Typ | Name | Rolle |
JMenuBar | jMenuBar1 | ein Menü | |
JLabel | jLabelSAP | die Anzahl der verfügbaren Plätze | |
JLabel | jLabelSE | die Wahlhürde | |
JComboBox | jComboBoxListNames | Liste der Namen der konkurrierenden Listen | |
JTextField | jTextFieldVotesList | die Anzahl der Stimmen für eine Liste | |
JLabel | jLabelAdd | um eine Liste hinzuzufügen (8) | |
(JScrollPane, JList) | jListNamesVoices | die Namen und Stimmen der Listen | |
JLabel | jLabelDelete | zum Entfernen aus (8) der in (8) ausgewählten Liste | |
JLabel | jLabelCalculate | um die Wahlergebnisse zu berechnen | |
JLabel | jLabelClear | zum Löschen der Wahlergebnisse | |
JLabel | jLabelSave | zum Speichern der Wahlergebnisse | |
(JScrollPane, JList) | jListResults | zur Anzeige der Wahlergebnisse | |
(JScrollPane, JTextPane) | jTextPaneMessages | zum Anzeigen von Folgemeldungen |
Die Anmerkung (JScrollPane, JList) [13-14] soll verdeutlichen, dass eine [JList]-Komponente, wenn sie in das Fenster gezogen wird, automatisch in eine [JScrollPane]-Komponente eingefügt wird, die das Blättern durch die Liste ermöglicht. Es ist die [JScrollPane]-Komponente, die es Ihnen ermöglicht, alle Elemente der Liste anzuzeigen, auch wenn zu einem bestimmten Zeitpunkt nur eine begrenzte Anzahl davon sichtbar ist. Dasselbe gilt für die [JTextPane]-Komponente [15-16].
Das Menü kann wie folgt erstellt werden:
![]() |
- [1, 2]: Eine [Menüleiste]-Komponente wird im Fenster platziert
- [3]: Das Standardmenü, wie auf der Registerkarte [Navigator] gezeigt
- [4, 5, 6]: Durch einen Rechtsklick auf eine Menüoption können Sie:
- den Text [4] und den Namen [5] ändern
- ihre Ereignisse verwalten [6]
- [7]: Das gewünschte Menü
Das gewünschte Menü sieht wie folgt aus:
Ebene 1 | Ebene 2 |
Wahlen | |
Beenden | |
Listen | |
Hinzufügen | |
Löschen | |
Ergebnisse | |
Berechnen | |
Löschen | |
Speichern | |
Über |
Sie können die grafische Benutzeroberfläche jederzeit testen:
![]() |
Beim Erstellen der Benutzeroberfläche müssen Sie bestimmten Beschriftungen und Menüs [Hinzufügen, Löschen, ...] einen Ereignisbehandler zuweisen. 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 [Ereignisse]
- [3]: Wählen Sie eine Ereigniskategorie aus
- [4]: Wählen Sie das Ereignis aus, das Sie bearbeiten 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 add your handling code here:
}
- Zeilen 1–5: Der Komponente jLabelCalculer wird ein Ereignisbehandler hinzugefügt. Die Methode addMouseListener erwartet als Parameter eine Klasse, die die folgende MouseListener-Schnittstelle implementiert:
![]() |
Die Schnittstelle „MouseListener“ wird von verschiedenen Klassen implementiert, darunter auch die Klasse „MouseAdapter“. Diese Klasse implementiert die fünf Methoden der Schnittstelle „MouseListener“, doch diese Methoden führen keine Aktion aus. Daher müssen Sie eine Unterklasse dieser Klasse erstellen, um die gewünschten Methoden zu implementieren. Dies geschieht im obigen Code und wird im Folgenden dargestellt:
jLabelCalculer.addMouseListener(new java.awt.event.MouseAdapter() {
public void mouseClicked(java.awt.event.MouseEvent evt) {
jLabelCalculerMouseClicked(evt);
}
});
Der obige Code verwendet die in Abschnitt 2.5 des Kurses [ref1] beschriebene Technik der anonymen Klassen.
In Zeile 1 ist der Parameter der Methode addMouseListener eine anonyme Klasse, die spontan definiert wird. Es handelt sich um eine Instanz einer von der Klasse MouseAdapter abgeleiteten Klasse (Zeile 1), deren Methode mouseClicked überschrieben wird (Zeilen 2–4), sodass sie eine bestimmte Aktion ausführt.
Die in Zeile 3 aufgerufene Methode *jLabelCalculerMouseClicked* ist wie folgt definiert:
private void jLabelCalculerMouseClicked(java.awt.event.MouseEvent evt) {
// TODO add your handling code here:
}
Der Entwickler verarbeitet das „MouseClicked“-Ereignis, 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 eine Methode mit dem Ereignis einer Komponente zu verknüpfen. Er kann seinen Code einfach in Zeile 2 oben einfügen. Hier ist ein Beispiel:
private void jLabelCalculerMouseClicked(java.awt.event.MouseEvent evt) {
System.out.println("Mouse Clicked");
}
Wenn Sie die GUI ausführen und auf die Schaltfläche [Berechnen] klicken, erscheint folgende Meldung auf der Konsole:
![]() |
- [1]: Klicken Sie zweimal auf die Schaltfläche [Berechnen]
- [2]: Der Ereignis-Handler wurde ausgeführt und hat die „mouseClicked“-Meldungen in der NetBeans-Konsole ausgegeben.
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 mit einem Typ T konfiguriert werden: dem Typ der Elemente im Modell, das von den Komponenten angezeigt wird. Dieser Typ T kann ein beliebiger Typ sein. Der in der Listenkomponente angezeigte Wert ist vom Typ [String]. Standardmäßig wird die Methode [T.toString()] für die Anzeige verwendet. Um die Anzeige besser steuern zu können, wird der Typ T hier als String-Typ festgelegt. Daher lautet die korrekte Deklaration unserer Listen wie folgt:
protected javax.swing.JComboBox<String> jComboBoxNomsListes;
protected javax.swing.JList<String> jListNomsVoix;
protected javax.swing.JList<String> jListResultats;
Wir erzielen dieses Ergebnis, indem wir eine der Eigenschaften der Komponente ändern:
![]() |
10.3.4. Code-Trennung
Kehren wir zur Struktur unserer Anwendung zurück:
![]() |
Die Klasse [AbstractElectionsSwing] muss die darüber liegende [ui]-Schicht implementieren. Ihr von NetBeans generierter Code enthält derzeit nur Code zur Fensterverwaltung und Ereignisbehandler, die zu diesem Zeitpunkt noch keine Funktion haben. Oben sehen wir, dass die Klasse [AbstractElectionsSwing] die Interaktionen mit der [business]-Schicht verarbeiten muss. Diese Verarbeitung findet in den Ereignisbehandlungsroutinen statt. Um die Codestruktur zu verdeutlichen, beschließen wir, sie in zwei Klassen aufzuteilen:
- [AbstractElectionsSwing], die bis auf wenige geringfügige Änderungen 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 ASP.NET-Webseiten (Nicht-MVC-Version). Das NetBeans-Projekt entwickelt sich wie folgt:
![]() |
Der Code für die Klasse [AbstractElectionsSwing] entwickelt sich 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();
}
}
....
// event managers
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: Behandlung des Klicks auf die Menüoption [jMenuItemCalculer]. Wir sehen, 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 Handler für das Klick-Ereignis auf dem Label [jLabelCalculer]. Ein Klick löst immer ein Ereignis aus, unabhängig davon, ob die [jLabel]-Komponente aktiv (enabled=true) oder inaktiv (enabled=false) ist. Hier stellen wir sicher, dass sie tatsächlich aktiv ist, um das Ereignis zu verarbeiten;
- Zeilen 15 ff.: Diese Technik, die Ereignisbehandlung an eine abstrakte Methode zu delegieren, 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 {
// event managers
@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] erweitert [AbstractElectionsSwing]
- Zeilen 7–50: die Ereignisbehandler für das grafische Fenster
Die Methoden der abgeleiteten Klasse [ElectionsSwing] manipulieren die Komponenten der übergeordneten Klasse [AbstractElectionsSwing]. Derzeit haben diese Komponenten einen privaten Geltungsbereich, wodurch die untergeordnete Klasse [ElectionsSwing] keinen Zugriff darauf hat:
private JMenuItem jMenuItemAPropos = null;
private JLabel jLabelAjouter = null;
Um dieses Problem zu beheben, stellen wir sicher, dass der Gültigkeitsbereich der GUI-Komponenten auf [protected] gesetzt ist:
![]() |
- Setzen Sie das Attribut [protected] in [3];
10.3.5. Implementierung der Schnittstelle [IElectionsUI]
Kehren wir zur Struktur unserer Anwendung zurück:
![]() |
Oben muss die [ui]-Ebene die [IElectionsUI]-Schnittstelle gegenüber dem [main]-Objekt 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 {
// interface [ElectionsUI] run method
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 GUI-Fenster anzeigen. Wie machen wir das? Wir können die von NetBeans in der Klasse [AbstractElectionsSwing] generierte Methode [main] als Vorlage verwenden, die genau das tut, was wir wollen:
public static void main(String args[]) {
/* Set the Nimbus look and feel */
//<editor-fold defaultstate="collapsed" desc=" Look and feel setting code (optional) ">
/* If Nimbus (introduced in Java SE 6) is not available, stay with the default look and feel.
* 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>
/* Create and display the form */
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 aussehen:
@Override
public void run() {
// on affiche l'interface graphique
SwingUtilities.invokeLater(new Runnable() {
public void run() {
init();
setVisible(true);
}
});
}
- Zeile 6: Die GUI wird mit der Methode [init] initialisiert. Hier möchten wir die Methode [initComponents] der übergeordneten Klasse aufrufen, diese ist jedoch privat. Daher fügen wir 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] hat, ist sie in der untergeordneten Klasse [ElectionsSwing] sichtbar;
- Zeile 7: Die GUI wird sichtbar gemacht;
Hinweis: Sobald die Methode [run] in der Klasse [ElectionsSwing] geschrieben wurde, kann die Methode [main] der abstrakten Klasse [AbstractElectionsSwing] entfernt werden.
10.3.6. Die ausführbare Klasse
Kehren wir zur Struktur unserer Anwendung zurück:
![]() |
Wir möchten, dass Spring die [ui]-Schicht so instanziiert, wie es bei der Implementierung durch eine Konsolenanwendung der Fall war. Dazu muss die Implementierungsklasse [ElectionsSwing] über einen Verweis auf die [business]-Schicht verfügen:
@Component
public class ElectionsSwing extends AbstractElectionsSwing implements IElectionsUI{
// reference on the [business] layer
@Autowired
private IElectionsMetier metier;
...
- Zeile 1: Die Klasse [ElectionsSwing] ist eine Spring-Komponente;
- Zeilen 5–6: Spring injiziert eine Referenz in die [Business]-Schicht;
Die grafische Benutzeroberfläche wird durch Ausführen der folgenden [BootElectionsSwing]-Klasse 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);
}
}
Wir haben ähnlichen Code in Abschnitt 9.5 erläutert, als wir den Code für die Klassen [AbstractBootElections] und [BootElectionsConsole] besprochen haben. In Zeile 12 rufen wir die Bean mit dem Namen [electionsSwing] ab, die dem Standard-Spring-Namen für die Klasse [ElectionsSwing] entspricht.
10.3.7. Initialisierung der grafischen Benutzeroberfläche
Wenn die GUI angezeigt wird, sind einige ihrer Komponenten bereits initialisiert:

Wie oben gezeigt:
- dass die Kombinationsfeld mit den Namen der Listen gefüllt wurde;
- dass die Anzahl der zu besetzenden Sitze und die Wahlhürde angezeigt werden;
- dass einige Links deaktiviert wurden;
- am unteren Rand des Fensters eine Erfolgsmeldung angezeigt wird;
Wann finden diese Initialisierungen statt? Sie können erst erfolgen, nachdem das Feld [electionsMetier] der Klasse [ElectionsSwing] initialisiert wurde. Der Grund dafür ist, dass die Listennamen von der [metier]-Schicht angefordert werden. Spring initialisiert dieses Feld in der folgenden Reihenfolge:
- unter Verwendung des parameterlosen Konstruktors der Klasse [ElectionsSwing];
- Einbindung von Abhängigkeiten, in diesem Fall die Referenz auf die [business]-Schicht;
- Ausführung der Methode [run] der Klasse [ElectionsSwing]:
@Override
public void run() {
// on affiche l'interface graphique
SwingUtilities.invokeLater(new Runnable() {
public void run() {
init();
setVisible(true);
}
});
}
- In Zeile 12 haben wir festgelegt, dass wir die Methode [init] der übergeordneten Klasse aufrufen, die die GUI-Komponenten zeichnet. Wir werden diese Methode lokal in der Klasse [ElectionsSwing] überschreiben. Innerhalb dieser Methode werden wir diesmal die Fensterkomponenten (Kombinationsfelder, Beschriftungen) mit Daten initialisieren:
Die lokale [init]-Methode könnte folgendes Grundgerüst haben:
public class ElectionsSwing extends AbstractElectionsSwing implements IElectionsUI {
...
// reference on the [business] layer
@Autowired
private IElectionsMetier metier;
// initializations
public void init() {
// generation of components by the parent class
super.init();
// lists are requested from the [metier] layer
...
// associate list names with the jComboBoxNomsListes combo
...
// and election parameters
...
// we initialize the labels linked to these two pieces of information
...
// message of success
...
// initialization status of certain components form
...
}
Beachten Sie in Zeile 11 den Aufruf der Methode [init] der übergeordneten Klasse.
10.3.8. Die Klasse [Utilities]
In der Klasse [Utilities] wurden eine Reihe statischer Hilfsmethoden zusammengefasst:
![]() |
Die Klasse [Utilities] sieht wie folgt aus:
package istia.st.elections.ui;
import javax.swing.JLabel;
import javax.swing.JMenuItem;
//utility class
class Utilitaires {
// manage label array status
public static void setEnabled(JLabel[] labels, boolean value) {
for (int i = 0; i < labels.length; i++) {
labels[i].setEnabled(value);
}
}
// manage the status of a table of menu options
public static void setEnabled(JMenuItem[] menuItems, boolean value) {
...
}
}
- Zeile 9: Die Methode setEnabled legt den Status der in einem Array definierten JLabel-Komponenten fest. Mit der Methode setEnabled einer JLabel-Komponente können Sie das JLabel aktivieren oder deaktivieren.
Aufgabe: Schreiben Sie in Anlehnung an das Beispiel der Methode setEnabled in Zeile 9 die Methode setEnabled in Zeile 16, die dasselbe mit *JMenuItem*-Komponenten bewirkt.
10.3.9. Der Code für die Klasse [ElectionsSwing]
Sehen wir uns die allgemeine Struktur der Klasse [ElectionsSwing] an:
package istia.st.elections.ui;
...
public class ElectionsSwing extends AbstractElectionsSwing implements IElectionsUI {
// reference on the [business] layer
@Autowired
private IElectionsMetier metier;
// initializations
public void init() {
...
}
// event managers
@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 Kombinationsfeld [4] mit den IDs und Namen der Listen im Format [ID – Name] zu füllen
- eine Erfolgsmeldung in [15] anzuzeigen
- die Beschriftungen [2] und [3] zu initialisieren
- bestimmte Links zu deaktivieren
Das Grundgerüst der [init]-Methode könnte wie folgt aussehen:
@Override
protected void init() {
// génération des composants par la classe parent
super.init();
// initialisations locales
modèleNomsVoix = new DefaultListModel<>();
jListNomsVoix.setModel(modèleNomsVoix);
modèleRésultats = new DefaultListModel<>();
jListResultats.setModel(modèleRésultats);
String info;
try {
// on demande les listes à la couche [métier]
listes = ...
// on associe les noms des listes au combo jComboBoxNomsListes
...
// ainsi que les paramètres de l'election
int nbSiegesAPourvoir = ...
double seuilElectoral = ...
// on initialise les labels liés à ces deux informations
...
// message de succès
info = "Source de données lue avec succès";
} catch (ElectionsException ex1) {
// on note l'error
info = getInfoForException("Les erreurs suivantes se sont produites :", ex1);
} catch (RuntimeException ex2) {
// on note l'error
info = getInfoForException("Les erreurs suivantes se sont produites :", ex2);
}
// on affiche l'info
jTextPaneMessages.setText(info);
jTextPaneMessages.setCaretPosition(0);
// état formulaire
Utilitaires.setEnabled(new JLabel[] { jLabelAjouter, jLabelCalculer, jLabelEnregistrer, jLabelSupprimer }, false);
Utilitaires.setEnabled(
new JMenuItem[] { jMenuItemAjouter, jMenuItemCalculer, jMenuItemEnregistrer, jMenuItemSupprimer }, false);
// centrer la fenêtre
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) {
// on affiche le message
StringBuffer info = new StringBuffer(String.format("%s -------------\n", message));
info.append(String.format("Code erreur : %d\n", ex.getCode()));
// on affiche les erreurs
for (String erreur : ex.getErreurs()) {
info.append(String.format("-- %s\n", erreur));
}
return info.toString();
}
private String getInfoForException(String message, RuntimeException ex) {
// on affiche le message
StringBuffer info = new StringBuffer(String.format("%s -------------\n", message));
// on affiche la pile des exceptions
Throwable cause = ex;
while (cause != null) {
info.append(String.format("-- %s\n", cause.getMessage()));
cause = cause.getCause();
}
return info.toString();
}
Aufgabe: Vervollständige den Code für die [init]-Methode.
Lesestoff für den Kurs: JTextField- und JLabel-Komponenten
Hinweis:
Eine JList-Komponente zeigt die Daten eines Modells an. Standardmäßig ist dieses Modell vom Typ „DefaultListModel“ (Zeilen 2 und 3). Ein DefaultListModel-Objekt verhält sich in gewisser Weise wie eine ArrayList:
- So fügen Sie ein Objekt o zum Modell hinzu:
[DefaultListModel].addElement(Object o);
In dieser Anwendung ist das Objekt o immer vom Typ String.
- So entfernen Sie das Element i aus dem Modell:
[DefaultListModel].remove(int i);
- So rufen Sie Element i aus dem Modell ab:
[DefaultListModel].elementAt(int i);
Um ein Element zur Kombinationsfeld [jComboBoxNomsListes] hinzuzufügen, verwenden Sie die Methode [addItem]:
jComboBoxNomsListes.addItem(chaîne de caractères)
Eine JTextPane-Komponente verfügt über die Methoden getText() und setText(), um den angezeigten Text zu lesen bzw. zu schreiben.
10.3.9.2. Verwalten des Status des [Add]-Links
Die Schaltfläche [Hinzufügen] [6] ist nur aktiv, wenn das Feld [5] für Stimmen nicht leer ist. In der Klasse [AbstractElectionsSwing] sieht der Handler, der die Cursorbewegungen im Feld [5] verfolgt, wie folgt aus:
private void jTextFieldVoixListeCaretUpdate(javax.swing.event.CaretEvent evt) {
doMajLabelAjouter()
}
Zeile 2 ruft die Methode [doMajLabelAjouter] der Klasse [ElectionsSwing] auf.
protected void doMajLabelAjouter() {
// on fixe l'état du label [jLabelAjouter]
...
// on fixe l'état du menu [jMenuItemAjouter]
...
}
Aufgabe: Vervollständigen Sie den Code für die Methode [doMajLabelAjouter].
10.3.9.3. Weisen Sie jeder Liste Stimmen zu
Gehen Sie für jede Kandidatenliste aus (4) wie folgt vor:
- Wählen Sie eine Liste aus (4) aus
- Geben Sie die Anzahl der Stimmen in (5) ein
- Bestätigen Sie durch Klicken auf den Link [Hinzufügen]
Eingabefehler werden wie im folgenden Beispiel markiert:

Wenn die Anzahl der Stimmen korrekt ist, wird die Liste zur Komponente (8) hinzugefügt, die Anzahl der Stimmen wird zurückgesetzt und der Link [Hinzufügen] wird deaktiviert:
![]() | ![]() |
In der Klasse [AbstractElectionsSwing] sieht der Handler, der den Klick auf den Link [Hinzufügen] verarbeitet, wie folgt aus:
private void jLabelAjouterMouseClicked(java.awt.event.MouseEvent evt) {
if (jLabelAjouter.isEnabled()) {
doAjouter();
}
}
In Zeile 3 wird die Methode [doAjouter] der Klasse [ElectionsSwing] aufgerufen:
// modèles des listes JList
private DefaultListModel<String> modèleNomsVoix = null;
private DefaultListModel<String> modèleRésultats = null;
// les listes en compétition
private ListeElectorale[] listes;
// listes saisies par l'user
private final List<ListeElectorale> listesSaisies = new ArrayList<>();
private ListeElectorale[] tListesSaisies;
...
@Override
protected void doAjouter() {
// le nombre de voix est-il correct ?
...
// si erreur, alors on la signale
if (erreur) {
JOptionPane.showMessageDialog(null, "Nombre de voix incorrect", "Elections : erreur",
JOptionPane.INFORMATION_MESSAGE);
jTextFieldVoixListe.requestFocus();
// retour à l'graphic interface
return;
}
// pas d'error - save the list
listesSaisies.add(...);
modèleNomsVoix.addElement(...);
// on nettoie le nombre de voix
jTextFieldVoixListe.setText("");
// état formulaire (menus, labels)
...
}
- Zeile 25: Jedes Mal, wenn der Benutzer Stimmen zu einer Liste hinzufügt und seine Auswahl bestätigt, wird diese Liste im Feld [listesSaisies] in Zeile 9 gespeichert. Die Liste wird dort mit den Informationen [id, version, name, voice] gespeichert. Die ersten drei Informationen stammen aus den Listen, die ursprünglich im Array in Zeile 6 gespeichert wurden. Die Methode [getSelectedIndex] des Kombinationsfelds gibt den Index der ausgewählten Liste zurück;
Aufgabe: Vervollständigen Sie den Code für die Methode [doAjouter].
10.3.9.4. Verwalten Sie den Status des Links [Delete]
Der Link [Delete] [9] ist nur aktiv, wenn in [8] ein Element ausgewählt ist.
In der Klasse [AbstractElectionsSwing] sieht der Handler, der auf einen Klick auf ein Element in der Liste [8] reagiert, wie folgt aus:
private void jListNomsVoixValueChanged(javax.swing.event.ListSelectionEvent evt) {
doMajLabelSupprimer();
}
Zeile 2 ruft die Methode [doMajLabelSupprimer] der Klasse [ElectionsSwing] auf.
@Override
protected void doMajLabelSupprimer() {
// on allume le label [jLabelSupprimer] et l'corresponding menu option
...
}
Aufgabe: Vervollständigen Sie den Code für die Methode [doMajLabelSupprimer].
10.3.9.5. Kandidatenliste löschen
Über den Link [Löschen] [9] können Sie das in (8) ausgewählte (Name,Stimme)-Paar löschen. Sobald der Löschvorgang abgeschlossen ist, wird der Link [Löschen] deaktiviert. Er wird erst wieder aktiviert, wenn in (8) eine neue Liste ausgewählt wird.
![]() | ![]() |
In der Klasse [AbstractElectionsSwing] sieht der Handler, der auf einen Klick auf den Link [Löschen] reagiert, wie folgt aus:
private void jLabelSupprimerMouseClicked(java.awt.event.MouseEvent evt) {
if(jLabelSupprimer.isEnabled()){
doSupprimer();
}
}
In Zeile 3 wird die Methode [doSupprimer] der Klasse [ElectionsSwing] aufgerufen.
@Override
protected void doSupprimer() {
// suppression de la liste sélectionnée, du modèle modèleNomsVoix et des listes saisies
...
// maj de l'status of labels and form menu options
Utilitaires.setEnabled(...);
...
}
Zu erledigende Aufgabe: Vervollständigen Sie den Code für die Methode [doSupprimer].
10.3.9.6. Verwalten Sie den Status des Links [Calculate]
Der Link [Calculate] [10] ist nur aktiv, wenn mindestens ein Element in [8] vorhanden ist.
Aufgabe: Fügen Sie den erforderlichen Code zur Verarbeitung dieses Links in die zuvor geschriebenen Methoden [doAdd] und [doDelete] ein. Die entsprechende Menüoption wird ebenfalls behandelt.
Hinweis: Die Anzahl der Elemente in einem DefaultListModel wird mit der Methode size() ermittelt.
10.3.9.7. Sitzplätze berechnen
Über den Link [Berechnen] [10] können Sie die Sitzplatzberechnung starten und die Ergebnisse in [14] anzeigen lassen. Sollte die Berechnung fehlschlagen (alle Listen wurden ausgeschlossen), wird in [15] eine Fehlermeldung angezeigt. In jedem Fall ist der Link [Berechnen] [10] nach der Berechnung deaktiviert.
In der Klasse [AbstractElectionsSwing] sieht der Handler, der auf einen Klick auf den Link [Berechnen] reagiert, wie folgt aus:
private void jLabelCalculerMouseClicked(java.awt.event.MouseEvent evt) {
if(jLabelCalculer.isEnabled()){
doCalculer();
}
}
In Zeile 3 wird die Methode [doCalculer] der Klasse [ElectionsSwing] aufgerufen.
// listes saisies par l'utilisateur
private final List<ListeElectorale> listesSaisies = new ArrayList<>();
private ListeElectorale[] tListesSaisies;
...
@Override
protected void doCalculer() {
tListesSaisies = listesSaisies.toArray(new ListeElectorale[0]);
// calcul des sièges
try {
...
} catch (ElectionsException ex) {
// on affiche l'exception
...
return;
}
// affichage des résultats
...
// maj état formulaire
Utilitaires.setEnabled(...);
}
Aufgabe: Vervollständigen Sie den Code für die Methode [doCalculer].
10.3.9.8. Speichern Sie die Ergebnisse in der Datenquelle
Über den Link [Speichern] (12) können Sie die Ergebnisse der Sitzplatzberechnung in der Datenquelle speichern. Sobald der Speichervorgang erfolgreich abgeschlossen ist, wird der Link [Speichern] deaktiviert. Wenn der Speichervorgang fehlschlägt, wird in [15] eine Fehlermeldung angezeigt. In beiden Fällen wird der Link [Speichern] anschließend deaktiviert.
In der Klasse [AbstractElectionsSwing] sieht der Handler, der den Klick auf das Label [Save] verarbeitet, wie folgt aus:
private void jLabelEnregistrerMouseClicked(java.awt.event.MouseEvent evt) {
if(jLabelEnregistrer.isEnabled()){
doEnregistrer();
}
}
In Zeile 3 wird die Methode [doEnregistrer] der Klasse [ElectionsSwing] aufgerufen:
@Override
protected void doEnregistrer() {
// on demande l'enregistrement à la couche métier
try {
...
} catch (ElectionsException ex) {
// on affiche l'exception
...
// retour à l'interface graphique
return;
}
// maj du formulaire
Utilitaires.setEnabled(...);
...
}
Aufgabe: Vervollständigen Sie den Code für die Methode [doEnregistrer].
10.3.9.9. Ergebnisse löschen
Der Link [Löschen] (11) löscht die in (14) angezeigten Ergebnisse.
In der Klasse [AbstractElectionsSwing] sieht der Handler, der den Klick auf das Label [Clear] verarbeitet, wie folgt aus:
private void jLabelEffacerMouseClicked(java.awt.event.MouseEvent evt) {
if(jLabelEffacer.isEnabled()){
doEffacer();
}
}
In Zeile 3 wird die Methode [doEffacer] der Klasse [ElectionsSwing] aufgerufen:
@Override
protected void doEffacer() {
// on vide la liste des résultats
....
// maj du formulaire
Utilitaires.setEnabled(...);
}
Aufgabe: Vervollständige den Code für die Methode [doEffacer].
Hinweis: Die Klasse DefaultListModel verfügt über eine clear()-Methode, die alle ihre Elemente entfernt.
10.3.10. Verbesserungen
Die bisherige grafische Benutzeroberfläche lässt sich auf verschiedene Weise verbessern: Der Benutzer könnte vergessen, die Stimmen für alle in der Kombinationsfeld vorhandenen Listen einzugeben, und er könnte versehentlich die Stimmen für dieselbe Liste mehrfach eingeben.
Aufgabe: Verbessern Sie den Algorithmus so, dass keiner dieser Fälle eintreten kann. Eine einfache Lösung besteht darin, ein Wörterbuch der eingegebenen Listen zu führen, wobei die Schlüssel die Elemente der Kombinationsfelds sind. Wir stellen außerdem sicher, dass die Schaltfläche [Berechnen] nur aktiviert ist, wenn alle Listen eingegeben wurden.
Siehe den Kurs [ref1]: die HashTable-Klasse in Abschnitt 3.8.






































