Das Ereigniskonzept in AS3, Abonnement-Metapher

 

Zur Laufzeit eines Programms können Ereignisse (Events) auftreten, auf die das Programm reagieren kann.

Grundsätzlich wird zwischen

  • built-in events, die Zustandsänderungen in der Laufzeitumgebung beschreiben, und
  • custom events, die Änderungen im Programmzustand beschreiben,

unterschieden.

Für den Anfang sind nur die built-in events relevant und davon gibt es sehr viele. Typische built-in events sind

  • Mausereignisse, wie das Bewegen der Maus, das Drücken und Loslassen einer Maustaste, das Bewegen des Mauszeigers über ein Anzeigeobjekt,
  • Tastaturereignisse, wie das Drücken und Loslassen einer Tastaturtaste,
  • das ENTER_FRAME-Ereignis, wenn der Abspielkopf in der Timeline in das nächste Bild springt,
  • Timer-Ereignisse, die nach Ablauf einer vorgegebenen Zeitspanne eintreten,
  • Ladeereignisse, die während des Ladevorgangs von swf- und anderen Dateien auftreten.

Custom events beziehen sich auf den Inhalt des Programms. Diese Ereignisse werden von den ProgrammiererInnen definiert. Z.B. ein Ereignis, das eintritt, wenn ein bestimmter Punktestand in einem Spiel erreicht ist oder ein ausgefülltes Formular abgeschickt wird.

Immer wenn ein built-in Ereignis eintritt, wird automatisch ein Ereignisobjekt von der Klasse Event (oder deren Subklassen) erzeugt. 

 
Abonnement-Metapher

Bei jedem Ereignis (event) wird ein Ereignisobjekt (event object) erzeugt. Aber wer kann mit diesem Ereignisobjekt etwas anfangen?
Um den Vorgang zu verstehen, ist der Metapher des Abonnements sehr hilfreich.

Ein Objekt kann einen bestimmten Ereignistyp abonnieren. Das Objekt wird als Ereignisziel oder event target bezeichnet. Beim Vorgang des Abonnierens übergibt das Ereignisziel mit dem Befehl addEventListener dem Ereignistyp einen event listener.
Ein event listener (auch Ereignisprozedur oder event handler genannt) ist eine vorher definierte Funktion, die beschreibt, was zu tun ist, wenn ein Ereignis von dem bestimmten Typ eintritt. Ein event listener erhält als einzigen Input eine Instanz des Ereignisklasse.

Wenn nun ein Ereignis von dem abonnierten Ereignistyp eintritt,

  • wird für das konkrete Ereignis eine Instanz der entsprechenden Ereignisklasse erzeugt,
  • der event listener wird verständigt,
  • erhält als Eingabe die Instanz der Ereignisklasse und
  • und reagiert auf das Ereignis, d.h. der Programmcode der Funktion wird abgearbeitet.
Über die Eigenschaft target des Ereignisobjekts kann der event listener auf die Eigenschaften und Methoden des event targets zugreifen.

Das Abonnement kann wieder abbestellt werden, indem das Ereignisziel mit dem Befehl removeEventListener den event listener wieder aus der Liste der Abonnenten herausnimmt.

Natürlich können mehrere Objekte denselben Ereignistyp abonnieren und auch den denselben event listener verwenden.

 
Allgemeine Code-Struktur für das Event-Handling

Die Platzhalter sind fett geschrieben.

Definition des event listeners:
 

function eventListener (eventObject:EventType): void {
...
Programmcode, der die als Reaktion auf das Ereignis auszuführenden Aktionen angibt
und
eventObject.target enthalten kann
...
}

 
Ein Objekt (event target) abonniert einen bestimmten Eventtyp (= registrieren des event listeners):
 

eventTarget.addEventListener(EventType.EVENT_NAME, eventListener);

 
Ein Objekt (event target) storniert das Abonnement für einen bestimmten Eventtyp:
 

eventTarget.removeEventListener(EventType.EVENT_NAME, eventListener);

 
Wichtig ist, dass der EventType vom event listener mit dem EventType bei add/removeEventListener übereinstimmt.

 
Beispiel

In einer Flashdatei ist im Mainmovie auf der Bühne ein Movieclip mit dem Instanznamen kreis platziert und im ersten Bild in der Maintimeline steht folgendes Skript:
 

function enterFrameListener(e:Event):void {
trace(e.target);
}
this.addEventListener(Event.ENTER_FRAME,enterFrameListener);
kreis.addEventListener(Event.ENTER_FRAME,enterFrameListener);

Zuerst wird eine Ereignisprozedur enterFrameListener definiert mit dem Ereignistyp Event als Input. Die Funktion macht nichts anderes als
mit der trace-Funktion das Ereignisziel e.target im Ausgabefenster anzuzeigen. (Die Eigenschaft target wird im letzten Kapitel genau behandelt.)
Anschließend abonniert das Mainmovie (bezeichnet durch this) mit this.addEventListener(Event.ENTER_FRAME,enterFrameListener) das Ereignis ENTER_FRAME, das immer eintritt, wenn der Abspielkopf in ein neues Bild wechselt.
genauso abonniert die Movieclipinstanz kreis dasselbe Ereignis. Beide Objekte benutzen dieselbe Ereignisprozedur enterFrameListener.

Nach dem Starten des Programms wird im Ausgabefenster bei jedem ENTER_FRAME-Ereignis folgendes ausgegeben:
 

[object MainTimeline]
[object Kreis]
 
Da sowohl das Mainmovie als auch die Movieclipinstanz kreis das Ereignis ENTER_FRAME abonniert haben und werden sie als Ereignisziele im Anzeigefenster ausgegeben.

 

Vertiefende Beispiele zu ENTER_FRAME-Ereignissen findet man im Modul Animation



Die wichtigsten Ereignistypen

Es gibt unzählige Eventklassen, die sich alle von der Basisklasse Event ableiten.
Die für den Anfang wichtigsten sind fett geschrieben:

  • ActivityEvent
  • AsyncErrorEvent
  • AutoLayoutEvent
  • CaptionChangeEvent
  • CaptionTargetEvent
  • ColorPickerEvent
  • ComponentEvent
  • ContextMenuEvent
  • DataChangeEvent
  • DataEvent
  • DataGridEvent
  • ErrorEvent
  • FocusEvent
  • FullScreenEvent
  • HTTPStatusEvent
  • IOErrorEvent
  • KeyboardEvent
  • LayoutEvent
  • ListEvent
  • MetadataEvent
  • MotionEvent
  • MouseEvent
  • NetStatusEvent
  • ProgressEvent
  • ScrollEvent
  • SecurityErrorEvent
  • SkinErrorEvent
  • SliderEvent
  • SoundEvent
  • StatusEvent
  • TextEvent
  • TimerEvent
  • TweenEvent
  • VideoEvent
  • VideoProgressEvent

Ereignislauf, Event-Phasen (nur für Displayobjekte)

Eine gute Übersicht über dieses Thema findet man in der Flash-Hilfe unter
Programmieren mit ActionScript 3.0 > Verarbeiten von Ereignissen > Ereignisablauf

Beim Auftreten von Ereignissen (events) löst der Flash-Player Ereignisobjekte (event objects) aus.
Wenn sich das Ereignisziel in der Anzeigeliste (display list) befindet, sendet der Flash-Player das Ereignisobjekt an die Anzeigeliste. Das Ereignisobjekt durchläuft dann die Anzeigeliste, bis das Ereignisziel erreicht ist.
Wenn das Ereignisziel kein Displayobjekt ist, wird das Ereignisobjekt direkt an das Ereignisziel gesandt.

Mit dem Ereignisablauf wird die Bewegung eines Ereignisobjekts innerhalb der Anzeigeliste beschrieben.
Wenn der Flash-Player ein Ereignisobjekt auslöst, durchläuft es die Hierarchie von der Bühne bis zum Zielknoten und zurück. Der Zielknoten ist das Anzeigelistenobjekt, in dem das Ereignis aufgetreten ist.

Wenn man beispielsweise auf das Vorderrad klickt, löst der Flash-Player ein Ereignisobjekt aus und verwendet die MovieClip-Instanz vorderrad  als Zielknoten.

 

 

Der Ereignisablauf ist konzeptionell in drei Abschnitte unterteilt:

  1. Der erste Abschnitt wird als Empfangsphase (capture phase) bezeichnet. Diese Phase besteht aus allen Knoten von der Bühne bis zum übergeordneten Knoten des Zielknotens.
    Im Beispiel: Instanz der Bühne > Maintimeline Landstrasse.swf > Spriteinstanz auto
     
  2. Der zweite Abschnitt ist die sogenannte Zielphase (target phase), die nur aus dem Zielknoten besteht.
    Im Beispiel: Movieclipinstanz vorderad
     
  3. Der dritte Abschnitt wird Aufstiegsphase (bubbling phase - Aufsteigen von Blasen) genannt. Diese besteht aus den Knoten, die vom übergeordneten Knoten des Zielknotens bis zur Bühne zurück durchlaufen werden.
    Im Beispiel: Spriteinstanz auto > Maintimeline Landstrasse.swf > Instanz der Bühne

In ActionScript 3.0 kann man Ereignis-Listener nicht nur dem Zielknoten, sondern auch jedem beliebigen Knoten entlang des Ereignisablaufs hinzufügen.

Die Möglichkeit, Ereignis-Listener entlang des Ereignisablaufs hinzuzufügen, ist dann hilfreich, wenn eine Benutzerschnittstellenkomponente aus mehr als einem Objekt besteht.

Im obigen Beispiel kann man z.B. dem ganzen Auto (der Spriteinstanz auto) einen Mausklick-Listener hinzufügen. Klickt man nun auf eines der beiden Räder, die Unterobjekte sind,  wird in der Empfangsphase und/oder Aufstiegsphase auch der Eventhandler des ganzen Autos (der Spriteinstanz auto) ausgeführt, obwohl nur das Rad der Zielknoten ist.

Ein weiteres Beispiel aus der Flash-Hilfe: Ein Button-Objekt enthält beispielsweise häufig ein Text-Objekt mit dem Schaltflächentext. Ohne die Möglichkeit, dem Ereignisablauf Listener hinzuzufügen, müssten Sie sowohl dem Button-Objekt als auch dem Text-Objekt einen Listener hinzufügen, um sicherzustellen, dass Sie Benachrichtigungen über Mausklickereignisse für die gesamte Schaltfläche erhalten. Der Ereignisablauf ermöglicht jedoch die Definition eines einzigen Ereignis-Listeners für das Button-Objekt, mit dem Mausklickereignisse für das Text-Objekt oder für die Bereiche des Button-Objekts verarbeitet werden, die nicht durch das Text-Objekt verdeckt sind.

Nicht jedes Ereignisobjekt durchläuft jedoch alle drei Phasen des Ereignisablaufs.

enterFrame-Ereignisse werden direkt an den Zielknoten gesendet und durchlaufen weder die Empfangsphase noch die Aufstiegsphase.

Mausereignis-Objekte vom Typ ROLL_OVER und ROLL_OUT haben keine Aufstiegsphase. Alle anderen Mausereignisse haben sowohl eine Empfangs- als auch eine Aufstiegsphase.

Die Ereignisobjekt-Eigenschaften bubbles, eventPhase, target, currentTarget

Die Eigenschaft "bubbles"

Im vorigen Kapitel wurde erklärt, dass nicht jedes Ereignisobjekt alle 3 Ereignisphasen des Ereignisablaufs durchläuft.

Jedes Ereignisobjekt, d.h. jede Instanz der Klasse Event, besitzt eine Eigenschaft bubbles, die nur gelesen werden kann.
Wenn bubbles den Wert true hat, dann durchläuft das Ereignisobjekt auch die Aufstiegsphase, sonst nicht.

Beispiel:

Mausereignis-Objekte vom Typ ROLL_OVER und ROLL_OUT haben keine Aufstiegsphase. Alle anderen Mausereignisse haben sowohl eine Empfangs- als auch eine Aufsteigsphase. Man kann das leicht selbst überprüfen:
Erstellen Sie auf der Bühne eine beliebige Movieclip-Instanz und geben dieser den Namen myClip.
Anschließend fügen Sie nachfolgendes Skript in das erste Frame der Timeline ein.
 

myClip.addEventListener(MouseEvent.ROLL_OVER,bubbleTest);
function bubbleTest(e:MouseEvent):void {
trace(e.bubbles);
}

Bei der entsprechenden Mausaktion wird der Wert von bubbles im Ausgabe-Fenster angezeigt.
Ersetzen Sie nun ROLL_OVER durch beliebige andere Mausereignis-Typen und prüfen die bubbles-Eigenschaft.
 

Die Eigenschaften "target" und "currentTarget"

Wenn man z.B. auf ein auf ein Displayobjekt, das auf der Bühne liegt, klickt, wird ein Mausereignis-Objekt vom Typ CLICK an die Displayliste weiter gegeben und durchläuft den Baum von der Bühne bis zu dem angeklickten Dispalyobjekt (= Zielobjekt) und wieder retour.
Wenn die Displayobjekte, die auf dem Weg zum Zielobjekt liegen, das click-Ereignis abonniert haben, wird der dazugehörigen EventListener aufgerufen. Daher müssen das Zielobjekt und das Objekt, das den EventListener aufruft, nicht übereinstimmen.

Um diesen Unterschied abfragen zu können, gibt es die Ereignisobjekteigenschaften target und currentTarget:
Über die Ereignisobjekteigenschaft target kann man immer das Zielobjekt des Ereignisses abfragen und
die Eigenschaft currentTarget liefert dem Eventlistener das Objek, das den Listener tatsächlich aufgerufen hat.

Beispiel:

Drei Movieclipinstanzen blau, gelb und rot liegen ineinander geschachtelt auf der Bühne.

Im ersten Frame der Maintimeline wird nachfolgendes Skript eingefügt, in dem sich die Bühne mit den EventListener showTarget beim mouseDown-Ereignis registriert. Der EventListener gibt im Ausgabe-Fenster die Namen der target- und currentTarget-Objekte aus.
 

this.stage.addEventListener(MouseEvent.MOUSE_DOWN, showTarget);

function showTarget(event:MouseEvent):void {
trace("target: "+event.target.name+" currentTarget: "+event.currentTarget.name);
}

Klickt man nun im Fenster des Flash-Players, dann liefert das Ausgabefenster:

Klick auf ...
Text im Ausgabefenster
weiße Fläche der Bühne
target: null   currentTarget: nul
rote Fläche
target: rot    currentTarget: null
gelbe Fläche
target: gelb   currentTarget: null
blaue Fläche
target: blau   currentTarget: null

Anmerkung: Da der Bühne kein Name zugeordnet ist, wird der Wert null für die Bühne ausgegeben.
currentTarget liefert immer die Bühne, da nur die Bühne einen EventListener registriert hat. target ändert sich jedoch, je nachdem worauf man geklickt hat.

Im nachfolgenden Video wird das  Beispiel zusammengefasst:
 

 

 

In welcher der drei Phasen wird ein Eventhandler ausgeführt?

Da man einen Eventlistener (Eventhandler) nicht nur dem Zielknoten, sondern auch jedem beliebigen Knoten entlang des Ereignisablaufs hinzufügen kann und dieser sowohl in der Empfangsphase als auch in der Aufstiegsphase durchlaufen wird, stellt sich die Frage, wann der Event-Handler ausgeführt wird.

Im ersten Kapitel wurde das Grundschema für das Registrieren eines Eventlisteners vorgestellt:
 

eventTarget.addEventListener(EventType.EVENT_NAME, eventListener);
 
Dabei wurde ein dritter möglicher Parameter useCapture von addEventListener weggelassen, der festlegt, ob der Eventlistener in der Empfangs- oder Aufstiegsphase ausgeführt wird.
 
eventTarget.addEventListener(EventType.EVENT_NAME, eventListener, useCapture);

Der Standardwert ist false, d.h. wenn man diesen Parameter nicht schreibt, ist der Wert false.
false
bedeutet, dass der Listener das Ereignis nur während der Ziel- oder Bubbling-Phase verarbeitet. D.h. standardmäßig wird zuerst (falls es einen gibt) der Eventlistener des Zielobjekts abgearbeitet und anschließend (falls registriert) die Eventlistener der übergeordneten Objekte.

true bedeutet, dass der Listener das Ereignis nur während der Empfangsphase und nicht während der Ziel- oder Bubbling-Phase verarbeitet. D.h. der Eventlistener des Zielobjekts wird nicht abgearbeitet.

Damit das Ereignis in allen drei Phasen aktiv ist, ruft man addEventListener() zweimal auf. Einmal ist useCapture auf true gesetzt, und beim zweiten Mal hat useCapture den Wert false
 

Die Eigenschaft "eventPhase"

Die drei Ereignisphasen werden in ActionScript 3.0 durch folgende drei Konstante beschrieben:

 Empfangsphase: EventPhase.CAPTURING_PHASE diese Konstante hat den Wert 1
 Zielphase: EventPhase.AT_TARGET
 diese Konstante hat den Wert 2
 Aufstiegsphase: EventPhase.BUBBLING_PHASE diese Konstante hat den Wert 3

Über die Eigenschaft eventPhase eines Ereignisobjekts kann festgestellt werden, in welcher der 3 Ereignisphasen des Ereignisablaufs der Eventlistener aufgerufen wurde. eventPhase liefert in Übereinstimmung zu den obigen Konstanten den Wert 1, 2 oder 3.
Sei event ein Ereignisobjekt, dann kann über einen der nachfolgenden Vergleiche eine Ereignisphase abgefragt und entsprechend darauf reagiert werden:
 

if (event.eventPhase == EventPhase.CAPTURING_PHASE) { ... }
if (event.eventPhase == EventPhase.AT_TARGET) { ... }
if (event.eventPhase == EventPhase.BUBBLING_PHASE) { ... }

 
Beispiel:

Wir verwenden noch einmal das Beispiel von oben und registrieren die Bühne, die Maintimeline und alle drei Movieclips sowohl für die Empfangsphase, als auch für die Ziel- und Aufstiegsphase beim mouseDown-Ereignis mit demselben Eventlistener, der im Ausgabe-Fenster die Eigenschaften target, currentTarget und eventPhase ausgibt.

Im ersten Frame der Maintimeline steht folgendes Skript:
 

this.addEventListener(MouseEvent.MOUSE_DOWN, eventFlow, false); // für Ziel- und Aufstiegsphase  
this.addEventListener(MouseEvent.MOUSE_DOWN, eventFlow, true); // für Empfangsphase
this.stage.addEventListener(MouseEvent.MOUSE_DOWN, eventFlow, false); // für Ziel- und Aufstiegsphase
this.stage.addEventListener(MouseEvent.MOUSE_DOWN, eventFlow, true); // für Empfangsphase
this.rot.addEventListener(MouseEvent.MOUSE_DOWN, eventFlow, false); // für Ziel- und Aufstiegsphase
this.rot.addEventListener(MouseEvent.MOUSE_DOWN, eventFlow, true); // für Empfangsphase
this.rot.gelb.addEventListener(MouseEvent.MOUSE_DOWN, eventFlow, false); // für Ziel- und Aufstiegsphase
this.rot.gelb.addEventListener(MouseEvent.MOUSE_DOWN, eventFlow, true); // für Empfangsphase
this.rot.gelb.blau.addEventListener(MouseEvent.MOUSE_DOWN, eventFlow, false); // für Ziel- und Aufstiegsphase
this.rot.gelb.blau.addEventListener(MouseEvent.MOUSE_DOWN, eventFlow, true); // für Empfangsphase

function eventFlow(event:MouseEvent):void {
trace("target:"+event.target.name+" currentTarget:"+event.currentTarget.name+" Phase: "+event.eventPhase);
}
 

Klickt man nun im Fenster des Flash-Players, dann liefert das Ausgabefenster:
 

Klick auf ...
Text im Ausgabefenster (Phase 2 = Zielphase wurde besonders gekennzeichnet)
weiße Fläche der Bühne
target: null   currentTarget: null   Phase: 2
rote Fläche
target: rot    currentTarget: null   Phase: 1
target: rot    currentTarget: root1  Phase: 1
target: rot    currentTarget: rot    Phase: 2
target: rot    currentTarget: root1  Phase: 3
target: rot    currentTarget: null   Phase: 3
gelbe Fläche
target: gelb   currentTarget: null   Phase: 1
target: gelb   currentTarget: root1  Phase: 1
target: gelb   currentTarget: rot    Phase: 1
target: gelb   currentTarget: gelb   Phase: 2
target: gelb   currentTarget: rot    Phase: 3
target: gelb   currentTarget: root1  Phase: 3
target: gelb   currentTarget: null   Phase: 3
blaue Fläche
target: blau   currentTarget: null   Phase: 1
target: blau   currentTarget: root1  Phase: 1
target: blau   currentTarget: rot    Phase: 1
target: blau   currentTarget: gelb   Phase: 1
target: blau   currentTarget: blau   Phase: 2
target: blau   currentTarget: gelb   Phase: 3
target: blau   currentTarget: rot    Phase: 3
target: blau   currentTarget: root1  Phase: 3
target: blau   currentTarget: null   Phase: 3

Anmerkung: root1 steht  für die Maintimeline.

Im nachfolgenden Video wird das Beispiel zusammengefasst. Sie bewegen am besten den Videoslider manuell und studieren die Ergebnisse im Ausagbe-Fenster.