Axel Rogat
Objektorientiertes Programmieren mit C++ und JAVA
 
32.1: Events in JAVA 1.0 Kapitel 32 33.1: Buttons 
 
  32.2 Events in JAVA 1.1  
 

Ab Version 1.1 ist man einen völlig anderen Weg zur Auswertung von Ereignissen gegangen. Es gibt eine Vielzahl verschiedener Event-Klassen, eine für jedes Ereignis. Alle sind von EventObject aus java.util abgeleitet, die uns interessierenden von der Unterklasse AWTEvent aus java.awt.

EventObject dient nur dazu, den Urheber des Ereignisses zu codieren:

public class EventObject implements java.io.Serializable { public EventObject(Object source); public Object getSource(); public String toString(); }
AWTEvent ist abstrakt, speichert aber schon Informationen über die Art des Ereignisses, die man mit getID abfragen kann:
public abstract class AWTEvent extends EventObject { public AWTEvent(Event event); public AWTEvent(Object source, int id); Event convertToOld(); public int getID(); protected void consume(); protected boolean isConsumed(); // ... }
Es werden nicht mehr alle Ereignisse an alle Komponenten gemeldet (und dort ggf. ignoriert). Stattdessen müssen die Komponenten, die an bestimmten Ereignissen interessiert sind, ihr Interesse anmelden. Eine solche Komponente heißt Event Listener, eine erzeugende Komponente Event Source. Source ist beim AWT immer eine AWT-Component. Man kann das Interesse wieder abmelden oder nachträglich anmelden.

Wenn ein Ereignis eintritt, ruft die Source-Component eine (Event-abhängige) bestimmte Methode in jedem angemeldeten Listener auf. Dazu müssen die Listener ein jeweils spezielles Interface implementieren. Diese Interfaces sind alle von java.util.EventListener abgeleitet und im Package java.awt.event gesammelt.

Einige Interfaces fordern nur eine einzige Methode, die meisten mehrere. Beispielsweise muß MouseListener implementiert werden, wenn man über Maus-Ereignisse informiert werden will:

public interface MouseListener extends EventListener { public void mouseClicked(MouseEvent e); public void mousePressed(MouseEvent e); public void mouseReleased(MouseEvent e); public void mouseEntered(MouseEvent e); public void mouseExited(MouseEvent e); }
Wenn man MouseListener implementiert, muß man alle fünf Methoden implementieren. Wenn man beispielsweise nur an einfachen Mausklicks interessiert ist, kann man besser eine Adapter-Klasse aus java.awt.event beerben, in der alle fünf Methoden leer implementiert sind:
public abstract class MouseAdapter implements MouseListener { public void mouseClicked(MouseEvent e) {} public void mousePressed(MouseEvent e) {} public void mouseReleased(MouseEvent e) {} public void mouseEntered(MouseEvent e) {} public void mouseExited(MouseEvent e) {} }
Man überschreibt dann nur die eine Methode, die einen interessiert, etwa mouseClicked(). Für alle Listener mit mehr als einer Methode gibt es eine solche Adapter-Klasse mit entsprechendem Namen.

In der folgenden Tabelle sind die Event-Klassen, die zugehörigen Interfaces und die darin enthaltenen Methoden aufgeführt.

Event-Klasse Listener-Interface Methoden im Listener
ActionEvent ActionListener actionPerformed()
AdjustmentEvent AdjustmentListener adjustmentValueChanged()
ComponentEvent ComponentListener componentHidden(), componentMoved(), componentResized(), componentShown()
ContainerEvent ContainerListener componentAdded(), componentRemoved()
FocusEvent FocusListener focusGained(), focusLost()
ItemEvent ItemListener itemStateChanged()
KeyEvent KeyListener keyPressed(), keyReleased(), keyTyped()
MouseEvent MouseListener mouseClicked(), mousePressed(), mouseReleased(), mouseEntered(), mouseExited()
  MouseMotionListener mouseDragged(), mouseMoved()
TextEvent TextListener textValueChanged()
WindowEvent WindowListener windowActivated(), windowClosed(), windowClosing(), windowDeactivated(), windowOpened(), windowIconified(), windowDeiconified()

Das Registrieren dieser Listener läuft wie folgt ab: Jede Komponente, die ein bestimmtes Ereignis auslösen kann, stellt eine addListener- und eine removeListener-Methode zur Verfügung. Beispielsweise löst die Komponente Scrollbar einen AdjustmentEvent aus, wenn der Benutzer sie bedient. Entsprechend definiert Scrollbar folgende Methoden:

void addAdjustmentListener(AdjustmentListener); void removeAdjustmentListener(AdjustmentListener);
Beispiel: Wir implementieren zunächst eine Fensterklasse, die an die Stelle eines Mausklicks einen kleinen Kreis zeichnet. Das Beispiel kann dann leicht so erweitert werden, daß es sich wie unser Applet aus
Abschnitt 32.1 verhält.

Wir arbeiten hier ohne Pufferung, d.h. die Punkte verschwinden, sobald das Fenster verdeckt oder in der Größe geändert wird. Wenn beim Klicken die Shift-Taste gedrückt ist, wird das Fenster geschlossen und das Programm damit beendet.

Wir leiten unsere Klasse von Frame ab und können daher nicht auch von MouseAdapter erben, müssen also alle fünf Funktionen aus MouseListener implementieren.

import java.awt.*; import java.awt.event.*; class ClickMeFrame extends Frame implements MouseListener { ClickMeFrame(int w, int h) { super("Click me!"); setSize(w,h); // d.h. Component.setSize() addMouseListener(this); // d.h. Component.addMouseListener() show(); // d.h. Window.show() } public void mousePressed(MouseEvent e) { if (e.isShiftDown()) dispose(); // d.h. Frame.dispose() else { Graphics g=getGraphics(); // d.h. Component.getGraphics() g.setColor(Color.black); g.fillOval(e.getX(),e.getY(),10,10); } } public void mouseClicked(MouseEvent e) { } public void mouseReleased(MouseEvent e) { } public void mouseEntered(MouseEvent e) { } public void mouseExited(MouseEvent e) { } } public class listen { public static void main(String[] args) { new ClickMeFrame(256,256); } }
Die Listener lassen sich am elegantesten mit anonymen Klassen implementieren. Es ist eben oft der Fall, daß spezielle Unterklassen der Adapter nur ein einziges Mal benötigt werden. Man definiert sie deshalb am besten direkt im Aufruf von addMouseListener().

Beispiel: Wir leiten hier von MouseAdapter ab und überschreiben nur mousePressed(). Unsere Unterklasse bekommt keinen Namen und wird direkt im new-Aufruf ihres einzigen Objekts definiert. Die Oberklasse wird hinter new angegeben, die überschreibenden Methoden in einem normalen Klassen-Rumpf.

class ClickMeFrame extends Frame { ClickMeFrame(int w, int h) { super("Click me!"); setSize(w,h); addMouseListener ( new MouseAdapter() { public void mousePressed(MouseEvent e) { if (e.isShiftDown()) dispose(); else { Graphics g=getGraphics(); g.setColor(Color.black); g.fillOval(e.getX(),e.getY(),10,10); } } } ); show(); } }
Beispiel: Wir erzeugen mehrere Instanzen unserer Fensterklasse, im Testprogramm vier Stück. Wir übergeben dem Konstruktor daher nun zusätzlich die Startposition.

Wir wollen nun statt mit Shift-Maustaste für das Schließen eines Fensters korrekt auf den (systemabhängigen) Schließ-Knopf reagieren. Wir fügen dazu jedem Frame einen WindowListener hinzu, indem wir von WindowAdapter ableiten und nur windowClosing() überschreiben.

Unser Listener schließt auf jeden Fall den Frame. Außerdem wird intern mitgezählt, wieviele Fenster noch offen sind. Falls unser Fenster das letzte war, beenden wir das Programm mit System.exit().

Als Erweiterung bauen wir noch vier Buttons ein, mit denen man die Farbe der Kreise einstellen kann. Den Buttons fügen wir einen ActionListener hinzu.

Wenn ein Farbknopf betätigt wird, speichern wir seine Nummer im Datenmember numcol. Dazu muß der ActionListener die Nummer des Knopfs kennen. Die Knöpfe werden in einer Schleife über i erzeugt.

Damit die anonyme Klasse auf dieses i zugreifen kann, legen wie eine finale Kopie icopy im Schleifenrumpf an.

class ClickMeFrame extends Frame { static String text[]={ "black","red","green","blue" }; Color colors[]=new Color[] { Color.black, Color.red, Color.green, Color.blue }; int numcol; static int count=0; ClickMeFrame(int x, int y, int w, int h) { super("Click me!"); setLayout(new FlowLayout()); addWindowListener ( new WindowAdapter() { public void windowClosing(WindowEvent e) { dispose(); if (--count==0) System.exit(0); } } ); for (int i=0;i<=3;++i) { final int icopy=i; Button b=new Button(text[i]); b.addActionListener ( new ActionListener() { public void actionPerformed(ActionEvent e) { numcol=icopy; } } ); add(b); } addMouseListener ( new MouseAdapter() { public void mousePressed(MouseEvent e) { Graphics g=getGraphics(); g.setColor(colors[numcol]); g.fillOval(e.getX()-5,e.getY()-5,10,10); } } ); setSize(w,h); setLocation(x,y); show(); ++count; } } public class listen4 { public static void main(String[] args) { new ClickMeFrame(0,0,256,256); new ClickMeFrame(260,0,256,256); new ClickMeFrame(0,260,256,256); new ClickMeFrame(260,260,256,256); } }
Im Moment haben wir jedem Button einen eigenen ActionListener spendiert. Man kann auch mit einem einzigen auskommen. Dieser muß dann feststellen, welcher Button den Event ausgelöst hat. Dazu kann er beispielsweise die Beschriftung des Buttons auslesen -- sie wird mit in den Event gepackt und kann mit getActionCommand() herausgelesen werden:
ActionListener al=new ActionListener() { public void actionPerformed(ActionEvent e) { String s=e.getActionCommand(); if (s.equals("black")) numcol=0; else if (s.equals("red")) numcol=1; else if (s.equals("green")) numcol=2; else numcol=3; } }; for (int i=0;i<=3;++i) { Button b=new Button(text[i]); b.addActionListener(al); add(b); }
Es gibt in JAVA 1.1 noch ein anderes Event-Model, das nicht für die Applikations-Ebene (also für Fenster oder Applets) gedacht ist, sondern für die Implementation von Komponenten wie speziellen Knöpfen, Menüs, etc. Die Handhabung wäre mit dem "großen" Modell manchmal zu umständlich.

Mit enableEvents() kann man für die eigene Komponente eine Bitmaske angeben, die bestimmt, welche Arten von Events überhaupt bei ihr ankommen sollen.

Dann fängt man die AWTEvents ab, bevor sie bei den Listenern ankommen, indem man die Methode processKeyEvent(), processMouseEvent(), etc. der Komponente überschreibt. Dort findet sonst das Verteilen auf die Listener statt. Wenn man nichts mit dem Event anfangen kann, gibt man ihn mit super. an die Methode der Oberklasse weiter.

 
32.1: Events in JAVA 1.0 Startseite 33.1: Buttons 
 

© 1998 Axel Rogat