Zurück   Flashforum > Flash > ActionScript > Softwarearchitektur und Entwurfsmuster

Antwort
 
LinkBack Themen-Optionen Ansicht
Alt 13-01-2006, 10:55   #1 (permalink)
helpQLODhelp
 
Benutzerbild von bokel
 
Registriert seit: Feb 2002
Ort: Köln
Beiträge: 8.505
Event Bubbling in AS2

In Flex (und auch in Javascript) gibt es eine schönes Feature, das sich Event Bubbling nennt. Bubbling bedeutet, dass ein Event, nachdem es vom Empfänger verarbeitet worden ist, an seine Eltern weitergereicht wird. Die Events steigen also wie Luftblasen im Wasser nach oben.

In Flash haben wir so eine Hierarchie schon in die MovieClip Klasse eingebaut. Jeder MovieClip ist Kind eines anderen MovieClips. Der oberste MovieClip ist der Clip, auf den _root zeigt. Jeder MovieClip hat eine Eigenschaft _parent, die auf seinen Eltern-MovieClip zeigt. Indem wir dieser Eigenschaft folgen, können wir uns von jedem MovieClip bis zum root hochhangeln. Der root ist der oberste Clip und deshalb ist seine parent Eigenschaft null. Daran können wir erkennen, dass wir oben angekommen sind.

ActionScript:
  1. //hochhangeln bis zum root
  2. var o:MovieClip = this;
  3. while( o != null){
  4.      trace(o);
  5.      o = o._parent;
  6. }

Befinden wir uns z.B. gerade im MovieClip mit dem Pfad _level0.a.b.c, dann gibt das obige Script folgendes aus:
_level0.a.b.c
_level0.a.b
_level0.a
_level0
Wir durchlaufen also jeden Clip, bis wir ganz oben angekommen sind.

Den gleichen Weg nehmen beim Bubbling die Events. Wenn z.B. der MovieClip c ein release-Event erzeugt, dann bekommt erst c selbst das Event, dann b, dann a und zum Schluss _level0.

Das schöne daran ist jetzt, dass a nicht genau wissen muss, woher das Event kommt, also wo c genau in der Hierarchie liegt. Wenn er das Event empfangen will, muss er nichts weiter tun, als zu warten bis es bei ihm ankommt. Ohne Bubbling müsste er sich irgendwie direkt als Listener bei c anmelden, er müsste als den Pfad zu c kennen oder irgendeinen speziellen Mechanismus bereitstellen, wie z.B. dass c sich bei ihm anmeldet. Wenn man c in der Hierarchie verschiebt, muss man diesen Code dann entsprechend anpassen. Mit Bubbling ist es total egal wo c liegt, solange es unterhalb von a ist. Das Event kommt auf jeden Fall bei a an.

Jetzt gibt es ja schon diverse Event-Mechanismen wie z.B. den EventDispatcher von Macromedia oder den erweiteren GDispatcher, der z.B. in Alex Uhlmanns AnimationPackage benutzt wird. Es wäre schön, wenn wir den Dispatcher beibehalten könnten, und unser Bubbling einfach hinzufügen könnten. Genau das macht die folgende Klasse:

ActionScript:
  1. /**
  2. * Enable event bubbling for MovieClips, which utilize a EventDispatcher
  3. * or compatible
  4. * @author: Ralf Bokelberg <info@bokelberg.de>
  5. * @date: 2006/Jan/09
  6. * @license: LGPL
  7. */
  8. class util.EventDispatcherUtil{
  9.    
  10.     /**
  11.      * Make dispatcher a bubbling dispatcher
  12.      * Given that dispatcher has a method dispatchEvent(evt), this
  13.      * method is replaced by a method which additionally dispatches the event
  14.      * to the parent of dispatcher, unless evt.bubbles === false
  15.      * @param dispatcher - a MovieClip with a EventDispatcher compatible
  16.      * dispatchEvent method
  17.      * @return the changed dispatcher
  18.      */
  19.     public static function initializeBubbling( dispatcher:Object):Object{
  20.         var parentDispatcher = findParentDispatcher( dispatcher);
  21.         var oldDispatchEvent = dispatcher.dispatchEvent;
  22.         dispatcher.dispatchEvent = function( evt){
  23.             evt.currentTarget = this;
  24.             if( evt.eventPhase == undefined){
  25.                 evt.eventPhase = 2; //at target
  26.             } else {
  27.                 evt.eventPhase = 3; //bubbling
  28.             }
  29.             oldDispatchEvent.call( dispatcher, evt);
  30.             if( evt.bubbles !== false){
  31.                 parentDispatcher.dispatchEvent( evt);                 
  32.             }
  33.        };
  34.        return dispatcher;
  35.     }
  36.    
  37.     /**
  38.      * Dispatch the event to the next dispatcher
  39.      * @param dispatcher - a MovieClip with a EventDispatcher compatible
  40.      * dispatchEvent method. If the dispatcher is not a dispatcher, its parentchain
  41.      * is traversed
  42.      * @param evt - the event to be dispatched
  43.      * @return true, if the event has been dispatched, false otherwise
  44.      */
  45.     public static function dispatchEvent( dispatcher:Object, evt:Object):Boolean{
  46.         if( dispatcher.dispatchEvent != undefined){
  47.             dispatcher.dispatchEvent( evt);
  48.             return true;
  49.         }
  50.         var parentDispatcher = findParentDispatcher( dispatcher);
  51.         if( parentDispatcher != null){
  52.             evt.eventPhase = 3;
  53.             parentDispatcher.dispatchEvent( evt);
  54.             return true;
  55.         }
  56.         return false;
  57.     }
  58.    
  59.     /**
  60.      * Return the next parent with a dispatchEvent method or null, if not found
  61.      * @param dispatcher - a MovieClip with a EventDispatcher compatible
  62.      * dispatchEvent method
  63.      * @return  - a parent of dispatcher with a EventDispatcher compatible
  64.      * dispatchEvent method or null, if none could be found
  65.      */
  66.     private static function findParentDispatcher( dispatcher:Object):Object{
  67.         var o = dispatcher._parent;
  68.         while( o != null){
  69.             if( o.dispatchEvent != null) return o;
  70.             o = o._parent;
  71.         }
  72.         return null;
  73.     }
  74.    
  75.    
  76. }

Die Klasse hat drei statische Methoden, wovon eine nur für den internen Gebrauch gedacht und deshalb private ist.
Die Methode initializeBubbling schnappt sich den übergebenen Dispatcher, und überschreibt seine dispatchEvent Methode, so dass sie zusätzlich das Event zu den Eltern verschickt.
Die Methode dispatchEvent ermöglicht es ein Event zu verschicken, auch wenn man selbst gar kein Eventdispatcher ist.
Die Methode findParentDispatcher liefert den nächsten Dispatcher in der Elternhierarchie.

Geändert von bokel (14-01-2006 um 00:40 Uhr)
bokel ist offline   Mit Zitat antworten
Alt 13-01-2006, 11:12   #2 (permalink)
helpQLODhelp
 
Benutzerbild von bokel
 
Registriert seit: Feb 2002
Ort: Köln
Beiträge: 8.505
Schön und gut, aber was kann ich damit anfangen? Hier sind ein paar Beispiele. Das erste Beispiel benutzt die dispatchEvent Methode, um Events von einem normalen Button ausgehend zu dispatchen.

ActionScript:
  1. import util.EventDispatcherUtil;
  2.  
  3. var btn = createEmptyMovieClip("btn", 1);
  4. //... hier ein paar Zeichenbefehle einfügen um die Grafik zu erstellen ...
  5. btn.onRelease = function(){
  6.      EventDispatcherUtil.dispatchEvent( this, {type:"click", target:this});
  7. }
  8. //mach aus der aktuellen timeline einen Dispatcher
  9. EventDispatcher.initialize( this);
  10. //melde mich an für das click event
  11. addEventListener("click", function(evt){trace([evt.type,evt.target])});

Wie man sieht, ohne dass ich btn direkt anspreche, kann ich ein Event von ihm empfangen. Das geht auch über beliebig viele MovieClip-Ebenen hinweg.

Das nächste Beispiel zeigt Bubbling im zusammenhang mit dem Laden von MovieClips. Wie wir alle wissen, kann man einem MovieClip keine Events zuordnen, bevor er geladen ist, weil die Events beim Laden gelöscht werden. Aus diesem Grund kann man z.B. kein onLoad Event von einem geladenen MovieClip bekommen. Mit Bubbling ist das jedoch überhaupt kein Problem. Wir melden einfach den Parentknoten des geladenen Films als Listener an und warten, bis das Event bei ihm ankommt.

ActionScript:
  1. import mx.events.EventDispatcher;
  2. import util.EventDispatcherUtil;
  3.  
  4. //der onLoad-Handler im Prototype wird überschrieben
  5. //immer wenn der clip eine andere url als sein parent hat,
  6. //soll das load und loadComplete Event erzeugt werden
  7. MovieClip.prototype.onLoad = function(){
  8.     if( this._parent != null && this._url != this._parent._url){
  9.         trace("onLoad");
  10.         EventDispatcherUtil.dispatchEvent(this, {type:"load", target:this});
  11.         this.onEnterFrame = function(){
  12.             delete this.onEnterFrame;
  13.             EventDispatcherUtil.dispatchEvent(this, {type:"loadComplete", target:this});
  14.         }
  15.     }
  16. }
  17.  
  18. //die aktuelle Timeline wird zum Dispatcher
  19. EventDispatcher.initialize( this);
  20.  
  21. //und wartet auf die Events die da kommen
  22. addEventListener("load", function(evt){trace([evt.type,evt.target])});
  23. addEventListener("loadComplete", function(evt){trace([evt.type,evt.target])});
  24.  
  25. //jetzt laden wir den Clip in einen neu erzeugten MovieClip
  26. createEmptyMovieClip("mc2", 2).loadMovie("testOnLoadDispatch_swfToLoad.swf");
  27.  
  28. //Ausgabe
  29. //load,_level0.mc2
  30. //loadComplete,_level0.mc2
  31.  

Wie man sieht, bekommen wir wunderbare load Events ohne Polling oder MovieClipLoader.

Das nächste Beispiel zeigt, wie man eine V2 Komponente dazu bringt, zu bubblen.
ActionScript:
  1. import mx.core.UIComponent;
  2.  
  3. class MyView extends UIComponent{
  4.    
  5.     public static var SYMBOL_NAME:String = "MyView";
  6.     public static var SYMBOL_CLASS:Function = MyView;
  7.    
  8.     public static function createInstance( parent:MovieClip, name:String, depth:Number):MyView{
  9.         Object.registerClass(SYMBOL_NAME, SYMBOL_CLASS);
  10.         var result = parent.attachMovie( SYMBOL_NAME, name, depth);
  11.         if( result == null) trace("MyView.createInstance: Error creating instance with args: " + arguments);
  12.         return result;
  13.     }
  14.    
  15.     private var b1:mx.controls.Button;
  16.     private var mc2:MovieClip;
  17.  
  18.    
  19.     public function onLoad(){
  20.         createChildren();
  21.         initEvents();
  22.     }
  23.  
  24.     private function createChildren(){
  25.         b1 = mx.controls.Button(createClassObject(mx.controls.Button, "b1", 1, {_y:30, label:"hi"}));
  26.         mc2 = createEmptyMovieClip("mc2", 2);
  27.         mc2._y = 60;
  28.         mc2.createClassObject(mx.controls.Button, "b2", 2, {label:"bokel"});
  29.     }
  30.    
  31.     private function initEvents(){
  32.         EventDispatcherUtil.initializeBubbling(this);
  33.         EventDispatcherUtil.initializeBubbling(b1);
  34.         EventDispatcherUtil.initializeBubbling(mc2.b2);
  35.        
  36.         addEventListener("click", this);
  37.        
  38.         //debugging only, normally you
  39.         //don't need to reference instances directly,
  40.         //because events do bubble up
  41.         b1.addEventListener("click", this);
  42.         mc2.b2.addEventListener("click", this);
  43.     }
  44.    
  45.     private function click( evt:Object):Void{
  46.         trace("MyView::click " + [evt.type, evt.eventPhase, evt.target, evt.currentTarget ]);
  47.     }
  48.    
  49. }
  50.  
  51. class MainClass{
  52.    
  53.     public static function main( mc:MovieClip):Void{
  54.         MyView.createInstance(mc, "mv1", 1);
  55.     }
  56. }

In der Bibliothek muss eine V2 Buttonkomponente liegen, damit das funktioniert. Nachdem wir die Buttons in createChildren erzeugt haben, werden sie in initEvent mit der Methode initializeBubbling so initialisiert, dass sie ihre Events bubblen. Deshalb kommen die Events jetzt nicht nur an, wenn wir uns direkt beim button anmelden, sondern auch, wenn wir uns quasi bei uns selbst anmelden. Das ist doch echt schick, oder?

Ok, das wars für heute erstmal. Ich bin mir sicher, ihr werdet noch eine Menge interessante Anwendungen dafür finden. Nachdem ich selbst erst etwas skeptisch war, bin ich jetzt überzeugter Bubbler und möchte nicht mehr ohne.

Weitere Details und vielleicht Anregungen für Erweiterungen könnt ihr auch der Flexdoku entnehmen. Drei Sachen, die ich zum Beispiel aus der Doku übernommen habe, sind Event.bubbles, Event.currentTarget, und Event.eventPhase. Mit bubbles=false kann man das Bubblen stoppen. Event.currentTarget zeigt immer auf den Dispatcher, der das Event gerade hat. Und eventPhase zeigt an, in welcher der drei Lebensphasen eines Events wir uns befinden. In unserem Fall gibt es allerdings nur die Phasen target und bubbling. Die dritte Phase capture ist quasi das Gegenstück zu bubbling, das Event fliesst von den Elten zu den Kindern. Das habe ich aber nicht implementiert. In Flex lässt es sich auch nur indirekt ansprechen. Eigene Events sind nach meinen Tests nie in der Capturephase.

Edit: da haben sich so merkwürdige zeichen eingeschlichen, & #91;steht für eckige Klammer auf. Anscheinend kann der Formatierer damit nicht richtig umgehen.

Viel Spaß,
Ralf.

Geändert von bokel (14-01-2006 um 00:43 Uhr)
bokel ist offline   Mit Zitat antworten
Alt 18-01-2006, 22:40   #3 (permalink)
agedoubleju
Gast
 
Beiträge: n/a
Du kannst dir meiner ehrfürchtigen Bewunderung sicher sein Tolle Sache das...
  Mit Zitat antworten
Alt 18-01-2006, 23:04   #4 (permalink)
helpQLODhelp
 
Benutzerbild von bokel
 
Registriert seit: Feb 2002
Ort: Köln
Beiträge: 8.505
LOL, danke schön.
Ich hoffe, du kannst was damit anfangen.
mfg. r
bokel ist offline   Mit Zitat antworten
Alt 20-01-2006, 19:56   #5 (permalink)
[+]
 
Benutzerbild von André Michelle
 
Registriert seit: Dec 2002
Ort: cologne
Beiträge: 2.271
Ich finde das auch super !

Geht das auch mit Objekten ? Das wäre auch nicht verkehrt, wenn ich eine (physikalische) Simulation habe, die mir Events von tief verschachtelten Membern nach bubblet.
__________________
aM

blog | laboratory | tonfall | processing

Audiotool.com
André Michelle ist offline   Mit Zitat antworten
Alt 20-01-2006, 20:40   #6 (permalink)
helpQLODhelp
 
Benutzerbild von bokel
 
Registriert seit: Feb 2002
Ort: Köln
Beiträge: 8.505
Das Bubblen hangelt sich am _parent nach oben. Wenn deine Objekte auch eine _parent Eigenschaft haben, dann klappt es.

mfg. r
bokel ist offline   Mit Zitat antworten
Alt 22-01-2006, 11:36   #7 (permalink)
nroy
Gast
 
Beiträge: n/a
das ist ja wirklich todschick

Im Grunde genommen habe ich also damit die Möglichkeit ein Objekt für einen Event anzumelden, ohne den Dispatcher zu kennen - solange der "unterhalb" von meinem Objekt liegt. Weil ich mein Objekt ja im Prinzip nur bei irgendeinem Glied der Kette für diesen Event anmelden muss.

Das gleiche könnte man doch auch erreichen, wenn man einen globalen Dispatcher anlegt und von überall aus dort dispatchEvent() aufruft. Dann hat man auch nicht das _parent-Problem und erreicht auch Listener, die nicht in der Kette liegen.

Schönheitsfehler wäre dann natürlich, dass alle Objekte diesen globalen Dispatcher kennen müssten...
  Mit Zitat antworten
Alt 22-01-2006, 11:52   #8 (permalink)
helpQLODhelp
 
Benutzerbild von bokel
 
Registriert seit: Feb 2002
Ort: Köln
Beiträge: 8.505
Ja, das wäre ein Schönheitsfehler, wobei ich damit noch leben könnte, wenn es in der initialize Methode versteckt wäre.
Das Problem ist aber, dass es dann z.B. nur einen click-EventDispatcher gibt, d.h. alle Listener müssten alle click Events handeln.
Was dann auch wegfällt, ist die Möglichkeit Events auf ihrem Weg nach oben mit weiteren Informationen anzureichern, oder zu filtern oder durch andere Events zu ersetzen, oder ... man würde also eine Menge Möglichkeiten verschenken.

mfg. r
bokel ist offline   Mit Zitat antworten
Alt 22-01-2006, 14:15   #9 (permalink)
nroy
Gast
 
Beiträge: n/a
stimmt, für click-Events ist das Bubbling optimal. Oder wenn ein Textfeld im Sub-MC Tastaturfocus bekommt und man das "ganz oben" wissen will! Hätte ich schon mal gut brauchen können

Ich hatte auch einen speziellen anderen Anwendungsfall im Kopf: Einen Loader, der für alle MovieClip.loadMovie() oder XML.load() Aktionen einen hübschen Ladebalken einblendet, egal wo gerade etwas reingeladen wird.
Wenn irgendwo im Projekt ein Ladevorgang gestartet wird, wird über den globalen Dispatcher ein Event losgeschickt, für den der Loader sich registriert hat. Dann weiß der Loader über die e.target-property wo gerade geladen wird und kann den Fotschritt anzeigen.
Beim Bubbling würde der Loader nicht alle Events bekommen (nur von eigenen Sub-MCs).

Egal, das ist wohl eher ein anderes Problem
  Mit Zitat antworten
Alt 28-01-2007, 14:21   #10 (permalink)
Crème brûlée
 
Registriert seit: Jan 2006
Ort: Düsseldorf
Beiträge: 719
Hallo zusammen,

wie müsste ich das denn anstellen, ein Eventbubbling bei verschachtelten Movieclips hinzukriegen, die auf onKeyDown-Ereignisse hören? addEventListener funktioniert ja nicht bei MovieClips - oder bin ich da einfach zu blöd?

Viele Grüße,
Benjamin
laxersaz ist offline   Mit Zitat antworten
Alt 28-01-2007, 16:08   #11 (permalink)
Nagelneuer User
 
Benutzerbild von hazy fantazy
 
Registriert seit: Dec 2005
Beiträge: 924
Deine MovieClips müssten eine Klasse haben, die das Eventhandling und Bubbling ermöglicht.
__________________
The fact that you've got "Replica" written on the side of your gun and the fact that I've got "Desert Eagle written on the side of mine ... :D
hazy fantazy ist offline   Mit Zitat antworten
Alt 28-01-2007, 18:22   #12 (permalink)
Crème brûlée
 
Registriert seit: Jan 2006
Ort: Düsseldorf
Beiträge: 719
Das ist schon klar. Mir ist es nur nicht gelungen, den Bubbling-Code so umzubiegen, dass das funktioniert. Hätte da jemand wohl ein kleines Beispiel?
laxersaz ist offline   Mit Zitat antworten
Alt 29-01-2007, 19:08   #13 (permalink)
Nagelneuer User
 
Benutzerbild von hazy fantazy
 
Registriert seit: Dec 2005
Beiträge: 924
Was genau willst du denn erreichen?
__________________
The fact that you've got "Replica" written on the side of your gun and the fact that I've got "Desert Eagle written on the side of mine ... :D
hazy fantazy ist offline   Mit Zitat antworten
Alt 30-01-2007, 07:32   #14 (permalink)
Crème brûlée
 
Registriert seit: Jan 2006
Ort: Düsseldorf
Beiträge: 719
Konkret gehts darum, eine Oberfläche in Flash zu bauen, die ausschließlich mit der Tastatur gesteuert wird. Dabei werden verschiedene Module in die Applikation geladen (die liegen alle untereinander), die jeweils wieder aus Elementen bestehen (Buttons, Textfelder etc.). Nun geht es darum, das Focusmanagement zu planen, d.h. z.B.

- Nutzer befindet sich in Modul 1 und kann mit den Tasten links und rechts horizontal im Modul von Button zu Button springen.

- nun drückt er den Pfeil nach unten

- dieses Event soll vom Modul nicht gehandelt werden, sondern an die übergeordnete Ebene weitergebubbelt werden, damit die dann den Modulfokus wechseln kann

- nun hat das Modul 2 den Focus und darin das Element 1

Das ist nur ein einfaches Beispiel. Es kann natürlich vorkommen, dass Elemente auch nur unter bestimmten Umständen Ereignisse verarbeiten sollen.

Momentan löse ich das mit Listenern, die bei Bedarf ein- und ausgeschaltet werden - das ist aber nicht sehr elegant und wenig flexibel. Daher wollte ich das gerne mit Bubbling lösen.
laxersaz ist offline   Mit Zitat antworten
Alt 30-01-2007, 10:59   #15 (permalink)
Nagelneuer User
 
Benutzerbild von hazy fantazy
 
Registriert seit: Dec 2005
Beiträge: 924
Also möchtest du ein Event aus einem geladenen SWF an den übergeordneten SWF schicken?
__________________
The fact that you've got "Replica" written on the side of your gun and the fact that I've got "Desert Eagle written on the side of mine ... :D
hazy fantazy ist offline   Mit Zitat antworten
Antwort

Lesezeichen

Themen-Optionen
Ansicht

Forumregeln
Es ist Ihnen nicht erlaubt, neue Themen zu verfassen.
Es ist Ihnen nicht erlaubt, auf Beiträge zu antworten.
Es ist Ihnen nicht erlaubt, Anhänge hochzuladen.
Es ist Ihnen nicht erlaubt, Ihre Beiträge zu bearbeiten.

BB-Code ist an.
Smileys sind an.
[IMG] Code ist an.
HTML-Code ist aus.
Trackbacks sind an
Pingbacks sind an
Refbacks sind an



Alle Zeitangaben in WEZ +1. Es ist jetzt 13:00 Uhr.

Domains, Webhosting & Vserver von Host Europe
Unterstützt das Flashforum!
Adobe User Group


Copyright ©1999 – 2012 Marc Thiele