Events werden dazu verwendet, um Objekte über Zustandsänderungen anderer Objekte zu informieren. Für JavaBeans wurde ein besonderes Event-Modell benötigt, das eine erweiterbare Menge an Events beinhaltet und das Entwicklungsumgebungen sowie Skript-Sprachen unterstützt. Da dies mit dem Event-Modell von Java 1.0.2 nicht oder nur teilweise möglich war, wurde das Event-Modell von Java 1.0.2 grundlegend überarbeitet. Seit JDK in der Version 1.1 verfügbar ist, existiert ein Event-Modell, dessen Vorzüge und Verwendungsmöglichkeiten im Laufe dieses Kapitels herausgestellt werden sollen. Um die Unterschiede zwischen den Event-Modellen beurteilen zu können, wird in diesem Kapitel zunächst das in Java 1.0.2 verwendete Modell vorgestellt, bevor das Modell von Java 1.1 besprochen wird. Events in Java 1.0.2 Es ist aus zwei Gründen notwendig, das Event-Modell von Java 1.0.2 näher zu betrachten. Einerseits werden dessen Funktionen immer noch automatisch verwendet, sofern keine Funktionen von Java 1.1 benutzt werden. Weiterhin unterstreichen die Probleme des 1.0-Modells die Motivation der Entwicklung und die Vorzüge des überarbeiteten 1.1-Modells. Im Event-Modell von Java 1.0.2 existiert nur eine Klasse von Events, java.awt.Event, die direkt von der Klasse java.lang.Object abgeleitet ist. Abb. 9.4: Event-Modell in Java 1.0 Alle AWT-Komponenten im Package java.awt beinhalten die Methode: public boolean handleEvent(Event e){ // Verarbeitung } Wenn ein Event ausgelöst wird (bspw. ein Mausklick), so wird die handleEvent-Methode der entsprechenden Klasse mit einem Event-Objekt als Parameter aufgerufen. Diese überprüft, ob der Event benötigt wird. Ist dies der Fall, so wird die entsprechende Bearbeitungsmethode aufgerufen und handleEvent() terminiert mit dem Rückgabewert true. Falls handleEvent() den Event nicht benötigt, so liefert es den Wert false zurück, d. h. der Event wurde nicht verarbeitet. Tritt dieser Fall ein, so wird der Event in der Container-Hierarchie nach oben weitergeleitet. Die Rückgabe des Werts true zeigt eine erfolgreiche Verarbeitung des Events an, so dass keine Weiterleitung erfolgen muss. Das Event-Modell von Java 1.0.2 arbeitet folglich nach dem Prinzip Inheritance and Propagation (Vererbung und Weiterleitung). Probleme des Modells von Java 1.0.2 Das Modell von Java 1.0.2 ist einfach und für kleinere Projekte ausreichend. Es hat aber insbesondere im Hinblick auf umfangreichere Anwendungen mehrere entscheidende Nachteile: Ableitungen Das 1.0.2-Modell zwingt den Programmierer zum Ableiten von GUI-Klassen, ohne dass eine veränderte Funktionalität oder ein verändertes Aussehen dies tatsächlich rechtfertigen würde. Dieses Vorgehen führt schnell zur Unübersichtlichkeit. Aufgrund der Weiterleitung von Events in der Container-Hierarchie kann man sich zwar darauf beschränken, den obersten Container abzuleiten. In diesem Fall muss man aber dort eine mit zunehmender Anwendungsgröße komplexe Typabfrage implementieren, was wiederum schnell zur Unübersichtlichkeit führt. Overhead Events werden immer an den übergeordneten Container weitergeleitet, unabhängig davon, ob dieser den Event verarbeitet oder nicht. Oft müssen sich Komponenten mit Events befassen, die sie nicht verarbeiten können. Dies ist bspw. oft beim Event-Typ MouseMovement der Fall, da nur wenige Objekte daran interessiert sind, jede Mausbewegung auszuwerten. Diese Verlagerung kann zur Verlangsamung der Rechnergeschwindigkeit führen. Unsaubere Implementierung Die Behandlung von Events findet immer in GUI-Komponenten statt. Eine saubere Trennung von GUI und übrigem Programmcode ist nicht möglich. Weiterhin können nur sichtbare AWT-Objekte Events erzeugen. Das neue Eventmodell Das Event-Modell von Java 1.1 stützt sich auf einen sog. Callback-Mechanismus. Die Bezeichnung dieses Modells lautet Delegation-Modell. Jedes Objekt, das an einem bestimmten Event-Typ interessiert ist, meldet sich als sog. Listener bei der Komponente an, die diesen Event-Typ erzeugt. Wird ein Event ausgelöst, so werden alle registrierten Listener benachrichtigt. Events, für die keine Listener registriert sind, werden nicht ausgelöst. Jeder ausgelöste Event ruft direkt die dazu gehörenden Event-Handler auf. Dieses Vorgehen erspart unnötigen Aufwand bei der Programmierung von handleEvent-Methoden, die nicht benötigte Events herausfiltern. Die handleEvent-Methoden bleiben daher kurz und übersichtlich. Da ein Event sofort den passenden Event-Handler aufruft, kann der Event beliebige Daten an den Handler übergeben. Bei der Wahl der Schnittstelle zwischen Event und Listener sind im Gegensatz zum Event-Modell von Java 1.0.2, dessen Events alle von einer Klasse abstammen müssen, keine festen Grenzen gesetzt. Durch das ab JDK 1.1 implementierte Event-Modell wird ein Event von einer Event-Quelle an einen Event-Listener weitergeleitet, indem die Event-Quelle eine Methode des Event-Listeners aufruft und ein Objekt vom zu erzeugenden Event-Typ übergibt. Zunächst werden die wichtigsten Teile des Event-Modells, das Event-Objekt (Event Object), der Event Listener und die Ereignisquelle (Event Source), erläutert. Event-Objekt Event-Objekte kapseln die Zustandsänderung einer Event-Quelle. Alle Event-Objekte werden von der Klasse java.util.EventObject abgeleitet. Der Name einer Event-Klasse muss die Endung Event haben, wodurch der allgemeine Typ eines Events bereits an seiner Klasse erkennbar wird. Man unterscheidet dabei Low-Level-Events, wie z. B. MouseEvent oder FocusEvent und High-Level-Events (semantische Events) wie z. B. ActionEvent (ausgelöst bspw. durch Betätigung eines Buttons oder durch Auswahl eines Listeneintrags). Reichen die vordefinierten Event-Objekte nicht aus, so muss ein neues Event-Objekt definiert werden. Dadurch, dass jede Event-Klasse von der Klasse java.event.EventObject abgeleitet wird, ist es für jeden Event möglich, eigene Daten und Methoden zu beinhalten. //Definition des eigenen Event-Typs public class MyEvent extends java.util.EventObject { // Implementierung des eigenen Event-Objekts } Event-Listener Jede Event-Klasse besitzt ein oder mehrere Listener-Interfaces, die passende Methoden bereithalten, um die Informationen des Events abzufragen. So beinhaltet die Event-Klasse MouseEvent bspw. die beiden Listener-Interfaces MouseListener und MouseMotionListener. Das Listener-Interface MouseListener dient bspw. dazu, MouseClick-Events zu übertragen. Dazu stellt es unter anderem Methoden zum Feststellen von MouseUp- und MouseDown-Events bereit. Das Interface MouseMotionListener stellt dahingegen andere Methoden bereit, da es sich mit der Auswertung von Mausbewegungen beschäftigt. Möchte sich ein Objekt für die Events eines bestimmten Typs registrieren, so muss es das entsprechende EventListener-Interface implementieren. Bevor man einen Event-Listener mittels einer der Methoden addXXXListener() registrieren kann, muss man einen Listener implementieren. Alle Event-Listener werden von der Klasse java.util.EventListener abgeleitet. Ein XXXListener beinhaltet für jeden Event-Typ, den die entsprechende XXXEvent-Klasse repräsentieren kann, mindestens eine als public deklarierte Methode. Durch die Implementierung dieser Methoden kann man den Event abfangen. Das folgende Beispiel definiert zwei Methoden für die Klasse myEvent: //Definition eines eigenen Event-ListenerTyps public interface MyListener extends java.util.EventListener { public abstract void machWas(MyEvent e); public abstract void machNochWas(MyEvent e); } Event-Quelle Im Kontext dieses Kapitels werden Events stets von JavaBeans ausgelöst. Eine Event-Quelle stellt Events zur Verfügung, indem sie die Methoden zur Registrierung eines Event-Listeners (addXXXListener(XXXListener l)) und zum Löschen der Registrierung (removeXXXListener()) anbietet. Im folgenden Beispiel wird zuerst ein Vektor erzeugt, damit alle Objekte verfolgt werden können, die an einem Event interessiert sind. Anschließend werden die Methoden zum Anmelden und Abmelden des EventListener-Objektes implementiert. // Registrieren und entfernen von Event-Listenern private vector myListeners = new Vector(); public synchronized void addMyListener (MyListener l){ myListeners.addElement(l); } public synchronized void removeMyListener(MyListener l){ myListeners.removeElement(l); } Kann ein EventListener-Interface nicht direkt implementiert werden, so wird auf das Konzept der Event-Adapter zurückgegriffen. Event-Adapter Bisher wurde stets angenommen, dass Events direkt zwischen den Event-Quellen und Event-Listenern ausgetauscht werden. Dies impliziert, dass die Event-Listener sich bei der Event-Quelle registrieren und anschließend die entsprechende Nachricht erhalten, wenn ein Event eintritt. Diese Vorgehensweise stellt den Normalfall dar. In manchen Fällen ist es allerdings notwendig, die Event-Quelle vom Event-Listener zu trennen. Um diese Trennung zu erreichen, werden Adapter benötigt. Sie werden zwischen der Event-Quelle und dem Event-Listener platziert und fungieren als Event-Listener für die Event-Quelle und als Event-Quelle für das EventListener-Objekt. Abb. 9.5: Verwendung von Adaptern Da Adapter häufig eingesetzt werden, werden im Folgenden einige Einsatzgebiete von Adaptern beschrieben. Filter Dieser Typ eines Event-Adapters empfängt die Events eines Beans, filtert sie entsprechend den Anforderungen des Programmierers und leitet die zu bearbeitenden Events anschließend weiter. Filteradapter werden bspw. benötigt, wenn nicht alle Events einer Komponente gesendet werden sollen, allerdings der Source-Code des Beans nicht vorliegt. Abb. 9-6 zeigt ein Beispiel eines solchen Adapters, der die Aufgabe hat, Mouse-Events zu ignorieren. Abb. 9.6: Ereignisadapter als Filter für Mouse-Events Warteschlangen Eine mögliche weitere Aufgabe einer Adapterklasse ist der Aufbau einer Event-Warteschlange. Hierbei kann die Event-Quelle weiter ablaufen, ohne darauf warten zu müssen, dass ein Event beim Event-Listener abgearbeitet wurde. Diese Adapterart ist dann besonders sinnvoll, wenn Beans unterstützt werden sollen, die eventuell während der Auslösung eines Events noch nicht vorhanden sind. Umhüllungsadapter Diese Adapterart wird verwendet, wenn der Source-Code des Event-Listeners nicht modifizierbar ist. Hierbei registriert sich der Adapter bei der Event-Quelle. Wenn ein Event generiert wird, setzt der Adapter die entsprechende Methode beim Ziel-Bean ein. Schaltungsadapter Diese Adapterart wird bei vielen Entwicklungsumgebungen dazu verwendet, um Beans zu verbinden. Durch die Introspektion stellt die Entwicklungsumgebung fest, welche Events von welchem Bean ausgelöst werden können und verbindet diese mit dem Ziel-Bean. Demultiplexer-Adapter Diese Adapterart erlaubt es, mit demselben Event-Typ verschiedene Methoden beim Event-Listener aufzurufen. Dieser Fall kann z. B. dann eintreten, wenn ein Event-Listener das ActionListener-Interface implementiert hat, aber auf verschiedene Art und Weise reagieren soll, je nachdem, welcher Button betätigt wurde. So ist bei der Betätigung des OK-Buttons eine andere Methode aufzurufen als bei der Betätigung des Cancel-Buttons. Abb. 9.7: Beispiel eines Demultiplexer-Adapters In Abb. 9-7 verfügt jeder Buttons über einen Adapter, um ActionEvent-Objekte abzubilden. Der Adapter für den OK-Button sieht wie folgt aus: // Adapter fuer OK-Button class OKButtonAdapter implements ActionListener{ public void actionPerformed (ActionEvent e){ Bean.doOKAction(); } } Ähnlich sieht der Adapter für den Cancel-Button aus: // Adapter fuer den Cancel-Button class CancelButtonAdapter implements ActionListener{ public void actionPerformed (ActionEvent e){ Bean.doCancelAction(); } } Bei der Instantiierung der beiden Buttons muss dann jeweils der Adapter angemeldet werden. // Anmeldung des OKButtonAdapter beim OK-Button myOKButton = new Button("OK"); myOKAdapter = new OKButtonAdapter(); myOKButton.addActionListener(myOKAdapter); // Anmeldung des CancelButtonAdapter beim Cancel-Button myCancelButton= new Button("Cancel"); myCancelAdapter = new CancelButtonAdapter(); myCancelButton.addActionListener(myCancelAdapter); Übertragung von Events Die JavaBeans-Komponenten können über mehrere Event-Listener verfügen. Mehrere Event-Listener dürfen sich also bei einer einzigen Event-Quelle zur gleichen Zeit anmelden und somit von der Quelle Events empfangen. Diese Art, Events zu übertragen, wird als Multicast-Übertragung bezeichnet. Die Event-Quelle muss sich in diesem Fall für alle Event-Listener registrieren können, was man durch die Verwendung eines java.util.Vector-Objekts erreicht. Gleichzeitig unterstützt Java die Unicast-Übertragung von Events. Hierbei handelt es sich um einen einzigen Listener, der zu einem bestimmten Zeitpunkt verwendet wird. Abb. 9.8: Unicast-Übertragung eines Ereignisses Während in Abb. 9-8 die Unicast-Übertragung eines Events dargestellt ist, wird in Abb. 9-9 die Multicast-Übertragung eines Events betrachtet. Hieraus ist ersichtlich, dass mehrere an einem Event interessierte Event-Listener den Event gleichzeitig empfangen können. Abb. 9.10: Multicast-Übertragung eines Ereignisses In diesem Unterkapitel wurde das Grundkonzept der Event-Behandlung von JavaBeans beschrieben. Events ermöglichen die Interaktion mit und zwischen JavaBeans-Komponenten. Das Event-Modell von JavaBeans beruht auf dem Event-Modell, das auch bisher in Java verwendet wird. Dies führt zu einer einfachen Verarbeitung von Ereignissen und daher auch zu keinem neuen Aufwand beim Erlernen des Komponentenmodells von JavaBeans. Es wurde weiterhin verdeutlicht, wie Event-Quellen und Event-Listener verwendet werden können, um den Event-Fluss zwischen Beans zu verwalten.