Advanced   Java   Services Das EventHandling ab 1.1 Back Next Up Home


Mit der Version 1.1 wurde ein neues Eventmodell eingeführt, das Event-Delegation-Modell. Wir nennen es hier Sender-Empfänger-Modell.


Das Sender-Empfänger Modell

eventhandling1.jpg

Ein Objekt e möchte von einem anderen Objekt s eine Nachricht erhalten. In dieser Situation nennen wir s einen Sender oder ein Sendeobjekt und e einen Empfänger oder ein Empfängerobjekt. Die Nachricht soll dann gesendet werden, wenn für das Objekt s ein bestimmtes Ereignis eintritt. Zu einem Ereignis gibt es eine oder mehrere zu sendende Nachrichten, die genau definiert sind. Ebenso gibt es verschiedene Ereignistypen. Jeder Ereignistyp hat also einen Satz von Nachrichten, die genau zu diesem Ereignistyp gehören.


Ereignistypen: Eventklassen

Zu jedem Ereignistyp gibt es in Java eine passende Klasse aus dem Package java.awt.event oder javax.swing.event. Die Namen dieser Klassen enden alle auf "Event", also z.Bsp. ActionEvent, MouseEvent, FocusEvent, PropertyChangeEvent usw. Alle diese Klassen leiten sich letzten Endes von der Klasse EventObject ab, die die Basisklasse für alle Eventklssen darstellt. Es gibt auch eine Klasse Event, diese existiert jedoch nur noch aus Gründen der Abwärtskompabilität. Sie stellt sicher, daß alte Programme, die das Ereignismodell von JDK1.0 verwenden immer noch laufen können.


Nachrichten zu den Ereignistypen stehen in den ListenerInterfaces

Zu jeder Eventklasse gibt es einen Satz von Nachrichten, die gesendet werden können, wenn ein Event eintritt. Diese Nachrichten stehen in den zum Ereignis gehörenden sog. ListenerInterfaces. So gibt es etwa zum ActionEvent ein Interface ActionListener, zum MouseEvent ein Interface MouseListener, zum FocusEvent ein Interface FocusListener, zum PropertyChangeEvent ein Interface PropertyChangeListener usw.


Zu einem Event gehört eines oder auch mehrere ListenerInterfaces

Es kann vorkommen, daß es zu einem Event viele Nachrichten existieren, die versendet werden können. In solchen Fällen kann es zu einem Event mehrere ListenerInterfaces geben. Jedes dieser Interfaces stellt dann ein Untergruppe der möglichen Nachrichten dar. So gibt es etwa zum MouseEvent die beiden Interfaces MouseListener und MouseMotionListener und zum WindowEvent sogar drei Listeners, nämlich WindowListener, WindowFocusListener und WindowStateListener.


Nachrichten senden heißt Methoden aufrufen

Wirft man einen Blick (oder auch mehrere) in die Dokumentation der ListenerInterfaces, so sieht man, daß jedes Interface einen Satz von Methoden vereinbart, was ja auch der Zweck eines Interfaces ist. Tritt nun für ein Sendeobjekt ein bestimmter Event ein, so ruft dieser Sender bestimmte Methoden des Empfängerobjektes auf. Dabei handelt es sich um solche Methoden, die in dem zum Event gehörenden EventListener Interface(s) vereinbart sind. Um für den Fall eines Events diese Methoden aufrufen zu können, verwaltet der Sender eine Liste von Empfängern. Tritt nun der Event ein werden alle Empfänger benachrichtigt.


Wer kann senden

Sender sind Objekte. Meist, aber nicht notwendigerweise, gehören die Objekte zu Klassen der graphischen Oberfläche (AWT oder Swing). Diese Klassen sind bereits so eingerichtet, daß ihre Objekte Nachrichten versenden können. Klassen, deren Objekte Nachrichten senden können erkennt man daran, daß sie bestimmte Methoden bereitstellen. Die Namen dieser Methoden werden nach einem festen Muster gebildet und beginnen alle mit "add" gefolgt von dem Namen des Interfaces, in dem die Nachrichten vereinbart sind, also etwa addActionListener oder addMouseListener oder addFocusListener oder addPropertyChangeListener usw. Eine Übersicht über die Events und die zugehörigen ListenerInterfaces finden Sie unter AWTEvents bzw. unter SwingEvent.

Unser Modell sieht also folgendermaßen aus:

eventhandling2.jpg



Realisierung des Sender-Empfänger Modells

In diesem Abschnitt wollen wir das Modell konkret implementieren.

Was muß man tun, damit ein Sendeobjekt Nachrichten senden kann ?

Nichts !

Ein Buttonobjekt z.B. reagiert auf einen Buttondruck automatisch mit einem Actionereignis.

Was muß man tun, damit ein Empfängerobjekt Nachrichten empfangen kann ?

Etwas mehr !

Das Empfangen geht nicht so automatisch wie das Senden. Hier muß man selbst aktiv werden.

Fangen wir mit einem Button als Sender an. Ein Empfängerobjekt muß sich beim Button registrieren lassen um Nachrichten zu erhalten. Dies geschieht mit der Methode addActionListener(ActionListener lis), die die Klasse Button bereitstellt. Da der Methode ein Objekt vom Typ eine Interfaces übergeben wird, muß unser Empfängerobjekt typverträglich zum Typ ActionListener sein, oder anders gesagt, die zugehörige Klasse muß das Interface ActionListener implementieren. So eine Klasse nennen wir in Zukunft Empfängerklasse. Jede Klasse, die dieses Interface implementiert ist eine Empfängerklasse.


Eine Klasse zu einer Empfängerklasse machen

Hier ganz schematisch die Schritte, die in einem Klassenentwurf durchzuführen sind.


Ein Objekt dieser Klasse zu einem Empfängerobjekt machen

Jedes Objekt der nach dem obigen Schema gebildeten Klasse ist nun ein Empfängerobjekt, natürlich nur für den zu XxxListener gehörenden Satz von Nachrichten. Das Objekt muß sich nun noch beim Sender, von dem es Nachrichten haben will, registrieren. Dabei kann sich ein Empfängerobjekt durchaus bei mehreren Sendeobjekten registrieren. Es enthält dann Nachrichten von mehreren Sendern. Das umgekehrte ist genau so normal. Ein Sender sendet seine Nachrichten an mehrere Empfänger.

sender.addXxxListener(empfänger) ; // Sender-Empfänger-Verbindung herstellen.

// optional

sender.addXxxListener(empfänger2) ;
sender2.addXxxListener(empfänger) ;

Will man etwa Actionereignisse empfangen, so sieht das codiert so aus.

import java.awt.event.*;

class ActionHandler implements ActionListener
{
   public void actionPerformed(Actionevent e)
   {
      // Diese Methode wird vom Sender gerufen
   }
}

In irgendeiner anderen Klasse (oder auch derselben) gibt es dann ein Buttonobjekt. Falls wir ein Empfängerobjekt registrieren, dann bekommt dieses Objekt Actionnachrichten von diesem Button.

class Xyz
{

   public void methode()
   {
      Button knopf = new Button();
      // Button konfigurieren

      knopf.addActionListener( new ActionHandler() );
      // Empfänger registrieren: Sender-Empfänger-Verbindung herstellen
   }
}


Verschiedene Arten von Empfängerklassen

Die folgende Frage ist einfach zu beantworten.


Welche Klassen können Empfängerklassen werden

Jede Klasse, die man selbst entwirft. Außerdem kann sie jeden beliebigen Status haben. Sie kann eine anonyme Klasse, eine lokale Klasse, eine innere Klasse oder eine top-level-Klasse sein, je nachdem welches Design man bevorzugt.

Hier nun einige Beispiele von Empfängerklassen.


Anonyme Klasse ist Empfängerklasse
class Xyz
{
   public void methode()
   {
      Button knopf = new Button();
      // Button konfigurieren

      knopf.addActionListener( new ActionListener()
         {
            public void actionPerformed(ActionEvent e)
            {
               // Reaktion auf Buttonclick programmieren.
            }
         }
      );
   }
}

Hier wird eine anonyme Klasse Empfängerklasse. Wie üblich und notwendig bei anonymen Klassen wird gleichzeitig ein Objekt angelegt, das als Empfängerobjekt registriert wird. Sehr beliebt, aber vom objektorientierten Standpunkt anfechtbar. Sie wird hier die Ereignisbehandlung mit der graphischen Konfiguration vermischt. Für kurzen Programmcode und einfache Reaktionen ok, ungeeignet dagegen für komplexes Ereignishandling. Vorteilhaft ist, daß es zu dem Empfängerobjekt (zwangsläufig) nur einen Sender gibt, man muß also nicht zwischen mehreren Sendern unterscheiden. Oft wird aus der Reaktionsmethode eine andere Methode aufgerufen. In so einem Fall kann man auch gleich eine innere Klasse schreiben.


Innere Klasse ist Empfängerklasse
class Xyz
{
   public void methode()
   {
      // Button konfigurieren
      Button knopf = new Button();
      // Sender-Empfänger-Verbindung
      knopf.addActionListener( new ActionHandler() );
   }

   // some other stuff

   // Innere Klasse als EventHandler
   class ActionHandler implements ActionListener
   {
      public void actionPerformed(ActionEvent e)
      {
         // Reaktion auf Buttonclick programmieren.
      }
   }

}

Hier hat man eine saubere Trennung zwischen user interface und Ereignisbehandlung. Will man auf Ereignisse von mehreren Sendern reagieren, dann kann man entweder mehrere Handler schreiben und so eine 1:1 Beziehung zwischen Sender und Empfänger herstellen. Es gibt jedoch auch bei mehreren Sendern und nur einem Empfänger die Möglichkeit, den Sender zu ermitteln, was weiter unten beschrieben wird.


toplevel-Klasse ist Empfängerklasse
class Xyz
{
   public void methode()
   {
      // Button konfigurieren
      Button knopf = new Button();
      // Sender-Empfänger-Verbindung
      knopf.addActionListener( new ActionHandler() );
   }

   // some other stuff
}

// extra Klasse als EventHandler
class ActionHandler implements ActionListener
{
   actionPerformed(ActionEvent e)
   {
      // Reaktion auf Buttonclick programmieren.
   }
}

Ermittlung des aktuellen Senders bei mehreren Sendern und einem Empänger

Die Klasse EventObject stellt hierzu die wichtige Methode Object getSource() bereit, die damit für alle Events verfügbar ist. Die Methode gibt eine Referenz auf das aktuelle Sendeobjekt zurück.


public void actionPerformed(ActionEvent e)
{
   Object quelle = e.getSource();

   if(quelle==knopf1)
   {
      // Reaktion auf ersten Button programmieren.
   }
   else if (quelle==knopf2)
   {
      // Reaktion auf zweiten Button programmieren.
   }
   else
   {
      // Reaktion auf dritten Button programmieren.
   }
}

Längere else-if Ketten sind allerdings nicht sehr schön. Der Code wird schwerer durchschaubar. Man kann sich wieder mit Methoden behelfen, die aus den einzelnen Blöcken aufgerufen werden. Hat man zudem sehr unterschiedliche Reaktionen, so ist es oft besser, man schreibt eine für eine Reaktion maßgeschneiderte Empfängerklasse.


High-Level und Low-Level Ereignisse

Wenn man auf einen Button klickt, dann ist es egal, an welcher Stelle man auf den Button klickt, Hauptsache man klickt. Ähnliches gilt für das Anhaken einer Checkbox oder das Auswählen eines Elementes aus einer Liste. Zu solchen Ereignissen gehört nur eine Nachricht, man hat den Button angeklickt oder man hat ein Element aus einer Liste ausgwählt. So sendet ein Button Actionereignisse, indem er für alle Listener die eine Methode actionPerformed(ActionEvent e) aufruft und so den ActionEvent übermittelt. Solche Ereignisse heißen High-Level Ereignisse.

Es gibt aber auch komplexere Ereignisse, zu denen mehr als eine Nachricht gehören. Ein gutes Beispiel ist der MouseEvent. Er gehört zu den Low-Level Events. Zu einem MouseEvent gehören insgesamt sieben Nachrichten, die auf zwei Listeners aufgeteilt wurden, MouseListener und MouseMotionListener. Tritt in einer Komponente ein Mausereignis ein, so werden zu registrierten MouseListenern fünf Methoden aufgerufen und zu registrierten MouseMotionListenern zwei Methoden. Die MouseListener erfahren u.a., ob die Maus nur gedrückt wurde (mousePressed) oder ob der User die Maus geklickt hat (mouseClicked) oder ob das Gebiet der dargestellten Komponente verlassen hat (mouseExited). MouseMotionListener werden darüber informiert, ob die Maus bei gedrückter Maustaste bewegt wurde (mouseDragged) oder lediglich bewegt wurde (mouseMoved). Über den so übermittelten MouseEvent erfährt der Listener z.B. auch noch die Koordinaten, an denen der Event ausgelöst worden ist und welche Maustaste der User verwendet hat. Informieren Sie sich in der Tabelle AWT Listeners and Events über ActionEvent und MouseEvent. Wir wollen hier kurz die Implementierung eines MouseListeners schildern.


Implementierung eines Low-Level Listeners

Im Grunde ist der Vorgang der gleiche wie oben bei unserem Buttonbeispiel. Es ist nur ein wenig aufwendiger. Angenommen wir wollen die Maus über einem JLabel kontrollieren. Ein JLabel sendet wie jede Komponente Mausereignisse.

public class Xyz
{
   JLabel label = new JLabel();

   public Xyz()
   {
      // JLabel konfigurieren
      label.addMouseListener( new MouseHandler() );
      // Empfänger registrieren für Mausnachrichten
      label.addMouseMotionListener( new MouseMotionHandler() );
      // Empfänger registrieren für MouseMotion-Nachrichten
   }
}

Hier die Listenerklasse für MouseListener-Objekte

class MouseHandler implements MouseListener
{
   public void mouseClicked(MouseEvent e)
   {
      // Reaktionscode
   }
   public void mouseEntered(MouseEvent e)
   {
      // Reaktionscode
   }
   public void mouseExited(MouseEvent e)
   {
      // Reaktionscode
   }
   public void mousePressed(MouseEvent e)
   {
      // Reaktionscode
   }
   public void mouseReleased(MouseEvent e)
   {
      // Reaktionscode
   }
}

Und hier die Listenerklasse für MouseMotionListener-Objekte

class MouseMotionHandler implements MouseMotionListener
{
   public void mouseDragged(MouseEvent e)
   {
      // Reaktionscode
   }
   public void mouseMoved(MouseEvent e)
   {
      // Reaktionscode
   }
}

Ob MouseHandler und MouseMotionHandler innere Klassen sind oder top-level, das ist wieder Ansichtssache. Wenn man will kann man auch nur eine Klasse schreiben, die beide Interfaces implementiert.

public class Xyz
{
   JLabel label = new JLabel();

   public Xyz()
   {
      // JLabel konfigurieren
      label.addMouseListener( new MouseEventHandler() );
      // Empfänger registrieren für Mausnachrichten
      label.addMouseMotionListener( new MouseEventHandler() );
      // Empfänger registrieren für MouseMotion-Nachrichten
   }

   class MouseEventHandler implements MouseListener, MouseMotionListener
   {
      public void mouseClicked(MouseEvent e)
      {
         // Reaktionscode
      }
      public void mouseEntered(MouseEvent e)
      {
         // Reaktionscode
      }
      // und weitere drei MouseListenermethoden
      // ...
      public void mouseDragged(MouseEvent e)
      {
         // Reaktionscode
      }
      public void mouseMoved(MouseEvent e)
      {
         // Reaktionscode
      }
   } // end inner class
}

Einige High-Level Events

Einige Low-Level Events

Die nächste Graphik bringt eine Übersicht über die Eventklassen. Da es eine große Anzahl von Eventklassen gibt, wird dort nur ein Ausschnitt gezeigt.


AdapterKlassen

Oft will man bei Low-Level Ereignissen nicht auf alle Nachrichten reagieren. Vielleicht ist man bei einem MouseEvent nur an einer mousePressed Nachricht interessiert. Trotzdem muß ein Listener natürlich alle Methoden des Interfaces implementieren, die dann als leere Methoden kodiert werden. Das ist nicht weiter schlimm, aber ein wenig lästig. Für einige der Low-Level Events stellt die API deswegen sog. Adapterklassen zur Verfügung. Diese Klassen übernehmen die Implementierung eines Listenerinterfaces. Hier z.B. der Quellcode der Klasse MouseAdapter.

public abstract class MouseAdapter implements MouseListener
{
   /**
   * Invoked when the mouse has been clicked on a component.
   */
   public void mouseClicked(MouseEvent e) {}

   /**
   * Invoked when a mouse button has been pressed on a component.
   */
   public void mousePressed(MouseEvent e) {}

   /**
   * Invoked when a mouse button has been released on a component.
   */
   public void mouseReleased(MouseEvent e) {}

   /**
   * Invoked when the mouse enters a component.
   */
   public void mouseEntered(MouseEvent e) {}

   /**
   * Invoked when the mouse exits a component.
   */
   public void mouseExited(MouseEvent e) {}
}

Wie man sieht, wird sie als abstract deklariert und implementiert alle Methoden des zugehörigen Interfaces als leere Methoden. Ein Ereignishandler wird dann als Ableitung der Adapterklasse angelegt und überschreibt dann diejenigen Methoden, auf deren Nachrichten man reagieren will. Ist man etwa nur an mousePressed Nachrichten interessiert, so kann man eine Variation des letzten Beispiels auch so codieren.

//import...

public class Xyz extends JFrame
{
   JLabel label = new JLabel();
   // ...

   public Xyz()
   {
      // JLabel konfigurieren
      label.addMouseListener( new MouseEventHandler() );
      // Empfänger registrieren für Mausnachrichten
      // ...
   }

   // ...

   class MouseEventHandler extends MouseAdapter
   {
      public void mousePressed(MouseEvent e)
      {
         // Reaktionscode
      }
   } // end inner class
}

Ableitungen von Adapterklassen werden gerne als innere Klassen realisiert. In der obigen Situation ist etwa die Klasse Xyz bereits eine Ableitung von JFrame und kann daher keine Ableitung mehr von MouseAdapter sein. Will man die Ereignisbehandlung in der gleichen Klasse haben muß man irgendeine Form einer nested class wählen.

Eine Übersicht über Adapterklassen gewinnt man aus den Tabellen AWT Listeners and Events und Swing Listeners and Events.

Valid XHTML 1.0 Strict top Back Next Up Home