Inhalt |
|
|
|
|
|
|
Wie wir schon im Kapitel über Fenster gesehen haben, beruht die Verwaltung einer Benutzeroberfläche auf dem Konzept des Ereignisses. Ein Ereignis ist normalerweise eine von Benutzer angestoßene Aktion, wie ein Tastendruck oder ein Mausklick. Andere Systeme stellen aber auch andersartige Ereignisse zur Verfügung, wie etwa einen Timer, der in gewissen Intervallen ein Ereignis auslöst. Eine andere Möglichkeit wäre die Ankunft eines Datenpaketes aus dem Internet.
Es stellt sich die Frage, wann Events verarbeitet werden. Es gibt dabei drei Möglichkeiten
Wir haben bisher nur eine Event-Verarbeitung kennen gelernt, nämlich die Verarbeitung des Repaint-Ereignisses in einer graphischen Komponenten. Dies geschah dadurch, dass die paint-Methode überlagert wurde. Diese Art der Verarbeitung durch Überlagerung wird in Java nur noch sehr restriktiv eingesetzt.
Die Ereignisbehandlung von Java beruht auf Objekten, die die Ereignisse entgegennehmen können. Diese Objekte heißen Event-Listener. Sie implementieren gewisse Interfaces und damit gewisse Methoden, die die Ereignisse behandeln. Außerdem müssen sich die Objekte bei den Event-Erzeugern anmelden. Es können sich dabei aber mehrere Listener bei einem Erzeuger anmelden.
Als Nebeneffekt des Listener-Modells wird die Implementation der Oberflächenelemente und ihrer Anordnung von der Programmlogik, die ja in der Verarbeitung der Ereignisse liegt, getrennt. Man kann damit das Aussehen des Programms und seine Funktionalität getrennt betrachten.
Als Beispiel behandeln wir das Ereignis, dass ein Knopf gedrückt wird. Dazu wird ein JButton in ein Fenster eingefügt. Als Event-Listener fungiert eine Methode, die das Programm beendet.
import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import javax.swing.JButton; import javax.swing.JFrame; class CloseButton extends JButton implements ActionListener { public CloseButton () { super("Close"); addActionListener(this); } @Override public void actionPerformed (ActionEvent e) { System.exit(0); } } public class ButtonTest { public static void main (String args[]) { JFrame F = new JFrame(); F.setTitle("Test"); F.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // Als Action Listener registrieren: F.add(new CloseButton()); // Anzeigen: F.pack(); F.setLocationRelativeTo(null); F.setVisible(true); } }
Wie man sieht, schafft dieses Beispiel eine neue Instanz eines ActionListener, um auf das Drücken des Button zu reagieren. Dazu muss diese Instanz als ActionListener beim Button registriert werden. Falls nun der Knopf gedrückt wird, so benachrichtigt er alle registrierten ActionListener davon, indem er deren Methode actionPerformed() aufruft. Als Parameter wird eine Instanz von ActionEvent übergeben, die nähere Informationen zum Ereignis enthält.
Ein Knopf kann mehrere ActionListener bedienen. Ein ActionListener kann sich mit Hilfe der Funktion removeActionListener() wieder aus der Liste der Ereignisempfänger entfernen.
Man sieht an diesem Beispiel auch, wie der Aufbau der Oberfläche von der Ereignisbehandlung getrennt wird. Schließlich kann jede Klasse als Listener fungieren, der das Ereignis abarbeitet.
Das Beispiel lässt sich auch anders organisieren, indem man eine eigene JFrame-Klasse schreibt. Diese kann sich dann selber als EventListener eintragen, und in einer Routine die Quelle des Events abfragen. Das sieht so aus.
import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import javax.swing.JButton; import javax.swing.JFrame; class TestFrame extends JFrame implements ActionListener { JButton Close; // Der Schließen-Knopf /** * Initialisiere das Fenster. Es enthält nur einen Schließen-Knopf. */ public TestFrame () { setTitle("Test"); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // Knopf einfügen: Close = new JButton("Close"); Close.addActionListener(this); add(Close); pack(); } /** * Der ActionListener, derzeit nur für den Knopf */ @Override public void actionPerformed (ActionEvent e) { if (e.getSource() == Close) { dispose(); } } } public class Test { public static void main (String args[]) { JFrame F = new TestFrame(); F.setLocationRelativeTo(null); F.setVisible(true); } }
Das Hauptprogramm erzeugt nun lediglich eine Instanz von TestFrame. Diese Klasse übernimmt alles selbst und verwaltet auch die Ereignisse ihrer Knöpfe. Da wir eventuell mehrere Knöpfe einfügen möchten, testen wir, woher das Ereignis kommt.
Als Beispiel für Mausereignisse reagieren wir auf das Anklicken im Innern eines TestCanvas. Es soll nur einfach die Koordinate des Klicks ausgegeben werden.
Das Interface heißt hier MouseListener und implementiert fünf Methoden. Wir registrieren das Canvas selber als MouseListener. Das Event heißt MouseEvent und hat z.B. die Methoden getX() und getY(), womit die Koordinaten des Klicks abgefragt werden können.
In diesem Beispiel registriert sich wieder die Komponente selbst als Event-Listener.
import java.awt.Canvas;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import javax.swing.JFrame;
class TestCanvas extends Canvas implements MouseListener
{
public TestCanvas ()
{
addMouseListener(this);
}
// Methoden von MouseListener
@Override
public void mousePressed (MouseEvent e)
{
System.out.println(e);
}
@Override
public void mouseReleased (MouseEvent e)
{
System.out.println(e);
}
@Override
public void mouseClicked (MouseEvent e)
{
System.out.println(e);
System.out.println(
"Mausklick bei " + e.getX() + "," + e.getY());
}
@Override
public void mouseEntered (MouseEvent e)
{
System.out.println(e);
}
@Override
public void mouseExited (MouseEvent e)
{
System.out.println(e);
}
}
public class Test
{
public static void main (String args[])
{
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(new TestCanvas());
f.setSize(300, 300);
f.setVisible(true);
}
}
Man sieht, wenn man dieses Beispiel laufen lässt, dass mouseClicked() nur aufgerufen wird, wenn die Maus an derselben Stelle gedrückt und losgelassen wurde. Außerdem sieht man, dass es schwer ist, Doppelklicks zu entdecken. Die dazu nötigen Techniken müssen wir auf später verschieben.
Will man die Bewegung der Maus verfolgen, so benötigt man einen MouseMotionListener. Die Technik ist ziemlich ähnlich. Entnehmen Sie daher die entsprechenden Klassen der Dokumentation zu Java. Will man Zeichenroutinen mit diesen Events verbinden, so kann man repaint() verwenden. Es ist allerdings unter Umständen nötig, einen Grafikpuffer einzurichten und die gezeichnete Grafik als Ganzes zu übertragen. Diese Techniken werden wir erst später behandeln.
Tastaturereignisse werden von dem KeyListener-Interface behandelt. Man hat drei Methoden zu implementieren. Hier wieder ein Beispiel-Code.
import java.awt.Canvas;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
class TestCanvas extends Canvas implements KeyListener
{
public TestCanvas ()
{
addKeyListener(this);
}
// Methoden von KeyListener:
public void keyPressed (KeyEvent e)
{
if (e.isActionKey())
System.out.println("Action-Key : " + e.getKeyCode());
else if (e.getKeyCode() == KeyEvent.VK_DELETE) // Bug?
System.out.println("Action-Key : " + e.getKeyCode());
}
public void keyReleased (KeyEvent e)
{
}
public void keyTyped (KeyEvent e)
{
System.out.println("Character : " + e.getKeyChar());
}
}
Wie man sieht, erzeugen normale Buchstabentasten keyTyped()-Aufrufe und Sondertasten nicht. Diese fängt man mit keyPressed() ab. Durch einen Fehler wird die Delete-Taste nicht als Action-Taste erkannt.
Die Codes der Action-Tasten sind alle in KeyEvent dokumentiert. Beipiele sind VK_F1 usw.
Als weiteres Beispiel für einen Event-Listener behandeln wir WindowListener. Dieses Interface dient dazu, auf Ereignisse zu reagieren, die den Status des Fensters betreffen. Es implementiert eine ganze Reihe von Methoden. Die Klasse TestFrame muss alle diese Methoden implementieren, obwohl nur eine benötigt wird. Diese eine Methode ist windowClosing() und wird aufgerufen, wenn der Benutzer den Schließknopf im Fensterrahmen betätigt, oder das Betriebssystem das Fenster schließen will.
import java.awt.event.WindowEvent; import java.awt.event.WindowListener; import javax.swing.JFrame; class Test extends JFrame implements WindowListener { public Test () { super("Test Frame"); setSize(300, 300); addWindowListener(this); } // Methoden von WindowListener: @Override public void windowActivated (WindowEvent e) { } @Override public void windowClosed (WindowEvent e) { } @Override public void windowClosing (WindowEvent e) { // Schließe das Fenster und beende das Programm: dispose(); System.exit(0); } @Override public void windowDeactivated (WindowEvent e) { } @Override public void windowDeiconified (WindowEvent e) { } @Override public void windowIconified (WindowEvent e) { } @Override public void windowOpened (WindowEvent e) { } public static void main (String args[]) { JFrame F = new Test(); F.setVisible(true); } }
Wir haben hier eine weitere Methode gewählt, um unser Programm zu testen. Die Fensterklasse ist hier Test selber, und sie enthält auch das Hauptprogramm main. Das ist eine gute Art, Testroutinen in Klassen, einzubauen.
Es gibt eine ganze Reihe weiterer Event-Listener-Interfaces, die alle auf Ereignisse spezieller Art warten (Mausereignisse, Tastatur-Eingaben usw.). Hier ist eine Liste.
Event-Klasse |
Interface |
Methoden |
Quellen |
---|---|---|---|
ActionEvent |
ActionListener |
actionPerformed() |
Button |
AdjustmentEvent |
AdjustementListener |
adjustmentValueChanged() |
Scrollbar |
ComponentEvent |
ComponentListener |
componentHidden() |
Component |
ContainerEvent |
ContainerListener |
componentAdded() |
Container |
FocusEvent |
FocusListener |
focusGained() |
Component |
ItemEvent |
ItemListener |
itemStateChanged() |
Checkbox |
KeyEvent |
KeyListener |
keyPressed() |
Component |
MouseEvent |
MouseListener |
mouseClicked() |
Component |
|
MouseMotionListener |
mouseDragged() |
Component |
TextEvent |
TextListener |
textValueChanged() |
TextComponent |
WindowEvent |
WindowListener |
windowActivated() |
Window |
Viele Event-Quellen, die hier angegeben sind, sagen uns noch nichts. Man beachte aber, dass Frame von Window abstammt und damit auch als Quelle eines WindowEvent in Frage kommt. Genauso stammen alle Komponenten, die wir bisher besprochen haben (Canvas, Panel etc.), von Component ab. Entsprechendes gilt für die Swing-Klassen mit vorangestelltem J, die meist dieselben Ereignisse erzeugen können.
Damit eine Klasse nicht durch zu viele Methoden eines Interface aufgebläht wird, gibt es Adapter-Klassen. Diese Klassen implementieren das Interface mit leeren Methoden. Man braucht nur noch die entsprechende Methode zu überschreiben.
Im folgenden Beispiel verwenden wir den WindowAdapter, der das WindowListener-Interface implementiert. Wir überschreiben lediglich die Methode windowClosing().
import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import javax.swing.JFrame; class Closer extends WindowAdapter { @Override public void windowClosing (WindowEvent e) { System.exit(0); } } class Test extends JFrame { String Close = "Close"; public Test () { super("Test Frame"); setSize(300, 300); addWindowListener(new Closer()); } public static void main (String args[]) { JFrame F = new Test(); F.setVisible(true); } }
Noch einfacher wird dies, wenn man Closer als innere Klasse deklariert. Dazu muss man einfach die Deklaration von Closer in die Klasse TestFrame kopieren. Der innere Zusammenhang wird dann klar. Außerdem kann man die Variablen von TestFrame verwenden.
class TestFrame extends Frame implements ActionListener { ... class Closer extends WindowAdapter { public void windowClosing (WindowEvent e) { dispose(); System.exit(0); } } ... }
Alles andere bleibt gleich. Man beachte, dass die innere Klasse auf die Variablen der umgebenden Klasse (in diesem Fall auf Close) zugreifen kann.
Die Adapter für die anderen Events heißen entsprechend MouseAdapter, KeyAdapter etc.
Es geht aber noch prägnanter. Dazu braucht man überhaupt keine Klasse mehr zu deklarieren, sondern verwendet anonyme Klassen. Die Deklaration erfolgt gleich im new-Statement. Dies ist eine sehr kompakte Möglichkeit, Kinderklassen zu deklarieren und gleich eine Instanz anzulegen. Es ist klar, dass man diese Klassendefinition nicht nochmals verwenden kann.
Die Instanz der anyonymen Klasse kann auf die Variablen der Klasse zugreifen, in der sie deklariert wurde, genau wie bei einer lokal definierten innderen Klasse.
import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import javax.swing.JFrame; class Test extends JFrame { String Close = "Close"; public Test () { super("Test Frame"); setSize(300, 300); addWindowListener( new WindowAdapter() { @Override public void windowClosing (WindowEvent e) { dispose(); System.exit(0); } }); } public static void main (String args[]) { JFrame F = new Test(); F.setVisible(true); } }
Dieses Beispiel zeigt eine anonyme innere Klasse, nämlich ein Kind von WindowAdapter. Die Syntax ist
new VaterKlasse (KonstruktorParameter) Klassendefinition