Object-Oriented MovieClip Callbacks

Object-Oriented MovieClip Callbacks

It’s Day 2 of Jason’s Actionscript 2.0 Bootcamp! The first step was to port his AS1.0 code into AS2.0 classes. We’ve gone as far as just creating all our components on the screen, but have not yet hooked up any events. Why? Because they’re tricky in AS2.0 (at least when you are rolling your own). For example, we had to go over how MovieClip callbacks in an object-oriented context doesn’t work. Witness this class definition:

class Foo {

var mc :MovieClip; var myProperty :String;

function Foo ( parentClip:MovieClip ) { mc = parentClip.attachMovie (“FooImage”,”Foo1″,1); mc.onPress = HandleOnPress; }

function HandleOnPress() { trace(“myProperty == ” + myProperty); }

}

When you run this, the output will say “myProperty == undefined”. The reasoning is a bit esoteric: The MovieClip class uses old callback conventions that don’t maintain object context in ActionScript 2.0…you’re just setting a pointer to the address of a stream of code, not a method being invoked on an object. With the object context, you can’t access any variables in the object you’re defining your code in, though it certainly almost looks like it should be fine.

Why they didn’t add MovieClip listeners in MX 2004 baffles me. Maybe they didn’t want to add duplicate listeners in an already confusing API? But then why did they go ahead and do that in the TextField class? You can also use the Mouse and Keyboard object listeners instead to do the same thing I’m describing. But I digress.

Anyway, I showed him the workaround. Here’s the modified constructor:

function Foo ( parentClip:MovieClip ) { mc = parentClip.attachMovie(“FooImage”,”Foo1″,1); mc.parentObject = this; mc.onPress = function() { this.parentObject.HandleOnPress() } }

The key to this is the this keyword. It means two different things here. In the first line, it refers to the instance of Foo that’s being created. In the anonymous function, this evaluates to the MovieClip that is triggering the callback function. If you didn’t know that, you’d be screwed! So, we’re using this behavior to re-establish object context in the callback without a lot of tedious manual lookup using associative arrays (which I’ve done in the past). MovieClip is a skanky object…it’s not too discerning about what you stick into it during runtime, if you catch my drift. The parlance in AS 2.0 is that it’s a “dynamic object”, requiring no declaration of properties before you can use them. You know, just like good ole horrible Actionscript 1.0, with its “call me and I’ll be there but useless and non-error generating so you’ll never know you misspelled ‘proerty’ in 1000 lines of code but hey it makes the language seem more friendly” insanity. Gah!

After Jason finished porting his code, we’ll talk about the need for explicit destructors, as using a composition class like this for MovieClip sort of defeats some of the magical garbage collection features of Actionscript 2.0.

UPDATE MAY 12, 2006:

Since writing this article, I found a neat trick from Colin Moock’s Essential Actionscript 2.0 book. It’s just mentioned in passing, but it’s very critical. Check out pages 300 and 308 about this use of an intermediate variable thisInstance to hold the value of this.

class Foo {

var mc :MovieClip; var myProperty :String;

function Foo ( parentClip:MovieClip ) { mc = parentClip.attachMovie (“FooImage”,”Foo1″,1); // dereference ‘this’ var thisInstance:Foo = this; mc.onPress = function() { thisInstance.HandleOnPress() } }

function HandleOnPress() { trace(“myProperty == ” + myProperty); }

}

The use of the local variable thisInstance saves a copy of this so it refers specifically to the instance being created. So, when the anonymous function assigned to mc’s onPress callback executes, it actually refers to the instance instead of the this keyword. Remember, the this keyword, in the context of an anonymous function used as a callback handler, refers to the MovieClip. This is a very handy trick.

If you’re using event listeners with components, you’ll be looking instead into using Macromedia’s mx.utils.Delegate class to create the handler for the addEventListener() call. Works great in conjunction with Grant Skinner’s GDispatcher class.

9 Comments

  1. Jason 18 years ago

    Thank you!!!!! I’ve been fighting with the onPress event and what the hell happens to the calling object for 2 days straight. I stumble across you article and it has helped me tremendously. Thank you very much.

    ——-

  2. Dave Seah 18 years ago

    Hey Jason! Since I wrote this article I realized that there’s a better way, from Moock’s Actionscript 2.0 Reference. I’ll update this article now.

  3. sdury 18 years ago

    Hello all. I’m facing to a similare issue. I’m trying to build a basic MovieCip class dedicated to provide elementary methods and function, particulary for that concerns event… such a class that can be inherited and created dynamically… no way to do that. event attached to this basic class is either never raised, either raised but not connected to the correct context… does nobody in the world face to this classical approach ??
    thanks for helping me…

  4. Dave Seah 18 years ago

    If you’re trying to just extend the intrinsic MovieClip class and create movieclips that way, it won’t work…it’s the way Flash works. However, you can WRAP the class instead:


    class MyMovieClip {
      var mc:MovieClip;
      function MyMovieClip() {
      mc = _root.createEmptyMovieClip(blah blah blah);
      }
      function setX(x:Number) { mc._x = x }
    }</pre>

    You get the idea. This is how I’ve done it, though I don’t know if this is entirely the best way. I wonder if you could do some clever casting…note I haven’t tried this, just thinking aloud.

  5. sdury 18 years ago

    Hi,
    thanks for the answer. this is finaly what I’ve done : a base class that wrap the movieclip. implementing a composition model more than a inheritance based model (if the experssion composition model” has a sens in English, it is a french word :)). But I’m facing the same issue concerning the event broadcasting and event management. See the example below:

    In a EventMain.as file:

    class Event.EventMain {

    public var _mainClip_mc:MovieClip;
    public var _Container_mc:MovieClip;

    public var _BannerBottom:Banner;
    public var _BannerTop:Banner;
    public var _EventDispatcher:GDispatcher;

    public function EventMain(mainClip_mc:MovieClip) {
      _mainClip_mc = mainClip_mc;
      Main();
      InitDispatcher();
    }

    public function InitDispatcher() {
      _EventDispatcher = new GDispatcher();
    }

    public function Main(){
      CreateContainer();
      CreateBanners();
    };


    public function CreateContainer() {
      _Container_mc = _mainClip_mc.createEmptyMovieClip(“Container”, 0);
    }

    public function CreateBanners() {
      _BannerTop = new Banner(_Container_mc, 1, 0, 0);
      _BannerBottom = new Banner(_Container_mc, 2, 0, 200);
    }

    public function PlayMessage(e:Object) {
      trace(e);
    }
    </pre>

    }

    in the banner class:

    class Event.Banner {

    public var _parentClip_mc:MovieClip;
    public var _Container_mc:MovieClip;
    public var _depth:Number = 0;
    public var _ContainerName = “Cont_mc”;

    public function Banner(parent_mc:MovieClip, depth:Number, xpos:Number, ypos:Number) {
      _parentClip_mc = parent_mc;
      _depth = depth;
      _ContainerName = “Cont_mc” + _depth;
      CreateBanner();
      SetPosition(xpos, ypos);
    }


    public function CreateBanner() {
      CreateContainer();
    }

    public function CreateContainer() {
      _Container_mc = _parentClip_mc.createEmptyMovieClip(_ContainerName, _depth);

      // Use MovieClipLoader to load the image.
      var my_mcl:MovieClipLoader = new MovieClipLoader();
      my_mcl.loadClip(“Banner.swf”, _Container_mc);
      configureEventDispatcher();
    }

    public function SetPosition (xpos:Number, ypos:Number) {
      _Container_mc._x = xpos;
      _Container_mc._y = ypos;
    }


    //——————————————————————————————————————
    // Event Handling
    //——————————————————————————————————————
    public function onMouseDown():Void {
      trace(_ContainerName + ” down”);
    }


    public function onPress():Void {
      trace(_ContainerName + ” pressed”);
    }

     

    //——————————————————————————————————————
    // poubelle handling
    //——————————————————————————————————————
    public function configureEventDispatcher() {


      var owner:Banner = this;
      _parentClip_mc.onPress = function():Void {
          owner.onPress();
          trace(“adding press handling on ” + owner._ContainerName);
      }
      /*
      var oListener:Object = new Object();
      oListener.onPress = function (e:Object) {
          owner.onPress();
          trace(“adding press handling on ” + _ContainerName);
      }
      _parentClip_mc.addEventListener(“onPress”, oListener);
      */

      /*
      _parentClip_mc.addEventListener(“press”, this);
      */

    }
    </pre>

    }

    The banner.fla is a single shape, and the EvenMain.fla contains only:
    import Event.EventMain;

    stop();

    var _Main:EventMain = new EventMain(this);

    As you can see, in this example, the onPress event is raised each time the user press within a banner (top or bottom)… but the function called is always the same: the last one “attached” to the onPress broadcaster : the listener of the bannerBottom.

    as you can sse, I’ve tried other approach for adding listener to banner without any success. The purpose is finally simple and can be summarised by this question : who can we raised event from a loaded movie clip to another ? Imagine that my bottom banner contains at term menu that raised event for the main class launching the loading of other movie, depending on context, xml flux ^, database data, etc..
    thanks for this interest.

  6. sdury 18 years ago

    oops.. please note that in the provided example there is a reference to the famous GDisptacher class that I’m trying to add in the global picture…. as it was too much simple…GDispatcher not actually used as is it shown.

  7. jeff 17 years ago

    Thank you ! You made my day !

  8. Dave 17 years ago

    Hope I’m not repeating anyone, didn’t have time to go through all the notes. I just found this link which solved a lot of my problems like this and then beyond…
    http://dynamicflash.com/2005/02/delegate-class-refined/
    Cheers,

  9. Yume 16 years ago

    Thank you for posting this!  I’ve been looking forever for a solution to this issue.