Zusätzlich zum direkten Aufruf von Methoden bietet Director den Befehl call. Er hat folgende Syntax:
call (#handlerName, Skript/Instanz, {argumente...})
Der Call-Befehl unterscheidet sich vom direkten Aufruf einer Methode lediglich durch die Tatsache, dass der Methoden- bzw. Handlername ein Symbol ist. Dadurch kann er in einer Variablen gespeichert werden. Auf diese Weise lassen sich sogenannte Callbacks realisieren. Das bedeutet, dass sich ein Objekt bei einem anderen Objekt anmeldet, um dessen Ereignisse "abzuhören". Tritt ein Ereignis ein, schickt dieses Objekt Nachrichten an alle angemeldeten Objekte, die entsprechend reagieren können.
Prinzip eines Callbacks
Zum Beispiel kann sich ein Objekt, das ein Fenster öffnet, als Zuhörer dieses Fensters anmelden. Wird dieses Fenster geschlossen, schickt es eine Nachricht an den Zuhörer, die beispielsweise eine ausgewählte Option als Argument enthalten kann. Dies funktioniert ähnlich wie ein Funktionsaufruf, mit dem Unterschied, dass Director zwischen Aufruf des Objektes und Rückgabe des Ergebnis andere Aufgaben erledigen kann, z.B. Eventhandler ausführen. Das praktische am call-Befehl ist, dass er den Namen der aufzurufenden Methode als Argument enthält, dieser also variabel sein kann.
Ein Beispiel soll dies verdeutlichen. Wir wollen die Möglichkeit haben, ein Dialogfenster zu öffnen, dass einen beliebigen Text anzeigt und je einen Button für "Ja" und "Nein", um Dialoge der Form "Möchtest Du noch einmal Spielen" zu verwirklichen. Mit einem Funktionsaufruf würde das folgendermaßen aussehen:
on spielEnde
ergebnis = oeffneFenster("Möchtest Du noch einmal Spielen?")
case ergebnis of
#ja: ...
#nein: ...
end case
end spielEnde
Der Nachteil dieser Methode ist, dass Director den spielEnde()-Handler nicht verlässt, bis die Funktion oeffneFenster() zurückkehrt und in dieser Zeit auch keine Eventhandler abarbeiten kann. Das Ergebnis wäre ein scheinbares Einfrieren des Rechners.
Die Realisierung mittels Callback hat dieses Problem nicht. oeffneFenster() ist als Methode eines Objektes ausgeführt, hier gFensterManagerObj genannt, die als weitere Argumente den Namen der Methode erhält, die sie aufrufen soll, sowie eine Referenz des aufrufenden Objektes. Das Skript würde folgendermaßen aussehen:
global gFensterManagerObj
on spielEnde me
gFensterManagerObj.oeffneFenster("Möchtest Du noch einmal Spielen?",
#ergebnisAuswerten, me)
end spielEnde
on ergebnisAuswerten me, ergebnis
case ergebnis of
#ja: ...
#nein: ...
end case
end ergebnisAuswerten
Das Gegenstück gFensterManagerObj könnte z.B. so aussehen:
property mNachricht, mCallbackObjekt,
...
on oeffneFenster me, anzeigetext, nachricht, callbackobjekt
mNachricht = nachricht
mCallbackObjekt = callbackobjekt
...
end oeffneFenster
on fensterSchliessen me,
gewaehlteAntwort
-- gewaehlteAntwort ist #ja oder #nein
call(mNachricht, mCallbackObjekt, gewaehlteAntwort)
end fensterSchliessen
Der Vorteil ist, dass Director den spielEnde()-Handler sofort verlässt und der Fluss des Filmes nicht gestört wird. Objekte auf der Bühne können weiterhin Events erhalten. Das FensterManager-Objekt ist so allgemein gehalten, dass es von jedem Objekt aus aufgerufen werden kann.
Zusätzlich zu der oben angegebenen Syntax bietet call noch die Möglichkeit, statt einem Skript bzw. einer Skriptinstanz eine Liste von Skripten bzw. Skriptinstanzen anzugeben. Dadurch lässt sich eine Methode in einer Liste von Objekten aufrufen. Enthält ein Objekt die angegebene Methode nicht, wird kein Fehler ausgegeben, sondern das Objekt einfach ignoriert:
call (#handlerName, Liste von Skripten/Instanzen, {argumente...})
Diese Form wird dazu benutzt, eine Methode auf ein Liste von Objekten anzuwenden. Zum Beispiel kann ein MemoryManagerObjekt alle geladenen Objekte in einer Liste speichern und bei Bedarf die unload()-Methode aller Objekte aufrufen. Implementiert ein Objekt diese Methode nicht, wird es einfach ignoriert.
on unloadAll me
call(#unload, mGeladeneObjekteListe)
mGeladeneObjekteListe.deleteAll()
end unloadAll
Genauso wie call funktioniert auch der Befehl callAncestor, mit dem einzigen Unterschied, dass direkt die Methode eines ancestors aufgerufen wird. Deswegen sind die folgenden Befehle gleichwertig:
call(#draw, mGrafikObjekt.ancestor)
callAncestor(#draw, mGrafikObjekt)
Auch callAncestor kann auf eine Liste von Objekten ausgeführt werden. In diesem Fall kommt es zu keiner Fehlermeldung, falls ein Objekt in der Liste keinen ancestor hat.
![]() |
|||
![]() |
4.16 Beispiel 7: Warhol's Flowers welken | 5 Verhalten (Behaviors) und ihre Befehle | ![]() |