Artikel - EventDispatcher in ActionScript 2

Logo

Traumberuf Dispatcher

Dispatcher. Das klingt wichtig. Das klingt nach jemandem, der enorm viel Verantwortung trägt und deswegen Respekt verdient. Das klingt nach jemandem, der früh um 6 Uhr aufsteht und einen für heutige Verhältnisse erstaunlich geregelten Tagesablauf an den Tag legt. Google und Babelfish etwa bieten als Übersetzung den Fahrdienstleiter an. Das Wörterbuch der TU Chemnitz zeigt sich mit Rechenzeitverteiler auf der Höhe der Zeit und Leo weiß um die immens große Vielfalt bei Bahnberufen:

[Transport] - Betriebsbediensteter, der insbesondere bei Schnellbahnen die Fahrzeuge bzw.Züge abfertigt. Er ist Bahnsteigwärter, Haltestellenwärter, Weichensteller und Zugbegleiter.

Unter den insgesamt 16 Treffern für Dispatcher befindet sich ein Begriff, der die Funktion eines 'Event Dispatcher' in ActionScript 2 recht gut beschreibt: Versender. Ein 'Event Dispatcher' ist also offensichtlich jemand, der Ereignisse an die empfangsbereite Kundschaft versendet. Entsprechend werden dabei Ereignisse (und das ist neu bei diesem Ereignismodell) als Objekte betrachtet und verschickt.

Bescheid vom Meldeamt

Die Implementierung des Ereignismodells in ActionScript 2 orientiert sich an der neuesten W3C-Spezifikation, welche zwar hauptsächlich auf baumartige Strukturen zielt, aber nicht ausschließlich.

Die ActionScript-Klasse EventDispatcher, zu finden im Ordner <Flash MX 2004>\en\First Run\Classes\mx\events, hat dabei analog zum aus Flash MX bekannten AsBroadcaster selbst nur eine statische initialize()-Methode, über deren Aufruf eine beliebige Klasse als Event Dispatcher befähigt wird. Die benötigten Methoden werden dabei wie beim AsBroadcaster zur Laufzeit draufgepackt:

Da eine Klasse aber nicht weiß, dass ihr selbst bzw. ihren Instanzen im weiteren Verlauf drei neue Flügel wachsen, müssen die obigen Methoden explizit als Eigenschaften für die Klasse deklariert werden, um den Compiler zufrieden zu stellen. Betrachten Sie das Meldeamt Ihrer Wahl als Versender von Bescheiden, dann sähe das Ganze so aus:

// Meldeamt.as
import mx.events.EventDispatcher;
			

class Meldeamt
{
	private var city:String;
	
	private var dispatchEvent:Function;
	public var addEventListener:Function;
	public var removeEventListener:Function;
	
	public function Meldeamt(city:String)
	{
		EventDispatcher.initialize(this);
		this.city = city;
	}
	
	public function sendeBescheid():Void
	{
		this.dispatchEvent({type:"onBescheid", target:this});
	}
	
	public function getCity():String
	{
		return this.city;
	}
}

Wie von unseren Bürgern und Bürgerinnen erwartet, werden sich diese nach einem Umzug beim Meldeamt registrieren und bekommen in Zukunft von dort mehr oder weniger bedeutungsvolle Bescheide.

// fla
var einwohner:Object = new Object();
einwohner.onBescheid = function(e)
{
	trace("Brief vom Meldeamt " + e.target.getCity());
};

var amt:Meldeamt = new Meldeamt("Berlin");
amt.addEventListener("onBescheid", einwohner);
amt.sendeBescheid();

Das Ausgabefenster zeigt beim Testen des Films folgendes an:

Brief vom Meldeamt Berlin

Begrüßungsgeld

Nun verschickt eine Meldbehörde ja nicht nur Bescheide, sondern hat noch viele weitere Dienstleistungen zu bieten.

Ein besonders günstiges Schnäppchen scheint mir etwa die 'Lebensbescheinigung' für sagenhafte €4,09. Wer in der Nachbarschaft noch weitere Mitbürger unter den Lebenden wähnt, für den wird es unter'm Strich sogar noch günstiger: Jeder lebende Zweibeiner kostet nur noch zusätzlich €1,53. Wesentlich teurer wird hingegen der 'Leichenpass', welcher mit stolzen €18,92 zu Buche schlägt. Dafür bekommt man in gut sortierten Buchläden schon ein halbes Fachbuch. Studierende, welche in den Genuß des Begrüßungsgelds der Stadt Berlin von €110 kommen wollen, haben dafür ein spezielles Formular auszufüllen.

Die Erweiterung der oben abgebildeten Klasse 'Meldeamt' gestaltet sich relativ einfach und zeigt gleichzeitig eine subtile Variation, wie man mit dem Ereignisobjekt arbeiten kann. Im vorherigen Beispiel hatte der Einwohner den Namen der Stadt (z.B. Berlin) über die target-Eigenschaft des Ereignisobjekts selbst 'abgefragt'. Man sagt, der Empfänger hat sich alle benötigten Informationen über die bekannten öffentlichen Methoden - hier also über die getCity()-Methode - aus der Quelle gezogen (engl. pull).

Dem Studenten hingegen wird der Geldbetrag als Eigenschaft des Ereignis-Objekts förmlich auf's Auge gedrückt (engl. push). Der 'Push'-Weg hat den Vorteil, dass sich die Quelle nicht nach außen öffnen muss. Der Daten-Transfer verläuft also so, wie man das von einer Bestechungsgeld-Transaktion in Berlin erwartet: unter der Hand. Beim 'Pull'-Ansatz dagegen steht das Tor offen. Es ist ja schließlich kein Geheimnis, dass sich das Meldeamt in Berlin befindet.

class Meldeamt
{	
	...
			
	public function ueberweiseGeld():Void
	{
		this.dispatchEvent({type:"onBegruessung", target:this, betrag:110.00});
	}
		
	...
}

Und so klingelt die Kasse in der Studenten-WG:

// fla
...

var student:Object = new Object();
student.onBegruessung = function(e)
{
	trace(e.betrag +  + " Euro von der Stadt " + e.target.getCity());
};

amt.addEventListener("onBegruessung", student);
amt.ueberweiseGeld();

Das Ausgabefenster zeigt beim Testen des Films folgendes an:

Brief vom Meldeamt Berlin
110 Euro von der Stadt Berlin

Was lernen wir daraus? Im Gegensatz zum AsBroadcaster, wo sich ein Objekt bei einer Ereignisquelle pauschal für alle Ereignisse anmeldet, muss sich ein Objekt bei einem EventDispatcher explizit für ein Ereignis registrieren, um davon informiert zu werden. Den Studenten interessiert natürlich zuerst das Begrüßungsgeld. Der gemeine Bürger hat hingegen auf diese Finanzspritze keine Chance, also braucht er gar nicht erst anfangen, das Formular auszufüllen, sprich sich dafür zu registrieren.

Unter der Haube

Wagen wir einen Blick unter die Haube der EventDispatcher-Klasse.

Zu finden wie gehabt im Ordner: <Flash MX 2004>\en\First Run\Classes\mx\events

/**
* dispatch the event to all listeners
* @param eventObj an Event or one of its subclasses describing the event
*/
	function dispatchEvent(eventObj:Object):Void
	{
		if (eventObj.target == undefined)
			eventObj.target = this;

		this[eventObj.type + "Handler"](eventObj);

		// Dispatch to objects that are registered as listeners for
		// this object.
		this.dispatchQueue(this, eventObj);
	}

Da fällt als erstes auf, dass die target-Eigenschaft im Ereignisobjekt, wenn nicht anders angegeben, immer eine Referenz auf die Quelle selbst enthält. Wir hätten also in den obigen Beispielen das Ereignisobjekt kürzer formulieren können:

this.dispatchEvent({type:"onBescheid"});
this.dispatchEvent({type:"onBegruessung", betrag:110.00});

Als nächstes stolpern wir interessiert über die addEventListener()-Methode und stellen fest: Eine Instanz, welche als EventDispatcher fungiert, verwaltet für jedes Ereignis eine separate Warteschlange (engl. queue), in welcher sich die Listener-Objekte drängeln, um später benachrichtigt zu werden. Der Bezeichner der Warteschlange ergibt sich (mit einem "__q_" Prefix versehen) aus dem Namen des Ereignisses. Aufpassen: Hier kann Hinz und Kunz eine Schlange für Ereignisse eröffnen, die es gar nicht gibt!

/**
* add a listener for a particular event
* @param event the name of the event ("click", "change", etc)
* @param the function or object that should be called
*/
	function addEventListener(event:String, handler):Void
	{
		var queueName:String = "__q_" + event;
		if (this[queueName] == undefined)
		{
			this[queueName] = new Array();
		}
		_global.ASSetPropFlags(this, queueName,1);

		EventDispatcher._removeEventListener(this[queueName], event, handler);
		this[queueName].push(handler);
	}

Den dicksten Brocken gibt's zum Schluß: Die interne dispatchQueue()-Methode, in welcher die eingetragenen Listener der Reihe nach benachrichtigt werden.

	// internal function for dispatching events
	function dispatchQueue(queueObj:Object, eventObj:Object):Void
	{
		...
					// this is a backdoor implementation that
					// is not compliant with the standard
					if (o.handleEvent == undefined)
					{
						o[eventObj.type](eventObj);
					}
					else // this is the DOM3 way
					{
						o.handleEvent(eventObj);
					}
		...

	}

Wie der Quellcode offenbart, wäre laut W3C Spezifikation im Gegensatz zu unserem bisherigen Vorgehen (über die 'backdoor implementation') nur ein Event-Handler erforderlich: handleEvent(eventObj). Ein passendes Interface für EventListener, welche sich an diesen Standard halten wollen, gibt es leider nicht (wobei man anmerken muss, dass in Flash MX 2004 aus unerklärlichen Gründen generell keine Schnittstellen in den Standard-Bibliotheken zu sehen sind).

Zu beachten ist an dieser Stelle: ein handleEvent()-Handler hat immer Vorrang vor neben-buhlenden Ereignisprozeduren.

Sonderausstattung

Wer mit komplexeren Anwendungen arbeitet, tut sicherlich gut daran, die zwar einfache, aber etwas fummlige und fehleranfällige Erstellung der Ereignisobjekte in der Form {type:"onBescheid", target:this, ... } gegen eine Instanzierung von wiederverwendbaren Event-Klassen einzutauschen, was in der einfachsten Form so aussehen könnte:

class EventBescheid
{
	public var type:String;
	public var target:Object;
	
	public function EventBescheid(target:Object)
	{
		this.type = "onBescheid";
		this.target = target;
	}
}

Das passende Ereignisobjekt lässt sich dann bequem wie folgt verschicken:

	public function sendeBescheid():Void
	{
		this.dispatchEvent(new EventBescheid());
	}

Den Empfänger kann man entsprechend mit einer Ereignistyp-Angabe vorbereiten, tapfer auf den Bescheid zu reagieren.

var einwohner:Object = new Object();
einwohner.onBescheid = function(e:EventBescheid)
{
	trace("Brief vom Meldeamt " + e.target.getCity());
};

Bzw. auf dem Standard-Weg mit einem handleEvent()-Handler und Abfrage des Ereignistyps:

var einwohner:Object = new Object();
einwohner.handleEvent = function(e)
{
	if(e instanceof EventBescheid) {
		trace("Brief vom Meldeamt " + e.target.getCity());
	}
};

Fragen und Anregungen jederzeit willkommen.

Lesestoff

Weiterführende Links: