# Event-Handling: Was steckt dahinter?



## kp007 (13. Mrz 2004)

Hallo java-forum.net Community,

Ich habe da eine Verständnisfrage bezüglich Event-Handling in Java.

Nehmen wir z.B. einen Button. Wird jedesmal wenn ich auf den Button klicke ein Event von diesem Button in Echtzeit ausgelöst und an einen Listener geschickt? Ich meine, prüft das „System“ jedes mal , ob ich die addListener() – Methode benutze oder nicht? Denn wenn es jedesmal, wenn ich auf den Button klicke der Button ein Event  auslöst und zwar unabhängig davon ob ein Listener existiert oder nicht, dann ist das doch Datenverschwendung. 

Oder ein anderes Beispiel : JFrame oder Frame.

Ich verstehe das so . Wenn ich die addWindowListener()- Methode verwende, sage ich dem Programm doch nur, was es machen soll, wenn es ein Event „abfängt“ oder ist da mehr dahinter?

Kann mir jemand mal dieses Delgationsmodell verständlich erklären. Ich brauche nur die Theorie. Die Ereignisbehandlung an sich macht mir keine Probleme und anwenden kann ich sie auch, aber ich will es auch richtig verstehen. Deswegen meine abschließende Frage : Was läuft da wirklich ab ???

Gruß,


P.S. : Bitte auf alle Saätze, die mit einem ? enden, eingehen


----------



## Beni (13. Mrz 2004)

Der JButton verschickt sein ActionEvent sobald er ein MouseEvent erhält. Das macht er immer, auch wenn keine Listeners bei ihm registriert sind.

Das ist aber keine schlimme Verschwendung, zum einen benötigt eine Instanc von ActionEvent nicht sehr viel Platz, zum anderen ist jeder normale Button mit (mindestens) einem ActionListener ausgestattet. Denn es ist selten so, dass der Programmierer einen Button irgendwo hinzufügt, und diesen Button gar nicht abhört. (Was würde ein "leerer" Button auch für einen Sinn haben?)


Eine kurze Zusammenfassung aus dem Buch Java ist auch eine Insel

Das Ganze nennt sich "Delegation Model".

Es gibt zwei Teile: die _Event Source_, und den _Event Listener_.

Die Source (z.B. ein Button) ist eine zwar funktionsfähige Klasse, die aber so wenig Code wie möglich hat.
Sobald die Source irgendetwas komplexeres ausführen sollte, reicht sie diese Aufgabe weiter. (delegiert sie an jemand anderen).

Der Befehlsempfänger ist der (oder besser die) Listeners. Er wird von der Source zu genau bestimmten Zeitpunkten aufgerufen, und führt eine komplexere Aufgabe durch.

Dieses Model bietet eine wunderschöne Trennung von "Daten" und "GUI". Bis auf eine kleine Verknüpfung haben der Listener und die Source nichts miteinander zu tun. Beide können sich auf ihr Kerngeschäft konzentrieren (Button: gut aussehen, Listener: komplexer Algorithmus)


Diese Model bietet auch eine hohe Flexibilität:

Der Listener kann (während der Laufzeit) von der Source getrennt werden und anderen Sourcen hinzugefügt. Auch können mehrere Listener gleichzeitig eine Source überwachen (z.B. ein Listener der die Aktion ausführt, ein anderer der eine Log-Datei schreibt). Oder ein Listener kann auf mehrere Sources verteilt sein (einmal das Menüitem, das andere mal der Toolbar-Button).


Weil das alles so schön ist, wird das Delegation Model auch in die umgekehrte Richtung verwendet. Die Daten sind die Event Source, und die GUI implementiert die Listeners. Das kann man z.B. beim DefaultTableModel und dem JTable schön sehen. Wird dem TableModel eine neue Row hinzugefügt, sendet es ein TableModelEvent aus, welches das JTable empfängt.


----------



## kp007 (13. Mrz 2004)

Müsste es aber dann keine Fehlermeldung geben, wenn der Button ein Event losschickt und gar kein Listener angemeldet ist. Der Button muss doch "in Laufzeit" eine Methode aufrufen um sein Event an den Listener zu schicken und wenn überhaupt kein Listener angemeldet ist ? 

Wie könnte das konkret ( welche Methode, Aufruf, Statemnet) aussehen, wenn z.B. das X (Schließen) an der rechten oberen Fensterecke ein Event an den WindowListener schickt. 

Da muss eine Struktur dahinter stecken , sonst macht die Verwendung von Schnittstellen keinen Sinn. 

Gruß


----------



## Beni (13. Mrz 2004)

Nein, es müsste keinen Fehler geben. Stell dir das ganze als Array vor, bei dem jedes Element aufgerufen wird, wenn keine da sind, wird die Schlaufe einfach nur 0 mal durchgegangen.

Es hat einen Thread (EventDispatchThread), der laufend Mouse-, Key-, und andere low-level-Events aufnimmt, und welcher die Dialoge, Frames und Windows davon unterrichtet. Diese Events werden weitergereicht bis die Component gefunden wurde, die betroffen ist (z.B. diejenige die den Fokus hat, bei einem KeyEvent).

Dann verarbeiten diese Components das low-level-Event und stellen (vielleicht) ein high-level-Event her (z.B. ein ActionEvent).
Dieses High-Level-Event wird dann an die Listener weitergeschickt.

Die Struktur ist dieser Aufruf, der im EventDispatchThread beginnt, und über mehrere Methoden bis zu einem Listener geleitet wird.

Was dein konkretes Beispiel angeht: Hier die Aufrufe, die in einem "actionPerformed" endet (die untere Methode ruft jeweils die obere auf):





> _Thread [AWT-EventQueue-0] (Suspended (breakpoint at line 23 in FrameTest$1))_
> FrameTest$1.actionPerformed(ActionEvent) line: 23  //<<<<<<<<<<<<<<<<<< dies ist die selbstgeschriebene Methode
> JButton(AbstractButton).fireActionPerformed(ActionEvent) line: not available
> AbstractButton$ForwardActionEvents.actionPerformed(ActionEvent) line: not available
> ...




Nimm mal einen Debugger, und beobachte selbst ein bisschen, was im Hintergrund passiert. Das nützt dir vielleicht mehr, als nur Erklärungen.


----------



## Tobias (13. Mrz 2004)

Ich hatte das alles schon mal heute morgen geschrieben, und dann habe ich den Text verloren, weil meine Session abgelaufen war - ARGH!

Ich versuch es noch einmal:

Ich halte es für sinnvoll, sich das Model anhand von Code klar zu machen - nicht Code zur Verwendung, sondern Code zur Implementierung des Listener-Patterns. Ich möchte dabei nicht mit dem WindowListener hantieren, wie von dir vorgeschlagen, weil der einigermaßen kompliziert ist (in Grundzügen aber genauso funktioniert) - schließlich reagiert der WindowListener auf andere Listener wie den MouseListener!
Bauen wir uns lieber einen eigenen Listener, der nicht so tief ans Beriebssystem ran muß wie der WindowListener. Mein Vorschlag wäre ein ChangeListener, der es uns erlaubt, auf Änderungen in der Struktur von Collections zu reagieren.

Zunächst einmal brauchen wir ein Interface für die Listener, um Typsicherheit bei der Übergabe solcher Objekte zu erhalten und um die zu implementierende Methode eindeutig festzulegen:


```
public interface ChangeListener {
    public abstract changeHappened(ChangeEvent);
}
```

Das Interface definiert nur eine einzige Methode: changeHappened(). Dieser Methode wird ein Objekt übergeben, das alle benötigten Informationen zur Veränderung in der Collection enthält.
In unserem speziellen Fall könnte das Objekt wie folgt aussehen:


```
public ChangeEvent {
    public static int REMOVED = 1;
    public static int ADDED = 2;
    public static int RESORTED = 3;

    int index;
    int type;

    public ChangeEvent(int index, int type) {
        if(type == RESORTED) {
            this.index = -1;
        } 
        else {
            this.index = index;
        }

        this.type = type;
    }

    public int getType() {
        return type;
    }

    public int getIndex() {
        return index;
    }
}
```

Aus diesem Objekt lassen sich alle wichtigen Informationen ziehen, die mit der Strukturänderung in der Collection zu tun haben. Fehlt noch ein Objekt, das dieses Event verschickt. Dieses Objekt muß eine Liste mit allen Objekten implementieren, die über Änderungen informiert werden wollen (die Listeners), und folgende drei Methoden:
addChangeListener(), removeChangeListener() und fireChangeUpdate(). Die ersten beiden sollte klar sein, die dritte ist dafür zuständig, das die Events tatsächlich versandt werden.


```
public class DataChangingOne {

    private Vector changeRecipients; // die Liste der Listener!
    private Vector importantVector; // der Vector, dessen Strukturänderungen weitergegeben werden!

    public DataChangingOne() {
        changeRecipients = new Vector();
        importantVector = new Vector();
    }

    public void addChangeListener(ChangeListener listener) {
        changeRecipients.add(listener);
    }

    public void removeChangeListener(ChangeListener listener) {
        changeRecipients.remove(listener);
    }

    public void fireChangeUpdate(ChangeEvent chEv) {
        Iterator recIt = changeRecipients.iterator();

        while(recIt.hasNext()) {
            ((ChangeListener)recIt.next()).changeHappened(chEv);
        }
    }

    public void addSomethingToImportantVector(Object object) {
        importantVector.add(object);

        fireChangeUpdate(new ChangeEvent(importantVector.indexOf(object), ChangeEvent.ADDED));
    }

// Denk dir noch ein paar Funktionen, die etwas mit importantVector anstellen...

}
```

Mit den Swing/AWT-Events funktioniert das genauso... Der Schlüssel ist die Methode fire...Update() und der Trick mit den Interface-Typen. Hoffe, dir geholfen zu haben.

mpG
Tobias

[Edit]_Verdammt, nur zweiter  - wenn ich heute morgen nur nicht ne halbe Stunde zum Tippen gebraucht hätte..._[/Edit]


----------



## kp007 (13. Mrz 2004)

Noch ein Letztes : 

Was ist mit der Schnittstellen-Geschichte ? 
Irgendwann muss doch der Button sagen : schickeEvent(Schnittstelle.tue_Etwas(Event))  - oder so ähnlich ?

Interessiere mich was das Programm während der Laufzeit alles macht, vor allem wie mit Events umgegangen wird. Könntet ihr mir Tipps geben, wo ich das nachlesen, ausprobieren oder „erfahren“ kann. Den Tipp mit dem Debugger gehe ich nach.


Thomas :

Danke für die Antwort, ich werde mir das mal in Ruhe durchlesen. Eine Winzigkeit, die ich beim Überfliegen nicht ganz verstanden habe 


```
public void fireChangeUpdate(ChangeEvent chEv) { 
        recIt = changeRecipients.iterator(); 

        while(recIt.hasNext()) { 
            ((ChangeListener)recIt.next()).changeHappened(chEv); 
        } 
    }
```

Was ist dieses recIt und wo kommt es her ?

Liege ich mit meiner obigen Vermutung (Schnittstellen) richtig oder muss ich da „anders“ an die Sache rangehen ?


----------



## Illuvatar (13. Mrz 2004)

Ich vermute mal, Tobias hat einfach nur ein "Iterator" vor dem recIt vergessen.

Der Button macht vermutlich processActionEvent, in welcher Methode wahrscheinlich in jedem hinzugefügten ActionListener actionPerformed aufgerufen wird.


----------



## Tobias (13. Mrz 2004)

Illuvatar hat recht, ich habe das Iterator vergessen. recIt ist der Iterator über den Vector changeRecipients.

Der Button tut genau das, was meine Klassen auch tun - das Äquivalent zu fireChangeUpdate() ausführen, was allen registrierten Listenern das Event schickt.

mpG
Tobias


----------



## kp007 (14. Mrz 2004)

Ich glaube ich habe es kapiert. Es dauert, bis es in "Fleisch und Blut" übergeht  :roll: 

Danke


----------



## Tobias (14. Mrz 2004)

> Es dauert, bis es in "Fleisch und Blut" übergeht



Sorry, wenn ich das Verstehen mit englischen Variablen- und Methodennamen verlangsamt haben sollte - aber ich hasse Deutsch-Englisch-Gemisch-Code, der nur für einen Inländer leichter zu verstehen ist... Briefe schreibe ich ja auch nicht gemischt ...

mpG
Tobias


----------



## kp007 (18. Mrz 2004)

@tobias

Ich hätte noch eine letzte Frage zum Event-Handling. 

Du hast in deinem Interface ChangeListener nur eine Methode, nämlich changeHappened(ChangeEvent). 

Wie sähe der komplette Code aus, wenn du mehr als eine Methode (im Interface) hättest. 

Bitte, mach dir nochmal die Mühe  

Danke


----------



## kp007 (18. Mrz 2004)

von Tobias :

Im Interface sind die Methoden definiert, die ein Objekt, dass auf Änderungen reagieren will implementieren muss. In unserem Beispiel könnte so ein Objekt ja zum Beispiel für jeden Änderungstyp eine eigene Methode implementieren. Dann sieht das ganze wie folgt aus: 

Das Interface: 


```
public interface ChangeListener { 
    public abstract deleted(ChangeEvent e); 
    public abstract added(ChangeEvent e); 
    public abstract resorted(ChangeEvent e); 
}
```

Das ChangeEvent-Objekt: 

```
public ChangeEvent { 

    int index; 

    public ChangeEvent(int index) { 
        this.type = type; 
    } 

    public int getIndex() { 
        return index; 
    } 
}
```

Das eventproduzierende Objekt: 

```
public class DataChangingOne { 

    public static final int ADDED = 1; 
    public static final int REMOVED = 2; 
    public static final int RESORTED = 3; 

    private Vector changeRecipients; // die Liste der Listener! 
    private Vector importantVector; // der Vector, dessen Strukturänderungen weitergegeben werden! 

    public DataChangingOne() { 
        changeRecipients = new Vector(); 
        importantVector = new Vector(); 
    } 

    public void addChangeListener(ChangeListener listener) { 
        changeRecipients.add(listener); 
    } 

    public void removeChangeListener(ChangeListener listener) { 
        changeRecipients.remove(listener); 
    } 

    public void fireChangeUpdate(ChangeEvent chEv, int type) { 
        Iterator recIt = changeRecipients.iterator(); 

        while(recIt.hasNext()) { 
            if(type == ADDED) { 
                ((ChangeListener)recIt.next()).added(chEv); 
            } 
            if(type == REMOVED) { 
                ((ChangeListener)recIt.next()).removed(chEv); 
            } 
            if(type == RESORTED) { 
                ((ChangeListener)resIt.next()).resorted(chEv); 
            } 
        } 
    } 

    public void addSomethingToImportantVector(Object object) { 
        importantVector.add(object); 

        fireChangeUpdate(new ChangeEvent(importantVector.indexOf(object), ADDED); 
    } 

    public void resortImportantVector() { 
        // resort 
        
        fireChangeUpdate(new ChangeEvent(-1), RESORTED); 
    } 

// Denk dir noch ein paar Funktionen, die etwas mit importantVector anstellen... 

}
```

Im Wesentlichen handelt es sich um eine Verlagerung der Arbeit aus dem Event-Objekt in das Sende-Objekt. Das hat gewisse Vorteile, aber eben auch ein paar Nachteile. Zu den Vorteilen gehört bestimmt, das ein Listener jetzt feiner bestimmen kann, auf welche Events er reagiert - er muss zwar alle Methoden des Interfaces implementieren, aber die Methoden für Events, die ihn nicht interessieren, läßt er einfach leer. 
Nachteil ist das Durchhalten von Standards (zum Beispiel Index = -1 bei RESORTED). Diese Konvention durchzusetzen wird jetzt nämlich Bestandteil des sendenden Objekts, die aber unte Umständen von verschiedenen Programmierern hergestellt werden - und Konventionen sollte man möglichst im Code durchsetzen und nicht durch eher unverbindliche "Absprachen" in der Dokumentation, an die sich am Ende eh keiner hält. 
Im Endeffekt sind das aber eher philosophische Vor- und Nachteile - die reine Implementation funktioniert genauso wie bei einer reagierenden Methode. Ich empfehle dir, dir über folgendes Thema weitere Informationen zu besorgen: 

-Interfacetypen (Objekte können über ihre Interfaces typisiert werden - meist bei Kapitel über Vererbung zu finden), 
z.B. bei www.javabuch.de 

mpG 
Tobias 

(...)

_________________
Freies Mitglied der Volksfront von Judäa[/code]


----------

