Flash AS3 Best way to communicate independent classes? - actionscript-3

what would be the simpliest way to communicate between independent classes?
I have been searching for two days and couldn't find nothing,
isn't there a way that one class has a dispatchEvent and the other an addEventListener, with a custom Event?? I could only find solutions within the same class o with a parentchild relationship, but what I'm looking for is like a "brother" relationship so to speak
thanks

In general, you want the class that dispatches the event to either extend EventDispatcher or implement IEventDispatcher. (All DisplayObjects do, so if your classes are DisplayObjects, you don't need to do anything extra.)
In the dispatching class:
class DispatchingClass extends Event Dispatcher {
function doSomething() {
// do stuff
dispatchEvent(new Event("FOO"));
}
}
In the listening class:
class ListeningClass {
function startListening(dispatcher:DispatchingClass) {
dispatcher.addEventListener("FOO", handleFoo);
}
function handleFoo(evt:Event) {
// do stuff
}
}
EventDispatchers work fine with custom events.
If for some reason your listening class doesn't have and can't get an instance of your dispatching class, you can make a global event broadcaster. Basically, make a universally-accessible class that extends EventDispatcher (or implements IEventDispatcher) and listens for and dispatches events to anything that tells it to.
Here is a bare-bones implementation of an event broadcaster:
import flash.events.EventDispatcher;
public class EventBroadcaster extends EventDispatcher {
private static var _instance:EventBroadcaster = new EventBroadcaster();
public function EventBroadcaster() {
if (_instance != null) {
trace ("Error: an instance of EventBroadcaster() already exists.");
}
}
public static function getInstance():EventBroadcaster {
return EventBroadcaster._instance;
}
}
You use it pretty much the same way:
class DispatchingClass {
function doSomething() {
// do something
EventBroadcaster.getInstance().dispatchEvent(new Event("FOO"));
}
}
class ListeningClass {
function startListening() {
EventBroadcaster.getInstance().addEventListener("FOO", handleFoo);
}
function handleFoo(evt:Event) {
// do stuff
}
}
dispatchEvent() and addEventListener() are just the functions from the built-in EventDispatcher.
There's some discussion at Event Broadcaster - Simple events solution... on ways to make an event broadcaster and how to add useful features. The article Centralized Event Management in Actionscript 2.0 has a good introduction to the concept.

So if you have ChildClass1, ChildClass2, and ParentClass, where ChildClass1 and ChildClass2 are both the children of ParenClass.
ChildClass1 will dispatch an event.
ParentClass will listen for this event, and then it's handler will update ChildClass2.

If you don't have a ParentClass, you could also use a ChildManagerClass that registers Childs and notifies them accordingly.

Related

Troubles with Custom Events

I've been having lots of trouble with custom events in as3 recently, I've googled a bunch of stuff that doesn't help and used this tutorial to try to figure them out but I'm still confused.
So I set up a new flash fla file to test them and couldn't figure it out, here is what I have:
document as file:
package
{
import flash.events.MouseEvent;
import flash.display.MovieClip;
public class Custom extends MovieClip
{
var intializer:Initializer;
var added:Added;
public function Custom()
{
intializer=new Initializer();
addChild(intializer);
intializer.addEventListener(MouseEvent.CLICK, OnClicker);
addEventListener(CustomEvent.EVENT_CUSTOM, OnCatch);
}
private function OnClicker(event:MouseEvent):void
{
added=new Added();
added.x=300; added.y=300;
addChild(added);
}
private function OnCatch(event:CustomEvent):void
{
trace("hi");
removeChild(added);
}
}
}
event as file:
package
{
import flash.events.Event;
public class CustomEvent extends Event
{
public static const EVENT_CUSTOM="event1";
public function CustomEvent(type)
{
super(type, false, false);
}
}
}
and the movieclips as file:
package
{
import flash.events.MouseEvent;
import flash.display.MovieClip;
public class Added extends MovieClip
{
var addedButton:AddedButton;
public function Added()
{
addedButton=new AddedButton();
addedButton.x=30; addedButton.y=30;
addChild(addedButton);
addedButton.addEventListener(MouseEvent.CLICK, OnClickie);
}
private function OnClickie(event:MouseEvent):void
{
dispatchEvent(new CustomEvent(CustomEvent.EVENT_CUSTOM));
}
}
}
Buttons use an empty class, This gives me this result: (top left corner for first button.)
http://www.fastswf.com/_EfGSoQ
Sorry for so much code, but custom events seem to require a lot of code.
The problem seems to be that you are listening for your custom event in the wrong place. You can address this several ways using event bubbling, the event's capture phase, or by listening for the event on the object that dispatches the event (the event target).
The Flash Event Model follows the W3C event model (used in the DOM/Javascript). When an event is dispatched, it goes through three phases: capture phase, target phase, and bubbling phase. It's described in the above link (in the "Event propagation and phases" section).
For performance reasons (my assumption), the capture and bubbling phases are not enabled by default.
Note you only need to do one of the 3 things below. I suggest using the event target, it's the easiest to understand. The others have their places and can be very useful, but you can do most everything w/the target phase.
How to use the target phase
In your document class remove this line from the constructor:
addEventListener(CustomEvent.EVENT_CUSTOM, OnCatch);
Remove it because we're going to listen for the event coming from the Added object directly (it's the event target). To do that change your code that adds the object in the document class:
private function OnClicker(event:MouseEvent):void
{
added=new Added();
added.x=300; added.y=300;
addChild(added);
added.addEventListener(CustomEvent.EVENT_CUSTOM, OnCatch);
}
How to use the capture phase
In your document class, add an additional parameter when adding the event listener to enable the capture phase:
addEventListener(CustomEvent.EVENT_CUSTOM, OnCatch, true);
This allows any parent of the event target to handle the event, before the target handles it.
How to use the bubbling phase:
To use the bubbling phase, your custom event object needs to "bubble". So you will need to modify the constructor in your custom event class:
public function CustomEvent(type)
{
super(type, true, false);
}
Here I've changed the second parameter in the call to super(), allowing the event to "bubble".
Since the event now bubbles back up the display list, any parent of the event target will also be able to listen for the event after the target has handled it.

How to access the variable "abc" in the following code structure

1) First of all I don't wanna use CustomEvent class. Some solution I am looking without using CustomEvent.
2) One of the solution can be having abc variable in ClassA. And then dispatching directly via ClassA ( rathar than saying classB.dispatchEvent() ). But still looking if there is some better solution than this.
//Frame1 code :
import flash.events.Event;
var classA:ClassA = new ClassA() ;
classA.addEventListener("hello", hello);
classA.init();
function hello(e:Event)
{
trace(e.currentTarget.abc); //<<<< NEVER EXECUTED
}
//classA
package
{
import flash.display.MovieClip;
import flash.events.Event;
public class ClassA extends MovieClip
{
var classB:ClassB ;
public function ClassA()
{
classB = new ClassB();
}
public function init()
{
classB.dispatchEvent( new Event("hello"));
}
}
}
//classB
package
{
import flash.display.MovieClip;
public class ClassB extends MovieClip
{
public var abc:Number =123;
public function ClassB()
{
}
}
}
You are missing a couple key concepts before you can get your example to work. First you are dispatching the event on an instance of ClassB, however you are listening on an instance of ClassA. So, they have to be related in some way, in order for event to be properly orchestrated when it gets dispatched. One way to do that is to use event bubbling. One caveat to that is that native event bubbling only really works for DisplayObjects, but both of your classes inherit from MovieClip so thats not a big deal.
So the first thing, you have to understand how bubbling events work. A simplified explanation is that events start at the top of the display hierarchy and capture down the display tree towards the element, they are finally dispatched on the target, then they turn around and bubble back out in the opposite direction.
This means that your instance of ClassB has to be a child of ClassA. So the first thing you'll have to change is in your ClassA constructor:
public function ClassA()
{
classB = new ClassB();
addChild(classB);
}
Next, when you dispatch the event, you'll need to explictly say that its a bubbling event, otherwise it'll be triggered on the target, and neither capture nor bubble through the display stack.
public function init()
{
classB.dispatchEvent( new Event("hello", true));
}
The second argument of true sets the event to a bubbling event.
Finally you'll need to change your handler. Right now, it's using e.currentTarget, which isn't going to be what you expect in this case (usually it is, thought).
You have to understand the difference between e.target and e.currentTarget. e.target is the actual target of the event, independent of how its bubbling or capturing. e.currentTarget on the other hand is the element which is presently handling the event. So in your case e.currentTarget is an instance of ClassA (the instance that the event handler is actually attached to) and e.target is an instance of ClassB (the instance that the event was dispatched on). During the event lifecycle, e.currentTarget will change as the event moves around, but e.target should always be the same.
In this case, you want to reference the actual target of the event, not the element that is currently processing the event. So you need to change your handler to:
function hello(e:Event)
{
trace(e.target.abc);
}
And then it should work. You can find a working example here that encapsulates the changes I've described.
If these classes weren't DisplayObjects then you would have to take a different approach -- either by using a signal pattern or to manually listen for an retrigger the event inside ClassA.
First of all you are adding an event listener to classA but your classA init method is asking classB to dispatch an event and this is the reason why your code does not get executed. If you want to catch the hello event you should be doing something like
public function init()
{
this.dispatchEvent( new Event("hello"));
}
Or you should be registering the listener on classB (which is not in scope so no code suggestion).
In ActionScript the best approach to transfer information is to use custom events so my suggestion is to re evaluate your decision on custom events.

AS3 - Defining functions inside of other functions

When adding event listeners and defining their corresponding functions I find myself defining the function in the code of a constructor a lot. Something like this:
package
{
public class stuff extends Sprite
{
public function stuff()
{
minimizeBtn.addEventListener(MouseEvent.CLICK, minimizeOnClick);
function minimizeOnClick(e:MouseEvent):void
{
//do minimization stuff here
}
}
}
}
However, there is clearly another option to define it like any other method of the class. Something like this:
package
{
public class stuff extends Sprite
{
public function stuff()
{
minimizeBtn.addEventListener(MouseEvent.CLICK, minimizeOnClick);
}
internal function minimizeOnClick(e:MouseEvent):void
{
//do minimization stuff here
}
}
}
The second option may not really make sense because the function isn't really a method of the class. But my concern is that the first method will use up extra memory for each instance of the class. Which is the most efficient and correct way to do this and also does the first method take up extra memory or CPU time for each instance of the class?
Thanks!
The latter example is the correct way, and it's true that you should try encapsulate your addEventListener() and listening function within the relevant class. In your case, you may want to consider making a class for your minimizeBtn:
public class MinimizeButton extends SimpleButton
{
public function MinimizeButton()
{
addEventListener(MouseEvent.CLICK, _onClick);
}
private function _onClick(e:MouseEvent):void
{
// do minimization stuff here
}
}
MinimizeButton's _onClick() should then target the relevant instance of your class stuff and run whatever stuff needs to do from there.
This example's process is more like:
MinimizeButton: "I've been clicked, I should inform stuff so it can do something relevant."
Rather than:
stuff: "I'm going to sit and wait for MinimizeButton to get clicked, then I'll do what's required."

Flex event handler not working

I have created a custom event in flex 3.5. But the handler is not invoked. How to solve this or what is the way to debug this problem?
The Event class:
package com.saneef.worldlanguages.events
{
import flash.events.Event;
public class LanguageEvent extends Event
{
public static const LANGUAGE_SELECTED:String = "LanguageSelected";
public function LanguageEvent(type:String,languageid:String)
{
super(type);
this.langid = languageid;
trace("LanguageEvent: " + this.langid);
}
public var langid:String;
override public function clone():Event {
return new LanguageEvent(type, langid);
}
}
}
Dispatching:
private function functionOne():void
{
try{
dispatchEvent(new LanguageEvent(LanguageEvent.LANGUAGE_SELECTED,"STR"));
}
catch(e:Error)
{
trace(e.message);
}
}
In the Main application class, EventListener:
protected function application1_initializeHandler(event:FlexEvent):void
{
this.addEventListener(LanguageEvent.LANGUAGE_SELECTED,
application1_LanguageSelectionHandler);
}
The event handler function:
public function application1_LanguageSelectionHandler(event:LanguageEvent):void
{
trace("application1_LanguageSelectionHandler: " + event.langid);
populate_countrya3id_languages(event.langid);
}
Your code looks fine. Since I can't see the full source, here are my two thoughts on what may be going on:
Are you sure your addEventListener call is done before you dispatch the event? Add some trace to make sure the application1_initializeHandler prints before functionOne does.
Is your functionOne call in another different component than your main application? If so, you'll need to set your custom event's bubbles attribute to true in your event's super call.
public function LanguageEvent(type:String,languageid:String,bubbles:Boolean=True)
{
super(type, bubbles);
this.langid = languageid;
trace("LanguageEvent: " + this.langid);
}
See the flash.events.Event docs for the constructor call. Also, here's a quote about the bubbles argument explained here:
The bubbles property
An event is said to bubble if its
event object participates in the
bubbling phase of the event flow,
which means that the event object is
passed from the target node back
through its ancestors until it reaches
the Stage. The Event.bubbles property
stores a Boolean value that indicates
whether the event object participates
in the bubbling phase. Because all
events that bubble also participate in
the capture and target phases, any
event that bubbles participates in all
three of the event flow phases. If the
value is true, the event object
participates in all three phases. If
the value is false, the event object
does not participate in the bubbling
phase.
Based on your source code, it looks like you've seen the "Dispatching Custom Events" in the flex docs, but I'll link to it anyways for future/easy reference: http://livedocs.adobe.com/flex/3/html/help.html?content=createevents_3.html.
Also, check out http://www.adnandoric.com/2008/12/29/understanding-the-flex-event-propagation/ for a high-level overview of the event propagation system to try to get a better understanding of what's going on while developing.
Edit:
Based on your comments I'm guessing your functionOne call is in a separate class and your "main" application has an instance of this class. If that's so you'll want to attach your event listener on that instance and delegate it to your main's application1_LanguageSelectionHandler function... Like so:
protected function application1_initializeHandler(event:FlexEvent):void
{
this.theInstanceThatHoldsYourFunctionOne.addEventListener(LanguageEvent.LANGUAGE_SELECTED,
application1_LanguageSelectionHandler);
}

Overriding flash.display.Sprite's eventDispatcher

Hai,
I want to override the dispatchEvent method while inheriting from flash.display.Sprite. Whenever an event gets dispatched now like ADDED_TO_STAGE and CLICK it will get through dispatchEvent in my theory. However there is no single trace he'll get through dispatchEvent.. so the events get dispatched internally from some where else?
public class TestSprite extends Sprite
{
public function TestSprite()
{
this.addEventListener(Event.ADDED_TO_STAGE, this.handleAddedToStage);
}
private function handleAddedToStage(event:Event):void
{
}
override public function dispatchEvent(event:Event):Boolean
{
trace(event.type);
return super.dispatchEvent(event);
}
}
this._sprite = new TestSprite();
this._sprite.graphics.beginFill(0x000000);
this._sprite.graphics.drawRect(20, 20, 200, 200);
this._sprite.graphics.endFill();
this.addChild( this._sprite );
There is no trace..
The Sprite implements IEventDispatcher so that the coder - you - can dispatch custom events from display-list classes. As you suspected however, native Flash Player events are not dispatched through the dispatchEvent method itself, they are created and passed to event listeners internally. I imagine a primary reason for this is performance.
sooner or later in the function you need to pass the event to the real sprite eventDispatcher or it won't work (hint: super.dispatchEvent(event) )
by the way this will work only on objects that subclasses your own class, the others will continue to subclass Sprite, hence use the Sprite method.