1. Sehr kurzer Einstieg in Swing und AWT
Wenn Java ein Auto wäre, dann wären Swing und AWT sein „Armaturenbrett“ und sein „Lenkrad“. AWT (Abstract Window Toolkit) ist die erste Bibliothek zur Erstellung von Desktop-Anwendungen in Java. Sie verwendet die nativen Steuerelemente des Betriebssystems, daher sieht sie unter Windows, Linux und Mac unterschiedlich aus.
Swing ist eine modernere Schicht über AWT, vollständig in Java geschrieben. Swing ist hübscher, flexibler, unterstützt viele zusätzliche Möglichkeiten und sieht auf allen Plattformen gleich aus (nun ja, fast). In beiden Fällen unterstützen alle UI-Elemente (Schaltflächen, Textfelder, Kontrollkästchen usw.) die Arbeit mit Ereignissen.
In dieser Vorlesung verwenden wir Swing: Es ist für Einsteiger einfacher und kommt in Lernbeispielen häufiger vor. Schauen wir uns an, wie die Ereignisverarbeitung funktioniert, am Beispiel der beliebtesten Aufgabe – der Reaktion auf einen Schaltflächenklick.
Schaltfläche erstellen
In Swing wird eine Schaltfläche so erstellt:
JButton button = new JButton("Klick mich!");
Hier ist „Klick mich!“ die Beschriftung auf der Schaltfläche.
Listener hinzufügen
Um auf den Klick zu reagieren, müssen Sie einen Listener auf das Ereignis „abonnieren“:
button.addActionListener(new MyActionListener());
Aber wer ist dieser MyActionListener? Das ist eine Klasse, die das Interface ActionListener implementiert:
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class MyActionListener implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("Die Schaltfläche wurde gedrückt!");
}
}
Wenn der Benutzer die Schaltfläche klickt, ruft Swing die Methode actionPerformed bei allen Listenern auf, die über addActionListener registriert wurden.
Komplettes Beispiel: einfaches Fenster mit Schaltfläche
Fassen wir alles zusammen und erstellen eine lauffähige Anwendung:
import javax.swing.*;
import java.awt.event.*;
public class SimpleButtonApp {
public static void main(String[] args) {
JFrame frame = new JFrame("Ereignisbeispiel");
JButton button = new JButton("Klick mich!");
// Listener zur Schaltfläche hinzufügen
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("Die Schaltfläche wurde gedrückt!");
JOptionPane.showMessageDialog(frame, "Hurra! Die Schaltfläche wurde gedrückt!");
}
});
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(button);
frame.setSize(300, 200);
frame.setVisible(true);
}
}
Was hier passiert:
- Wir erstellen ein Fenster (JFrame).
- Wir erstellen eine Schaltfläche (JButton).
- Wir fügen einen Listener hinzu (anonyme Klasse, um keine separaten Dateien zu erzeugen).
- In actionPerformed geben wir eine Meldung in der Konsole und ein Popup-Fenster (JOptionPane) aus.
- Wir fügen die Schaltfläche dem Fenster hinzu und zeigen das Fenster dem Benutzer.
Probieren Sie es selbst: Kopieren Sie diesen Code, starten Sie ihn – und klicken Sie auf die Schaltfläche!
2. Anonyme Klassen und Lambda-Ausdrücke
Im modernen Java-Code für einen einzigen Handler separate Klassen zu schreiben, ist wie mit einem Panzer zum Bäcker zu fahren. Viel bequemer sind anonyme Klassen oder Lambda-Ausdrücke.
Anonyme Klasse
Diese Herangehensweise haben wir oben bereits gesehen:
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
// Verarbeitungs-Code
}
});
Hier wird die Klasse direkt an Ort und Stelle ohne Namen deklariert.
Lambda-Ausdruck (Java 8+)
Wenn das Listener-Interface funktional ist (also nur eine einzige abstrakte Methode enthält), kann eine Lambda verwendet werden:
button.addActionListener(e -> {
System.out.println("Schaltfläche per Lambda geklickt!");
});
Oder noch kürzer, wenn der Rumpf einzeilig ist:
button.addActionListener(e -> System.out.println("Lambda: Schaltfläche geklickt!"));
Das verkürzt nicht nur den Code, sondern macht ihn auch lesbarer. Fakt für Neugierige: Das Interface ActionListener ist funktional, weil es nur eine Methode actionPerformed enthält.
4. Weitere Ereignistypen
Die Welt besteht nicht nur aus Schaltflächen! In Swing und AWT unterstützt nahezu jedes UI-Element seine eigenen Ereignisse. Hier die beliebtesten:
Mausereignisse: MouseListener und MouseAdapter
Wenn Sie auf Mausklicks reagieren möchten, verwenden Sie MouseListener:
button.addMouseListener(new MouseListener() {
@Override
public void mouseClicked(MouseEvent e) {
System.out.println("Mausklick auf die Schaltfläche!");
}
// Die übrigen Methoden können leer bleiben
@Override public void mousePressed(MouseEvent e) {}
@Override public void mouseReleased(MouseEvent e) {}
@Override public void mouseEntered(MouseEvent e) {}
@Override public void mouseExited(MouseEvent e) {}
});
Fünf leere Methoden für einen einzigen Handler zu schreiben, ist nicht besonders bequem. Dafür gibt es den MouseAdapter:
button.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
System.out.println("Mausklick auf die Schaltfläche (über den Adapter)!");
}
});
MouseAdapter implementiert alle Methoden des Interfaces, und Sie überschreiben nur die benötigten.
Tastaturereignisse: KeyListener/KeyAdapter
Wenn Sie Tastendrücke abfangen möchten:
button.addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
System.out.println("Taste gedrückt: " + e.getKeyChar());
}
});
Texteränderungsereignisse: DocumentListener
Für Textfelder (JTextField, JTextArea) gibt es spezielle Listener:
JTextField textField = new JTextField();
textField.getDocument().addDocumentListener(new DocumentListener() {
@Override
public void insertUpdate(DocumentEvent e) {
System.out.println("Text geändert: " + textField.getText());
}
@Override public void removeUpdate(DocumentEvent e) {}
@Override public void changedUpdate(DocumentEvent e) {}
});
Übrigens gibt es für die meisten Ereignisse Adapter, damit Sie keine leeren Methoden schreiben müssen.
5. Praxis: Mini-Beispiel mit Fenster und Schaltfläche
Erstellen wir eine einfache GUI-Anwendung: ein Fenster mit einer Schaltfläche, die beim Klicken einen Zähler erhöht und eine Meldung ausgibt.
Beispiel: Klickzähler
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class ClickCounterApp {
public static void main(String[] args) {
JFrame frame = new JFrame("Klickzähler");
JButton button = new JButton("Klick mich!");
JLabel label = new JLabel("Klicks: 0");
// Klickzähler (muss final oder effectively final sein)
final int[] count = {0};
button.addActionListener(e -> {
count[0]++;
label.setText("Klicks: " + count[0]);
});
frame.setLayout(new FlowLayout());
frame.add(button);
frame.add(label);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(250, 100);
frame.setVisible(true);
}
}
Erklärung:
- Wir erstellen ein Fenster, eine Schaltfläche und ein Label zur Anzeige des Zählers.
- Wir verwenden ein Array mit einem Element (final int[] count = {0};), um die Einschränkung von Lambdas auf final/effectively final Variablen zu umgehen.
- Bei jedem Klick auf die Schaltfläche erhöhen wir den Zähler und aktualisieren den Text des Labels.
Idee für die Praxis: Versuchen Sie, bei jedem fünften Klick die Farbe der Schaltfläche zu ändern!
6. Wie das Ereignismodell intern funktioniert
Schauen wir kurz unter die Haube. Wenn Sie Folgendes aufrufen:
button.addActionListener(listener);
fügt die Schaltfläche (Objekt JButton) Ihren Listener einer internen Liste hinzu. Wenn der Benutzer die Schaltfläche klickt, passiert intern Folgendes:
- Die Schaltfläche erzeugt ein Ereignisobjekt (ActionEvent).
- Die Schaltfläche iteriert über die Liste der Listener und ruft bei jedem die Methode actionPerformed auf.
- Ihr Handler führt die nötigen Aktionen aus (zum Beispiel aktualisiert er das Label).
Wichtig: Jedes Ereignis kann beliebig viele Listener haben – Sie können mehrere Handler für eine Schaltfläche hinzufügen, sie werden der Reihe nach aufgerufen.
7. Visuelles Schema: Ereignisverarbeitung
graph TD
A[Benutzer hat die Schaltfläche geklickt] --> B{JButton}
B --> C[ActionEvent erzeugen]
C --> D[actionPerformed bei allen Listenern aufrufen]
D --> E[Handler führt Aktionen aus]
7. Typische Fehler bei der Arbeit mit Ereignissen in Swing und AWT
Fehler Nr. 1: Listener zu registrieren vergessen. Die Schaltfläche reagiert nicht auf Klicks, weil Sie addActionListener nicht aufgerufen haben.
Fehler Nr. 2: aufwändige Arbeit im Handler. Wenn Sie im Ereignishandler eine lange Schleife starten oder etwas aus dem Internet herunterladen, friert die Oberfläche ein. Für lang laufende Aufgaben verwenden Sie separate Threads oder SwingWorker.
Fehler Nr. 3: Versuch, eine Variable in einer Lambda ohne final/effectively final zu ändern. Innerhalb einer Lambda können nur final oder „effectively final“ Variablen verwendet werden. Für Zähler verwenden Sie Arrays oder spezielle Klassen (AtomicInteger, wenn Sie es „erwachsener“ möchten).
Fehler Nr. 4: Listener nicht entfernt. Wenn Sie eine Komponente entfernen, den Listener aber nicht abmelden, kann es zu einem Speicherleck kommen. Entfernen Sie Abonnements, wenn die Komponente nicht mehr benötigt wird.
Fehler Nr. 5: Nicht alle Methoden des Interfaces implementiert. Wenn Sie zum Beispiel MouseListener direkt implementieren, vergessen Sie nicht alle Methoden! Verwenden Sie besser Adapter (MouseAdapter, KeyAdapter).
GO TO FULL VERSION