While most components and objects in Macromedia Flash MX 2004 have built-in support for UI events (mouse, keyboard, etc.) and Load or Data events (load, complete, progress, etc.), these events alone may not always meet developers' needs. There are times when you may want to detect a complex change in application state, build custom components, or just force methods to wait for the completion of a lengthy process. Often Flash developers resort to the use of global variables or convoluted loops to repeatedly check for state change with limited success.
It is simpler to specify your events using the Flash event listener object model. This article discusses the basics of using the EventDispatcher class and provides the steps for building custom classes that broadcast custom events.
To complete this tutorial you will need to install the following software and files:
Before reading this article, you should be familiar with event handling, including the various ways to write an event listener, and with building classes in ActionScript 2.0. For more information see the Handling Events overview in the Using ActionScript section of the Macromedia Flash MX 2004 product documentation.
To build a successful event, you really need two fundamental things to happen. First, you need to broadcast the event, and second, you need to have objects to listen to those events when you broadcast them. If you have used event listeners with version 2 components or with ActionScript 2.0 classes, then you should be comfortable with adding event listeners and creating event objects. Listening to your custom events is the same as listening to events broadcast from the core Flash objects/classes and components. In this article, you will focus on setting up your custom class and its methods so that objects can subscribe to and receive events from it.
For starters, here's an example custom class with some vary basic functionality. The Draw Me a Square class will create a child movie clip within another movie clip and draw a red square in it.
In an external AS file named drawMeASquare.as:
// The Draw Me a Square Class will draw a square in any movie clip.
class drawMeASquare{
var square_mc:MovieClip;
function drawMeASquare(){
}
function drawItNow(cliptoDrawin:MovieClip){
// I draw a square in any movie clip
cliptoDrawin.createEmptyMovieClip("square_mc",cliptoDrawin.getNextHighestDepth());
this.square_mc = cliptoDrawin.square_mc;
square_mc.beginFill(0xFF0000);
square_mc.moveTo(10, 10);
square_mc.lineTo(100, 10);
square_mc.lineTo(100, 100);
square_mc.lineTo(10, 100);
square_mc.lineTo(10, 10);
square_mc.endFill();
}
}
// On the Timeline in Frame 1
// Create a new custom class object (must be new to run the constructor)
var mySquareObject:drawMeASquare = new drawMeASquare();
// trigger the method that draws the square. pass in any valid movie clip object as a target
mySquareObject.drawItNow(_root);
Drawing shapes in a movie clip is an example of core functionality that is available in the MovieClip class, even through this functionality has no built-in events. While this example class works just fine because it is simple, what if it were actually drawing a more complex shape or group of shapes that took more time to render in Flash Player? You may not want to execute other methods that animate, draw more shapes, or even add properties to your new square until you are sure the shape is drawn and visible on the Stage. This presents you with a challenge.
One solution to the problem would be to call these additional dependent methods from the drawItNow method when you are ready to use them. However, if you have a very dynamic application, which may be dependant on multiple classes that you may not have much control over, or you are using many instances of this class, it can get rather difficult to manage all of the additional customization methods you need to call. You could create a lot of processor overhead and write a lot of code to manage the state of each instance, which can result in lots of runtime errors.
Note: Performance differences on client machines can cause methods to execute at unexpected times. Using events can insure a proper execution sequence, even if performance delays (or speeds up) method execution.
You are not going to call all your methods that depend on the red square being present on the Stage from the drawMeASquare class. You're not going to try to set some global property that you can check periodically with your methods. Instead you are going to make the drawMeASquare class a broadcaster, which other objects can subscribe to. The broadcaster will trigger these dependent methods when it is appropriate.
To broadcast a custom event from the drawItNow method in your example class, you have to set up instances of the class as dispatchers. To make the class a dispatcher, follow these four steps:
import mx.events.EventDispatcher statement before your class definition.dispatchEvent, addEventListner, and removeEventListener methods used by the EventDispatcher class as empty methods in your class. (The EventDispatcher class sets up these methods at runtime so you can add and remove listeners from each instance of the class.) initialize method of the EventDispatcher class and add the instance of your class in the class constructor. dispatchEvent method with the event object as a parameter.Here are the updates to your class file:
// import the Event dispatcher class (Step1)
import mx.events.EventDispatcher
class drawMeASquare{
var square_mc:MovieClip;
//declare the dispatchEvent, addEventListener and removeEventListener methods that EventDispatcher uses (Step2)
function dispatchEvent() {};
function addEventListener() {};
function removeEventListener() {};
function drawMeASquare(){
// send instance of self to the Event Dispatcher (Step3)
mx.events.EventDispatcher.initialize(this);
}
function drawItNow(cliptoDrawin:MovieClip){ // I draw a square in any movie clip
cliptoDrawin.createEmptyMovieClip("square_mc", cliptoDrawin.getNextHighestDepth());
this.square_mc = cliptoDrawin.square_mc;
square_mc.beginFill(0xFF0000);
square_mc.moveTo(10, 10);
square_mc.lineTo(100, 10);
square_mc.lineTo(100, 100);
square_mc.lineTo(10, 100);
square_mc.lineTo(10, 10);
square_mc.endFill();
//define the event object that is passed to any listeners when the event is broadcast (Step4)
// You must specify a target property and then name of the event or the event “type” property in the event object
var eventObject:Object = {target:this, type:'drawn'};
// any optional properties
eventObject.drawObject = 'square';
eventObject.whereDrawn = cliptoDrawin.square_mc;
//dispatch the event (Step5)
dispatchEvent(eventObject);
}
}
That's it; the drawMeASquare class will now return the eventObject object to any event listeners that are subscribed to its drawn event.
So what exactly is the event object?
The event object is basically an object with two key properties:
target property, which must always be a reference to the class itself or this type property, which is basically what you call the event you are making available for subscriptionIn the drawMeASquare class example, you are providing a drawn event type. This could just as easily be click, load, complete, or press. Sound familiar? It should; this is basically how all event listeners in Flash work. They depend on the type name to distinguish one event from another.
Note: If you are building a class that has a number of different states/methods, defining different event types can be a convenient way to help you manage things. Each method can broadcast an event if necessary, and, because you can give each type a custom name to distinguish it, you can have as many objects listening to as many different events as you need.
Another feature of the event object is that you can add other properties to it. In the drawMeASquare class example you added two properties: the drawObject property, which you assigned to the string square, and the whereDrawn property, which you assigned to the cliptoDrawin.square_mc movie clip. This is very useful way of getting specific properties of a class instance, which may only exist during or after the execution of the method that broadcasts or dispatches the event from that instance.
So for example, you could capture the drawObject property's value only while the drawItNow method is running. There is really no way for you to do that from your code on the Timeline—not without capturing the value in an event listener. You also may not be able to trace the target of cliptoDrawin.square_mc (which, in this case, is _level0.square_mc) from the Timeline. If it takes too long for the drawItNow method to run (which can be the case on slower machines) the trace could execute before _level0.square_mc is actually created and available on the Stage.
This is a common problem with components that may require initialization time after they appear on the Stage. Without events, the ActionScript will run more or less asynchronously as lines of code are processed by Flash Player. (Just because a loop on Line 10 has not finished executing doesn't mean the code on Lines 11-20 is going to wait for it before executing.)
Now it's time to test your new and improved drawMeASquare class.
Create an event listener to listen for the trigger of the drawn event and trace out your properties. You just need to add a few lines to your code on the Timeline to test this out.
// new custom class object (must be new to run the constructor)
var mySquareObject:drawMeASquare = new drawMeASquare();
// new object we can use for a listener
var myListnerObj:Object = new Object;
// method definition for the drawn event
myListnerObj.drawn = function(evtObj) {
// Now trace out a few things. At this point you know the square is drawn and anything you need from the square is available!
trace("The Square is drawn: ");
trace("This is the drawObject prop: "+ evtObj.drawObject);
trace("This is the whereDrawn prop: "+ evtObj.whereDrawn);
trace("This is the event type: "+ evtObj.type);
trace("This is the event target: "+ evtObj.target);
}
// subscribe myListnerObj to mySquareObject
mySquareObject.addEventListener("drawn",myListnerObj);
// draw the square and trigger the event:
mySquareObject.drawItNow(_root);
You can use these four steps to create custom events in your custom classes. When used properly, this reduces the amount of code you need to write and helps you reduce performance lags and timing bugs that can be hard to find and correct. It will also help to ensure that when you build classes or components that will be used by other developers, you can be far more certain your code will function predictably without the need for modification—taking into account differences in runtime performance.