Interdisziplinäres Hauptpraktikum für Studierende der Elektrotechnik

Versuch 19: Komponentenbasierte Programmierung in Java


Events & Listener


Ein wichtiger Bestandteil der Programmierung von Oberflächen ist das Verarbeiten von Ereignissen, die durch Aktionen des Benutzers ausgelöst werden. Beispiele von Aktionen, die Ereignisse auslösen, sind das Anklicken eines Buttons, das Auswählen eines Eintrages in einer Auswahlliste usw. Auf diese Ereignisse soll das Applet reagieren. Zu diesem Zweck erlaubt es das Event-Modell von Java, sogenannte Listener zu spezifizieren. Deren Aufgabe ist es, auf bestimmte Ereignisse zu warten (zu lauschen) und im Fall des Auftretens des Ereignisses eine bestimmte Aktion auszuführen.

Wie definiere ich einen geeigneten Listener?

Ausgangspunkt ist eine Komponente y des Applets, für die auf ein bestimmtes Ereignis reagiert werden soll. Zu jeder Komponente eines Applets und zum Applet selbst ist eine Menge von Ereignissen definiert, die zu dieser Komponente auftreten können. Zu einem oder mehreren dieser Ereignisse kann ein Listener implementiert und an das Element angehängt werden. Dazu gibt es vordefinierte Schnittstellen, die Listener-Schnittstellen, die festlegen, welche Methoden in diesem Fall zu implementieren sind. Im folgenden wird Schritt für Schritt die Vorgehensweise bei der Definition eines geeigneten Listeners beschrieben:

  1. Suche in der Tabelle 1 zu der Komponente y das Ereignis XEvent, auf das reagiert werden soll (z.B. ActionEvent).
  2. Durch Anklicken des Ereignisses gelangst Du in Tabelle 2, in der Du die zugehörigen Listener-Schnittstelle findest (XListener). Dann sind zwei Fälle zu unterscheiden:
    1. Die Schnitstelle hat nur eine Methode (wie z.B. ActionListener): In diesem Fall definiere eine neue Listener-Klasse, die diese Schnittstelle implementiert. Dazu muß im wesentlichen die Methode der Schnittstelle realisiert werden. Die Listener-Klasse wird als innere Klasse des Applets definiert, d.h. als Klasse in der Klasse. Für das Beispiel ActionListener sieht das wie folgt aus, wobei MyListener die neu definierte innere Klasse ist.
          ... // inside the applet
          class MyListener implements ActionListener {
      
              public void actionPerformed (ActionEvent e) {
                  // define what should be done when the event occurs
              } 
      
          }// class MyListener
      
          ... // rest of the applet

      Die Reaktion auf das Anklicken eines Buttons wird z.B. auf diese Weise realisiert (siehe Beispiel).

    2. Die Schnittstelle hat mehr als eine Methode (z.B. MouseListener): In diesem Fall gibt es zusätzlich eine zugehörige Adapter-Klasse. Die Adapter-Klasse sieht Standardimplementierungen zu allen Methoden vor und vermeidet somit, daß man alle Methoden des Listeners selbst implementieren muß. Es genügt also wiederum, nur die Methode zu implementieren, die man aktuell braucht. Diesmal wird allerdings eine innere Klasse implementiert, die von der Adapter-Klasse erbt.
          ... // inside of the applet
          class MyMouseListener
              extends MouseAdapter
          {   
              public void mousePressed (MouseEvent m) {
                  // define what should be done when the event occurs
              }// mousePressed
          }
          ... // rest of the applet
  3. Nach diesem Schritt hat man eine innere Klasse MyXListener.

  4. Erzeuge ein Objekt von der neu definierten Klasse MyXListener und spezifiziere es innerhalb der init-Methode des Applets als Listener für die Komponente y. Dies geschieht durch einen Aufruf einer Methode addXListener, wobei X die Art des Events beschreibt (z.B. addActionListener oder addMouseListener). Für ein ActionEvent sieht das wie folgt aus:
        public void init () {
            add (y) ; 
            ...
            y.addActionListener (new ChangeButtonListener ()) ;
        }
  5. Erweitere die import-Liste des Applets um die Klausel:
        import java.awt.event.* ;
        

 

Tabelle 1: Elemente und die zugehörigen Ereignisse (Events)

Applet, Panel Button TextField TextArea
ContainerEvent
FocusEvent,
KeyEvent,
MouseEvent,
ComponentEvent
ActionEvent,
FocusEvent,
KeyEvent,
MouseEvent,
ComponentEvent
ActionEvent,
TextEvent,
FocusEvent,
KeyEvent,
MouseEvent,
ComponentEvent
TextEvent,
FocusEvent,
KeyEvent,
MouseEvent,
ComponentEvent
Label Checkbox Choice List
FocusEvent,
KeyEvent,
MouseEvent,
ComponentEvent
ItemEvent,
FocusEvent,
KeyEvent,
MouseEvent,
ComponentEvent
ItemEvent,
FocusEvent,
KeyEvent,
MouseEvent,
ComponentEvent
ActionEvent,
ItemEvent,
FocusEvent,
KeyEvent,
MouseEvent,
ComponentEvent

Weitere Ereignisklassen, die hier nicht betrachtet werden sind AdjustmentEvent und WindowEvent.

Tabelle 2: Ereignisse und die zugehörigen Listener-Schnittstellen (und Adapter-Klassen)

Ereignis Listener-Schnittstelle Adapter-Klasse Methoden der Schnittstelle
ActionEvent ActionListener keine actionPerformed(ActionEvent)
ComponentEvent ComponentListener ComponentAdapter componentHidden(ComponentEvent)
componentMoved(ComponentEvent)
componentResized(ComponentEvent)
componentShown(ComponentEvent)
ContainerEvent ContainerListener ContainerAdapter componentAdded(ContainerEvent)
componentRemoved(ContainerEvent)
FocusEvent FocusListener FocusAdapter focusGained(FocusEvent)
focusLost(FocusEvent)
KeyEvent KeyListener KeyAdapter keyPressed(KeyEvent)
keyReleased(KeyEvent)
keyTyped(KeyEvent)
MouseEvent MouseListener MouseAdapter mouseClicked(MouseEvent)
mouseEntered(MouseEvent)
mouseExited(MouseEvent)
mousePressed(MouseEvent)
mouseReleased(MouseEvent)
MouseMotionListener MouseMotionAdapter mouseDragged(MouseEvent)
mouseMoved(MouseEvent)
ItemEvent ItemListener keine itemStateChanged(ItemEvent)
TextEvent TextListener keine textValueChanged(TextEvent)

 

Listener für Buttons

Typisch für die Verwendung eines Buttons ist es, auf das Ereignis, daß der Button angeklickt wird, reagieren zu wollen. Dabei wird ein ActionEvent erzeugt, so daß man die Schnittstelle ActionListener implementieren muß, um darauf zu reagieren. Diese Schnittstelle enthält nur eine Methode, und zwar actionPerformed, die implementiert werden muß. Dazu definiert man eine innere Klasse innerhalb des Applets. In dem folgenden Beispiel wird die Beschriftung des Buttons b verändert, wenn ein Ereignis vom Typ ActionEvent für diesen Button auftritt.

    public class ChangeButton
        extends Applet
    {

        Button b = new Button (...) ;

        // applet methods:
        public void init () {
            add (b) ; 
            ...
            b.addActionListener (new ChangeButtonListener ()) ;
        }

        // inner class:
        class ChangeButtonListener implements ActionListener {
            public void actionPerformed (ActionEvent e) {
                ...
                b.setLabel (...) ;
            }
        } // end of inner class

    } // end of applet class

Das vollständige Beispiel zeigt einen Button, dessen Beschriftung zeigt, wie oft er angeklickt worden ist.

Listener für Listen

Typisch für ein Listenobjekt ist es, den ItemListener zu verwenden. Er wird bei einem ItemEvent ausgeführt. Bei einer Liste tritt dieses Ereignis auf, wenn ein Eintrag der Liste seinen Zustand ändert, d.h. von selektiert zu deselektiert oder umgekehrt.

Die Schnittstelle ItemListener enthält nur eine Methode, nämlich itemStateChanged, die bei der Definition eines Listeners implementiert werden muß. Zu diesem Zweck definiert man im Applet eine innere Klasse, die die Schnittstelle ItemListener implementiert. Das folgende Beispiel zeigt die Definition einer solchen inneren Klasse.

    public class EisAuswahl extends Applet {

        List list = new List (3, true) ;
        TextArea t = new TextArea (5, 20) ;

        public void init () {
            ...
            add (list) ;
            list.addItemListener (new EisListener ()) ;
            add(t);
        }

        class EisListener implements ItemListener {
            public void itemStateChanged (ItemEvent ie) {
                t.setText ("") ;
                String [] items = list.getSelectedItems () ;
                for (int i = 0; i < items.length; i++) 
                    t.append (items [i] + "\n") ;
            }
        } // end of inner class
    } // end of applet

Durch den Aufruf der Methode getSelectedItems kann man auf die aktuell selektierten Einträge der Liste zugreifen (hier: list.getSelectedItems ()). Als Rückgabewert erhält man ein Array mit Strings, die man weiterverarbeiten kann. Im obigen Beispiel werden sie mit einer for-Schleife in ein dafür vorgesehenes Objekt der Klasse TextArea eingefügt. Zu dieser Art Listener existiert auch ein vollständiges Beispiel. Wenn nur ein Eintrag der Liste selektiert werden kann, kann auf dieses mit der Methode getSelectedItem zugegriffen werden, die einen String zurückliefert.

 

Listener für Check Boxes und Radio Buttons

Wie bei den Listen sind auch bei den Check Boxes die wichtigsten Ereignisse die der Klasse ItemEvent. Dieses Ereignis tritt auf, wenn eine Checkbox angeklickt wird. Es ist also wieder die Schnittstelle ItemListener zu implementieren. Check Boxes gehören oft in Gruppen zusammen, insbesondere wenn es sich um Radio Buttons handelt. Für solche Gruppen gibt es prinzipiell zwei Ansätze:

Die erste Variante ist besonders dann geeignet, wenn man je nachdem, welche Box ausgewählt worden ist, unterschiedliche Operationen ausführen möchte. Sie kann aber zu einer hohen Zahl von inneren Klassen führen.

Das folgende Beispiel zeigt die zweite Variante. Die für die Ereignisbehandlung notwendigen Teile sind rot markiert.

    public class Lieblingseis extends Applet {
        ...
        CheckboxGroup cg = new CheckboxGroup () ;
        Checkbox c1 = new Checkbox ("Heidelbeere", cg, false),
                 c2 = new Checkbox("Erdbeere", cg, false),
                 ... ;
        TextField t = new TextField (...) ;

        public void init () {
            add (c1) ;
            c1.addItemListener (new EisListener ()) ;
            add (c2) ;
            c2.addItemListener (new EisListener()) ;
            ...
            add (t) ;
        }

        class EisListener implements ItemListener {
            public void itemStateChanged (ItemEvent ie) {
                t.setText ((String)ie.getItem ()) ;
            }
        }
    }

Über den Parameter ie vom Typ ItemEvent steht der Methode itemStateChanged zur Zeit des Aufrufes ein Handle für das eingetretene Ereignis zur Verfügung. Für Objekte der Klasse ItemEvent sind eine Reihe von Methoden definiert. Dazu zählt auch die parameterlose Methode getItem, die das von der Methode betroffene Objekt zurückliefert. Bei Check Boxes handelt es sich dabei (nach einer Konvertierung zu String) um die Zeichenkette, die zur Beschriftung der Check Box verwendet wurde. Der Ausdruck

(String)ie.getItem ()

liefert also im konkreten Beispiel die jeweilige Eissorte zurück. Das Textfeld wird verwendet, um die selektierte Eissorte anzuzeigen (siehe auch vollständiges Beispiel).

Listener für Mouse Events

Neben den konkreten Aktionen, die durch das Bedienen der interaktiven Komponenten eines Applets, wie z.B. das Drücken eines Buttons oder die Auswahl eines Eintrages, angestoßen werden, löst auch die Bewegung der Maus und das Betätigen der Maus-Buttons Ereignisse aus, wenn sich die Maus innerhalb des Applets befindet. Um diese Ereignisse verarbeiten zu können, gibt es die Ereignisklasse MouseEvent. Wie Tabelle 1 zu entnehmen ist, werden Ereignisse dieser Klasse für alle betrachteten Komponenten unterstützt.

Zur Behandlung von Mausereignissen sind zwei verschiedene Schnittstellen MouseMotionListener und MouseListener definiert (mit den zugehörigen Adapterklassen).

MouseMotionListener ist für Bewegungen der Maus zuständig und enthält zwei Methoden mouseMoved und mouseDragged:

MouseListener ist für die Maustasten zuständig und enthält dafür drei Methoden mouseClicked, mousePressed und mouseReleased. Weiterhin können auch Aktionen dafür definiert werden, daß die Maus in eine bestimmte Komponente eintritt oder sie verläßt. Dafür gibt es die Methoden mouseEntered und mouseExited.

Beide Schnitstellen haben mehr als einen Methode, so daß hier in der Regel mit den zugehörigen Adapter-Klassen gearbeitet wird (s. Vorgehensweise).

Beispiel zu MouseMotionListener

Im folgenden Beispiel werden die Mausbewegungen in einem Panel paintPanel als Ereignisse verarbeitet. Zu diesem Zweck wird eine innere Klasse MyMotionListener als Subklasse von MouseMotionAdapter implementiert. In MyMotionListener wird die Methode Methode mouseMoved redefiniert, da auf Mausbewegungen reagiert werden soll.

    public class MouseExample extends Applet {
 
        Panel monitor = new Panel (),
              paintPanel = new Panel () ;
        TextField newPosition = new TextField (20) ;
        ...
 
        public void init () {
            setLayout (new BorderLayout ()) ;
            add (BorderLayout.CENTER, paintPanel) ;
            paintPanel.addMouseMotionListener (new MyMotionListener ()) ;
            add(BorderLayout.SOUTH, monitor);
            ...
            monitor.add(newPosition);
        }

        class MyMotionListener extends MouseMotionAdapter {
     
            int x = 0, y = 0 ; 

            public void mouseMoved (MouseEvent m) {
                ...
                x = m.getX () ;
                y = m.getY () ; 
                newPosition.setText ("x = " + x + " y = " + y) ;
            }

        }// class MyMotionListener

    }// class MouseExample

Über den Parameter m vom Typ MouseEvent besitzt die Methode ein Handle für das eingetretenen Ereignis. Die Methoden getX () und getY () sind für Objekte des Typs MouseEvent definiert und erlauben es, auf die X und Y-Koordinate der Mausposition zuzugreifen. Im Beispiel wird die jeweilige Mausposition in das Textfeld newPosition ausgegeben. Im vollständigen Beispiel wird jeweils auch die vorherige Position ausgegeben, die in den Variable x und y zwischengespeichert wird.

Beispiel zu MouseListener

Das vorherige Beispiel wird jetzt um Aktionen erweitert, die ausgeführt werden, wenn innerhalb der Komponente paintPanel die Maustaste gedrückt bzw. wieder losgelassen wird. Dazu wird eine innere Klasse MyMouseListener als Subklasse der Klasse MouseAdapter implementiert. In dieser Klasse werden die Methoden mousePressed bzw. mouseReleased redefiniert. Im Beispiel zeichnen diese Methoden an der aktuellen Position einen roten Kreis (mousePressed) bzw. einen schwarzen Punkt (mouseReleased). Der Effekt kann am vollständigen Beispiel beobachtet werden.

    public class MouseExample extends Applet {

        Panel paintPanel = new Panel () ;
        ...
 
        public void init () {
            setLayout (new BorderLayout ()) ;
            add (BorderLayout.CENTER, paintPanel) ;
            paintPanel.addMouseListener (new MyMouseListener ()) ;
            ...
        }

        class MyMouseListener
            extends MouseAdapter
        {

            public void mousePressed (MouseEvent m) {
                Graphics g = paintPanel.getGraphics () ;
                g.setColor (Color.red) ;
                g.drawOval (m.getX (), m.getY (), 20, 20) ;
            }

            public void mouseReleased (MouseEvent m) {
                Graphics g = paintPanel.getGraphics () ;
                g.setColor (Color.black) ;
                g.fillOval (m.getX (), m.getY (), 10, 10) ;
            }

        }// class MyMouseListener

    }// class MouseExample

Dynamisches Hinzufügen und Löschen von Listenern

Listener können nicht nur in der init-Methode an die Komponenten angehängt werden, sondern auch zu anderen Zeitpunkten. Insbesondere ist es auch möglich, innerhalb einer Listener-Methode einen Listener an eine Komponente anzuhängen oder von einer Komponente wegzulöschen. Damit ist es möglich, festzulegen, daß eine Komponente x erst dann anfängt oder aufhört auf ein bestimmtes Ereignis zu reagieren, wenn vorher ein anderes Ereignis für diesselbe oder eine andere Komponente eingetreten ist. Zum Löschen eines Listeners XListener wird die Methode removeXListener verwendet.

Im folgenden Beispiel wurde die Eisauswahl um einen Schalter (switch) erweitert, mit dem man die Eisauswahl an- und ausschalten kann: Nur wenn der Schalter angeschaltet ist, hat die Auswahl von Eissorten einen Effekt. Dazu wurde ein Knopf mit einem ActionListener eingefügt. Erst wenn dieser Knopf gedrückt wird (Schalter an), wird der EisListener an die Eisliste angehängt. Bei erneutem Anklicken des Knopfes (Schalter aus) wird der Listener wieder gelöscht.

    public class Switch extends Applet {

        Button b = new Button ("off") ; 
        List list = new List (3, true) ;
        ...
  
        public void init () {
            ...
            add (b) ;
            b.addActionListener (new SwitchListener ()) ;
            list.addItem ("Heidelbeere") ;
            ...
            add (list) ; 
        }

        class EisListener
            implements ItemListener
        {
            public void itemStateChanged (ItemEvent ie) {
                ...
            }
        }// class EisListener

        class SwitchListener
            implements ActionListener
        {
            ItemListener il = new EisListener () ;
            public void actionPerformed (ActionEvent e) {
                if ( b.getLabel ().equals ("off") ) {
                    list.addItemListener (il) ;
	            b.setLabel ("on") ;
                }
                else {
                    list.removeItemListener (il) ;
                    ...
                    b.setLabel ("off") ;
                }
            }
        }// class SwitchListener

    }// class Switch

Der Effekt des Schalters kann am vollständigen Beispiel untersucht werden.

Aufgaben

Ergänze das Applet PaintTool um eine geeignete Ereignisbehandlung

  1. Definiere eine innere Klasse BackgroundListener, die die Schnittstelle ItemListener implementiert. Dieser Listener soll verwendet werden, um auf das Anklicken der Radio-Buttons "Vordergrund"/"Hintergrund" aus der vorherigen Übung zu reagieren. Implementiere dazu die Methode itemStateChanged dieser Schnittstelle, so daß sie ein Datenfeld isForeground vom Typ Boolean Deines Applets entsprechend umsetzt.
  2. Definiere eine innere Klasse ColorListener, die ebenfalls die Schnittstelle ItemListener implementiert. Dieser Listener soll verwendet werden, um auf das Selektieren einer Farbe aus der Farbliste, die in der vorigen Übung in das Applet eingefügt wurde, zu reagieren. Implementiere dazu die Methode itemStateChanged dieser Schnitstelle, so daß abhängig vom Wert des Datenfeld isForeground (s.o.) die Vordergrund- bzw. Hintergrundfarbe der Komponente paintPanel auf die gewählte Farbe umgesetzt wird. Verwende die Methode translate der Klasse ColorChoice, um von dem selektierten Farbnamen (String) zur Farbe vom Typ Color zu übersetzen.
  3. In der Komponente paintPanel soll, wenn man die Maus mit gedrückter Taste darin bewegt, ein Linienzug gezeichnet werden, der der Mausbewegung im Panel folgt. Implementiere dazu eine Klasse MyMotionListener, die bei der Bewegung der Maus eine Linie von der vorherigen Mausposition zur neuen Mausposition zeichnet. Die vorherige Mausposition muß dazu in der inneren Klasse zwischengespeichert weden (Datenfelder xPosition, yPosition).

    Zusatzaufgabe (freiwillig):

    Ein Problem dieses Applets ist, daß das Zeichnen der Linie immer im Ursprung beginnt. Um dies zu umgehen, kann das Applet so modifiziert werden, daß der Listener erst dann angehängt wird, wenn die Maustaste gedrückt wird. Dabei wird die aktuelle Mausposition für die Initialisierung von (xPosition, yPosition) übergeben. Dazu muß eine zusätzliche Listener-Klasse geschrieben und in der inneren Klasse MyMotionListener ein Konstruktor definiert werden, der die x und y-Koordinate als Parameter erhält. Beim Loslassen der Maustaste wird der Listener wieder gelöscht.

 


STS-Logo.gif (1335 Byte) Home hw.sehring, apr-2002