Flash MX 2004 Framework Component Tutorial

Want to learn how to create an ActionScript 2.0 based component that utilizes the Flash MX 2004 Professional framework? Then follow this tutorial in creating a simple Sign In dialogue. Let’s dissect this mutha…

Class

First thing’s first. You have to inherit, at least, from UIObject. Now, he’s cool and all with nifty extensions, built in methods, call backs, invalidation, the whole 9 yards (…or 8.2 meters). UIComponent is even better. So, let’s plug into the framework, shall we? Make an AS file, and save the mug in a folder somewhere. He should start like this:

class SignInView extends mx.core.UIComponent

Variables

Next, we have to define/dim our main framework variables. The first is symbolName which is used in some of the factory construction methods (functions that create components within components; createClassObject for example). Next is symbolOnwer who is used mainly in createClassObject to determine who the base/super class is. Typically, this is the same as your class path. Finally, you have className, which is used createClassObject as well as when calculating style values. It’s also nice for debugging. Instead of using typeof or instanceof, you can just trace out className to figure out what the heck your component is.

// standard mx 2004 framework variables
static var symbolName:String = "SignInView";
static var symbolOwner:Object = SignInView;
public var className:String = "SignInView";

Next up is the variables we’ll use to hold our data, as miniscule as that is for something as simple as a View; view being the GUI that the user interacts with to tell the Controller, “Yo, user changed some stuff.”

Typically, Flash MX 2004 components have getter/setters for their main public variables since this allows them easy integration with the IDE. If your not going to dynamically attach your component, or you think a potential user would, it’s best to provide getter/setters so they can be inspectable by the IDE, and more easily settable.

We know our component will have a username, password, and some form of state. What happens behind the scenes, however, is that your public getter/setters are merely publicly accessible functions that set internal private variable values. This is number 1, controlled access, and number 2, a way to have addProperty co-chill on the prototype. In the past, the only way to get addProperty (the getter/setter functions used to set your class’ property) to be set on the prototype, and thus not be added to each and every instance (and therefore wasting memory/RAM) was to have a go between variable. In our case, it’s just a private variable.

Finally, I like to use a base class with Grant Skinner’s GDispatcher, but to start simple, we’ll assume for this class only, you’d like to utilize the EventDispatcher. To do so, you typically define a static private, one time use boolean to determine if you’ve initialized the EventDispatcher. So, we’ll add him too.

So, let’s setup the privates first. I use an underscore to designate them as privates (as if the word private wasn’t enough… hey man, you never know…).

// dim them there vars boi!
static private var inited:Boolean = false;
private var _state:String = "sign in";
private var _username:String;
private var _password:String;

Before we start littering our class with some functions, let’s finish defining the rest of our variables. The rest are just controls. Now, me personally I don’t like using import in my classes. I had a bug once where a class wasn’t exported because I didn’t use it. Using an asterisk(*) at the end of a package could sometimes crash Flash, and there is a big difference between Button and mx.controls.Button; if your just short cutting it to Button, Flash thinks it’s the intrinsic one. So, I just write my fully qualified class paths.

Now, the controls. We’ll be using some labels, text input fields, 2 buttons, a progress bar, and a dead preview movie clip.

// controls
private var deadPreview_mc:MovieClip;

private var main_lbl:mx.controls.Label;
private var username_lbl:mx.controls.Label;
private var password_lbl:mx.controls.Label;
private var username_ti:mx.controls.TextInput;
private var password_ti:mx.controls.TextInput;
private var signIn_pb:mx.controls.Button;

private var progress_pr:mx.controls.ProgressBar;

private var done_lbl:mx.controls.Label;
private var signOut_pb:mx.controls.Button;

Ok, now let’s setup the getter/setters. They are merely public functions that set and get at our private variables. You’ll notice, however, I treat each variable a different way. Let’s talk about state first.

// getter/setters
// ---------------------------------------
public function get state():String
{
        return _state;
}

public function set state(str:String):Void
{
        _state = str;
        draw();
}

This is your basic getter/setter. The getter returns the private’s value, and the setter sets it. The difference here is that since changing state actually changes the visual & graphical layout of the component, rather than call size, which just sizes your component, I call draw, which physically redraws component elements; in our case removing and attach components as necessary. The draw method calls size once it’s done.

Next is username and password. They, however, have 2 entry points into how the values are set. The first, is outside access, and thus why we are using gettter/settters. If someone outside the component wants to set the username, they can via the setter, and this will trigger a refresh. However, components inside our component can also set the value. I don’t want components internally causing a refresh upon themselves, but I also don’t want them directly accessing the private either. There are a couple of ways of handling this. You can just have the component set the private, with the knowledge that at least it’s an internal class component doing so, so it’s not illegal in OOP necessarily. However, you’ve broken whatever internal protective access you may have had in place. Additionally, if you had specific component update stuff you wanted to happen just by running the setter, it will no longer get triggered since your no longer using the setter. Some methods actually have a 2nd or 3rd parameter to prevent the event from being triggered. The setSize method does this; you can pass in true for the 3rd parameter, and a redraw event will not get triggered. Finally, my way is to merely make an additional couple getter/setter methods that merely set the variable, but nothing else. If the setter needs to update the GUI, so be it, but if internal components just need to update the value, they have their route as well.

public function get username():String
{
        return getUsername();
}

public function set username(str:String):Void
{
        setUsername(str);
        size();
}

private function setUsername(str:String):Void
{
        _username = str;
}

private function getUsername(Void):String
{
        return _username;
}

public function get password():String
{
        return getPassword();
}

public function set password(str:String):Void
{
        setPassword(str);
        size();
}

private function getPassword(Void):String
{
        return _password;
}

private function setPassword(str:String):Void
{
        _password = str;
}

Since we’re going to be using a mix-in class, EventDispatcher, I need to at least define his methods, as properties, so the class will assume it has them when trying to compile.

public var addEventListener:Function;
public var removeEventListener:Function;
private var dispatchEvent:Function;

Methods

One unique feature of the framework is that your class’ init method is automatically called for you so there is no need to call an init method inside your class’ constructor like in the past. It is, however, good practice to at least define your constructor.

function SignInView()
{
}

Next, we define our init method that set’s up our class. The first thing you do is call your super class’s init method. I also initialize, only once, my class to have event broadcasting capabilities via EventDispatcher. One thing I found in testing is that the component doesn’t really look good when it’s too small, so I set a min width and height. These variables are already getter/setters in UIObject, so you don’t have to define them, just set them. If your component requires extra logic to calculate those sizes, feel free to overwrite the getter/setters with your own as most classes don’t really use them.

I also need a default size for my component. One big change from Flash MX to Flash MX 2004 is that width and height are now merely getters to _width and _height. Those properties of a movie clip are a little too accurate. By that, strokes can can half-pixel widths to your component, mask’s even if they show only a small part are still added to the total size, and invisible content can do the same. With components that aren’t dynamic, this typically isn’t a problem, but a good Flash practice is to have nothing beyond “stop();” actions on the timeline, so that doesn’t help us. The deadPreview movie clip, something merely used to give a LivePreview a defined drawing rect, is the authortime content, but he won’t last long enough to be of much use, hence calling setSize. The deadPreview used to be removed automatically for you in Flash MX, but in Flash MX 2004, he’s not, so you can either make a base class of your own like I do, or just put the 2 lines of code in there to remove him. Finally, I ensure my setSize call has a true as the 3rd parameter so it doesn’t trigger a redraw since the component framework will call size for me anyway initially.

public function init(Void):Void
{
        super.init();
        
        if(!inited)
        {
                inited = true;
                mx.events.EventDispatcher.initialize(SignInView.prototype);
        }
        
        deadPreview_mc.swapDepths(0);
        deadPreview_mc.removeMovieClip();
        
        minWidth = 100;
        minHeight = 244;
        
        setSize(240, 240, true);
}

The next method is createChildren. Now, typically, this method is called first and you use it to lay out all the components and graphics in your component. However, since our’s is so dynamic because it’s state directly affects what it shows, I’ve chosen to use draw primarily for this purpose. The only component that actually stays throughout the entire life of the component is the label stating what this component is, so it is the only one I’ll create in this instance.

private function createChildren(Void):Void
{
        createClassObject(mx.controls.Label, "main_lbl", getNextHighestDepth());
        main_lbl.text = "Sign In";
        main_lbl.setStyle("fontWeight", "bold");
}

Next is draw. He’s the main hub of actually updating the visuals when the state changes. I first remove everything, and then based on state, attach what is appropriate, and initialize their properties as necessary. Finally, I call size to position everything.

private function draw(Void):Void
{
        username_lbl.removeMovieClip();
        password_lbl.removeMovieClip();
        username_ti.removeMovieClip();
        password_ti.removeMovieClip();
        signIn_pb.removeMovieClip();
        
        progress_pr.removeMovieClip();
        
        done_lbl.removeMovieClip();
        signOut_pb.removeMovieClip();
        
        if(state == "sign in")
        {
                createClassObject(mx.controls.Label, "username_lbl", getNextHighestDepth());
                username_lbl.text = "Username";
                username_lbl.autoSize = "left";
                
                createClassObject(mx.controls.Label, "password_lbl", getNextHighestDepth());
                password_lbl.text = "Password";
                password_lbl.autoSize = "left";
                
                createClassObject(mx.controls.TextInput, "username_ti", getNextHighestDepth());
                username_ti.addEventListener("change", this);
                
                createClassObject(mx.controls.TextInput, "password_ti", getNextHighestDepth());
                password_ti.addEventListener("change", this);
                
                createClassObject(mx.controls.Button, "signIn_pb", getNextHighestDepth());
                signIn_pb.addEventListener("click", this);
                signIn_pb.label = "Sign In";
        }
        else if(state == "signing in")
        {
                createClassObject(mx.controls.ProgressBar, "progress_pr", getNextHighestDepth());
                progress_pr.indeterminate = true;
                progress_pr.label = "Signing In...";
                
        }
        else if(state == "signed in")
        {
                createClassObject(mx.controls.Label, "done_lbl", getNextHighestDepth());
                done_lbl.autoSize = "center";
                done_lbl.text = "Signed In";
                
                createClassObject(mx.controls.Button, "signOut_pb", getNextHighestDepth());
                signOut_pb.addEventListener("click", this);
                signOut_pb.label = "Sign Out";
        }
        
        size();
}

Next is size. He is the one who positions everyone based on state. Size is where your component merely changes the size of your components based on a component width and height change, mainly from setSize. That way, your whole component doesn’t have to redraw, re-attach, and re-initialize things based on a setting of the size. I use a built in method called “drawRect” to have a simple background for the component.

private function size(Void):Void
{
        clear();
        lineStyle(2, 0x666666);
        beginFill(0xEEEEEE);
        drawRect(0, 0, width, height);
        endFill();
        
        main_lbl.move(10, 10);
        
        if(state == "sign in")
        {
                username_lbl.move(main_lbl.x, main_lbl.y + main_lbl.height + 10);
                
                username_ti.move(username_lbl.x, username_lbl.y + username_lbl.height + 10);
                username_ti.setSize(width - 20, 22);
                username_ti.text = (username == null) ? "" : username;
                
                password_lbl.move(username_lbl.x, username_ti.y + username_ti.height + 10);
                
                password_ti.move(password_lbl.x, password_lbl.y + password_lbl.height + 10);
                password_ti.setSize(width - 20, 22);
                password_ti.text = (password == null) ? "" : password;
                
                signIn_pb.setSize(60, 22);
                signIn_pb.move((width / 2) - (signIn_pb.width / 2), password_ti._y + password_ti.height + 10);
        }
        else if(state == "signing in")
        {
                var w = width - 20;
                var h = progress_pr.height;
                // progress bar is not respecting the setSize width nor height...freak
                progress_pr.setSize(w, h);
                progress_pr.move((width / 2) - (w / 2), main_lbl.y + main_lbl.height + 10);
        }
        else if(state == "signed in")
        {
                done_lbl.setSize(100, 22);
                done_lbl.move((width / 2) - (done_lbl.width / 2), main_lbl.y + main_lbl.height + 10);
                
                signOut_pb.setSize(60, 22);
                signOut_pb.move((width / 2) - (signOut_pb.width / 2), done_lbl._y + done_lbl.height + 10);
        }
}

Next, I extend the setSize function to incorporate my minWidth and minHeight variables.

public function setSize(w:Number, h:Number, noEvent:Boolean):Void
{
        var w = Math.max(w, minWidth);
        var h = Math.max(h, minHeight);
        super.setSize(w, h, noEvent);
}

Events

Now for the events. My form has 2 click events that it responds to. First, when the user clicks the sign in button to sign in, and when they click the sign out button to sign out.

// Events
private function click(event_obj:Object):Void
{
        if(event_obj.target == signIn_pb)
        {
                var u = username_ti.text;
                var p = password_ti.text;
                dispatchEvent({type: "signIn",
                                      target: this,
                                      username: username_ti.text,
                                      password: password_ti.text});
        }
        else if(event_obj.target == signOut_pb)
        {
                dispatchEvent({type: "signOut", target: this});
        }
}

Lastly, I need to make sure when the user changes the username or password in the input text fields, it updates the variables in the class, but does not trigger to setter’s redraw function. In the draw method, I registered for change events, so when text in the fields is changed, it triggers an event. This event checks to see which field it was that changed, and uses the simple setter functions to merely update the value, and generates events in case someone needs to know if the values have changed.

private function change(event_obj:Object):Void
{
        var t = event_obj.target;
        var str = t.text;
        switch(t)
        {
                case username_ti:
                setUsername(str);
                dispatchEvent({type: "usernameChanged", target: this, username: str});
                break;
                case password_ti:
                setPassword(str);
                dispatchEvent({type: "passwordChanged", target: this, password: str});
                break;
        }
}

Class in Total

And our class, in total, looks like so:

/*
SignInView
Sign In form; used for signing into things.
Provides a GUI to generate events + values.
*/
class SignInView extends mx.core.UIComponent
{
// Variables
        
// standard mx 2004 framework variables
        static var symbolName:String = "SignInView";
static var symbolOwner:Object = SignInView;
public var className:String = "SignInView";
// dim them there vars boi!
        static private var inited:Boolean = false;
private var _state:String = "sign in";
private var _username:String;
private var _password:String;
// controls
        private var deadPreview_mc:MovieClip;
private var main_lbl:mx.controls.Label;
private var username_lbl:mx.controls.Label;
private var password_lbl:mx.controls.Label;
private var username_ti:mx.controls.TextInput;
private var password_ti:mx.controls.TextInput;
private var signIn_pb:mx.controls.Button;
private var progress_pr:mx.controls.ProgressBar;
private var done_lbl:mx.controls.Label;
private var signOut_pb:mx.controls.Button;
// getter/setters
        // --------------------------------------------------------------------------------------
        public function get state():String
{
return _state;
}
public function set state(str:String):Void
{
_state = str;
draw();
}
public function get username():String
{
return getUsername();
}
public function set username(str:String):Void
{
setUsername(str);
size();
}
private function setUsername(str:String):Void
{
_username = str;
}
private function getUsername(Void):String
{
return _username;
}
public function get password():String
{
return getPassword();
}
public function set password(str:String):Void
{
setPassword(str);
size();
}
private function getPassword(Void):String
{
return _password;
}
private function setPassword(str:String):Void
{
_password = str;
}
// --------------------------------------------------------------------------------------
        
public var addEventListener:Function;
public var removeEventListener:Function;
private var dispatchEvent:Function;
function SignInView()
{
}
public function init(Void):Void
{
super.init();
if(!inited)
{
inited = true;
mx.events.EventDispatcher.initialize(SignInView.prototype);
}
deadPreview_mc.swapDepths(0);
deadPreview_mc.removeMovieClip();
minWidth = 100;
minHeight = 244;
setSize(240, 240, true);
}
private function createChildren(Void):Void
{
createClassObject(mx.controls.Label, "main_lbl", getNextHighestDepth());
main_lbl.text = "Sign In";
main_lbl.setStyle("fontWeight", "bold");
}
private function draw(Void):Void
{
username_lbl.removeMovieClip();
password_lbl.removeMovieClip();
username_ti.removeMovieClip();
password_ti.removeMovieClip();
signIn_pb.removeMovieClip();
progress_pr.removeMovieClip();
done_lbl.removeMovieClip();
signOut_pb.removeMovieClip();
if(state == "sign in")
{
createClassObject(mx.controls.Label, "username_lbl", getNextHighestDepth());
username_lbl.text = "Username";
username_lbl.autoSize = "left";
createClassObject(mx.controls.Label, "password_lbl", getNextHighestDepth());
password_lbl.text = "Password";
password_lbl.autoSize = "left";
createClassObject(mx.controls.TextInput, "username_ti", getNextHighestDepth());
username_ti.addEventListener("change", this);
createClassObject(mx.controls.TextInput, "password_ti", getNextHighestDepth());
password_ti.addEventListener("change", this);
createClassObject(mx.controls.Button, "signIn_pb", getNextHighestDepth());
signIn_pb.addEventListener("click", this);
signIn_pb.label = "Sign In";
}
else if(state == "signing in")
{
createClassObject(mx.controls.ProgressBar, "progress_pr", getNextHighestDepth());
progress_pr.indeterminate = true;
progress_pr.label = "Signing In...";
}
else if(state == "signed in")
{
createClassObject(mx.controls.Label, "done_lbl", getNextHighestDepth()); 
done_lbl.autoSize = "center";
done_lbl.text = "Signed In";
createClassObject(mx.controls.Button, "signOut_pb", getNextHighestDepth());
signOut_pb.addEventListener("click", this);
signOut_pb.label = "Sign Out";
}
size();
}
private function size(Void):Void
{
clear();
lineStyle(2, 0x666666);
beginFill(0xEEEEEE);
drawRect(0, 0, width, height);
endFill();
main_lbl.move(10, 10);
if(state == "sign in")
{
username_lbl.move(main_lbl.x, main_lbl.y + main_lbl.height + 10);
username_ti.move(username_lbl.x, username_lbl.y + username_lbl.height + 10);
username_ti.setSize(width - 20, 22);
username_ti.text = (username == null) ? "" : username;
password_lbl.move(username_lbl.x, username_ti.y + username_ti.height + 10);
password_ti.move(password_lbl.x, password_lbl.y + password_lbl.height + 10);
password_ti.setSize(width - 20, 22);
password_ti.text = (password == null) ? "" : password;
signIn_pb.setSize(60, 22);
signIn_pb.move((width / 2) - (signIn_pb.width / 2), password_ti._y + password_ti.height + 10);
}
else if(state == "signing in")
{
var w = width - 20;
var h = progress_pr.height;
// progress bar is not respecting the setSize width nor height...freak
                        progress_pr.setSize(w, h);
progress_pr.move((width / 2) - (w / 2), main_lbl.y + main_lbl.height + 10);
}
else if(state == "signed in")
{
done_lbl.setSize(100, 22);
done_lbl.move((width / 2) - (done_lbl.width / 2), main_lbl.y + main_lbl.height + 10);
signOut_pb.setSize(60, 22);
signOut_pb.move((width / 2) - (signOut_pb.width / 2), done_lbl._y + done_lbl.height + 10);
}
}
public function setSize(w:Number, h:Number, noEvent:Boolean):Void
{
var w = Math.max(w, minWidth);
var h = Math.max(h, minHeight);
super.setSize(w, h, noEvent);
}
// Events
        private function click(event_obj:Object):Void
{
if(event_obj.target == signIn_pb)
{
var u = username_ti.text;
var p = password_ti.text;
dispatchEvent({type: "signIn",
target: this,
username: username_ti.text,
password: password_ti.text});
}
else if(event_obj.target == signOut_pb)
{
dispatchEvent({type: "signOut", target: this});
}
}
private function change(event_obj:Object):Void
{
var t = event_obj.target;
var str = t.text;
switch(t)
{
case username_ti:
setUsername(str);
dispatchEvent({type: "usernameChanged", target: this, username: str});
break;
case password_ti:
setPassword(str);
dispatchEvent({type: "passwordChanged", target: this, password: str});
break;
}
}
}

Lastly, we want our LivePreview to redraw correctly, so we add a setSize every time the component is resized.

// LivePreview
function onUpdate()
{
setSize(Stage.width, Stage.height);
}

Authortime Setup

The last thing to do is package her up into an SWC for faster compile time, portability, and ease of use. Do the following:

– create a movie clip and call it “SignInView”
– name the layer “actions” and put a stop(); action on frame 1
– make a new layer called “dead preview”
– draw a 240 by 240 gray square
– select the square, and convert to a movie clip, and call it dead preview
– give it an instance name of “deadPreview_mc”
– make a new layer called “assets”
– make a blank keyframe on frame 2
– select the frame, and drag the following components to the stage (doesn’t matter where)
+ Label
+ Button
+ ProgressBar
+ TextInput
– right click on the movie clip “SignInView” and select “Linkage…”
– make its Indentifer “SignInView”
– make its AS 2.0 Class “SignInView”
– click OK
– right click on the movie clip symbol “SignInView” in the library again, and select “Component Definition…”
– First, type “SignInView” in the AS 2.0 Class field. Second, check the “Display in Components Panel” check box, and type in “SignInView” and click OK
– finally, right click on the symbol a 3rd time, and select “Export SWC File…”
– Find your components directory. On PC WinXP, it’s:

C:\Documents and Settings\[user]\Local Settings\Application Data\Macromedia\Flash MX 2004\en\Configuration\Components

Once you find the directory, hit save.

To test:

– make a new FLA
– open the Components panel, and in the drop down menu it has on the top right of it, select “Reload”
– find your SignInView component, and drop on stage
– you can either test from here, or use attachMovie. You can addEventListener for your events, etc.

Conclusion

I hope that helps build your first AS2, Flash MX 2004 framework based component and you learned how to make them yourself!

Tutorial Source

10 Replies to “Flash MX 2004 Framework Component Tutorial”

  1. Much appreciate the time taken to post this. I’ve learned alot. But of course it’s spawned a question or two…

    Given this is the V in an MVC pattern would input validation take place here or in the controller? e.g. if one required a valid email address would one want to handle that in the click function prior to dispatching the event?

    Also….it’d be ultra cool to see a barebones Controller written against this View.

  2. I don’t know if this was true in MX 2004, but at least as of Flash 8, the UIEventDispatcher class is mixed into the UIComponent class, so you don’t (any longer) need the following lines:

    public var addEventListener:Function;
    public var removeEventListener:Function;
    private var dispatchEvent:Function;

  3. Follow-on to my previous post.

    Since UIComponent mixes in UIEventDispatcher, the above code does not require any of the code mixing in EventDispatcher, thus it doesn’t need the following lines:

    static private var inited:Boolean = false;

    public var addEventListener:Function;
    public var removeEventListener:Function;
    private var dispatchEvent:Function;

    if(!inited)
    {
    inited = true;
    mx.events.EventDispatcher.initialize(SignInView.prototype);
    }

Comments are closed.