Enabling Access to Timeline Items in AS3 after gotoAndStop()

So there’s a problem with using gotoAndStop() in AS3 classes, as soon as you call it, you temporarily lose access to items on stage (on the timeline) whether they are defined as member variables, or using getChildByName(). This is different from AS2, ite…

So there’s a problem with using gotoAndStop() in AS3 classes, as soon as you call it, you temporarily lose access to items on stage (on the timeline) whether they are defined as member variables, or using getChildByName(). This is different from AS2, items on stage were immediately accessible.

Why use the timeline at all? For one you might have a simple button using named keyframes as button states, or when dealing with assets created by designers that include animations with portions that require localisation of text. So like before you use gotoAndStop() or gotoAndPlay() to manage which “state” your MovieClip is in, but when you go to access anything on stage, it is null, even if it was on the previous keyframe. Here’s a snippet from a typical AS3 class:


...
public var myTitleField: TextField;

protected function onAddedToStage( event: Event ): void
{
  myTitleField.text = "Step" + currentStep; // All good
}
...

But how about changing the current frame as a result of a mouse click for example:


protected function showNextStep(): void
{
  gotoAndStop( "step" + currentStep );
  myTitleField.text = "Step" + currentStep; // myTitleField is null!
}

Ouch, so unlike AS2, you cannot reference something on stage after a gotoAndStop()… I know! Wait a frame!? Afraid not. Waiting a frame (using a callLater or simply hooking into one ENTER_FRAME event broadcast) will not be long enough. But there is another event dispatched by Stage which might work, Event.RENDER.

I think the event sequence goes something like this:

  1. myTitleField is defined here
  2. gotoAndStop( “step2” );
  3. myTextField is null here
  4. Event.ENTER_FRAME is dispatched
  5. myTitleField is still null here
  6. Any code written on the keyframe itself is executed
  7. stage’s Event.RENDER is dispatched
  8. myTitleField is defined again!

So thanks to a tip from Senocular, the RENDER event looks like what we need. To force this event to fire, you must call stage.invalidate(), also the event is only dispatched to items on a DisplayList, and on top of that, it doesn’t go through a typical capture phase, the event is broadcast directly to the DisplayObject, but that shouldn’t matter here.

Ok so a sample might now look like this:


...
public var myTitleField: TextField;

protected function onAddedToStage( event: Event ): void
{
  myTitleField.text = "Step" + currentStep; // All good
  stage.addEventListener( Event.RENDER, onStageRender );
}

protected function showNextStep(): void
{
  gotoAndStop( "step" + currentStep );
  stage.invalidate();
}

protected function onStageRender( event: Event ): void
{
   myTitleField.text = "Step" + currentStep; // myTitleField is back!
}

So that’s fairly crude, but the idea is there. I was speaking to Tink who suggested that we override the gotoAndStop/Play methods in a base class to automatically call stage.invalidate().

Really you want to wrap all of this up in a base class and hook it into a redraw cycle so that you don’t have to add the stage RENDER listener and handler each time you need to do this.

In my case, I have a base View class that contains some simple functionality such as a Flex-like initialization phase and callLater method. I’ve also added these overriden methods and in my case they call invalidate() on my base class, which invokes the “component-like” redraw function whenever a property is changed and it’s time to update the visuals.

My initial reason for doing this was because of shortcomings in Flash’s built in SimpleButton class, which doesn’t appear to allow for localisation or font embedding when switching states, so I ported an AS2 SimpleButton I had written.

This issue is a major annoyance as without using this workaround you are basically locking out designers from working on FLAs, and using code for everything, which isn’t always the best approach in highly creative work, it isn’t even always possible.

Anyway, I hope this proves useful, there’s a couple of other solutions out there but this one feels the most processor friendly and doesn’t rely on essentially “polling” the ADDED event, or ENTER_FRAME until your on stage element appears in memory.

Note: It’s important to remember that there are bugs related to both Event.ADDED_TO_STAGE and Event.RENDER (with wmode). So best be sure your viewers are using Flash Player 9.0.115.0 or greater to avoid a world of pain ๐Ÿ™‚

18 thoughts on “Enabling Access to Timeline Items in AS3 after gotoAndStop()”

  1. Yeah, it’s a major downer… I used the same approach when I first stumbled upon the problem. However, I quickly noticed that for some #%&! reason the render event is not thrown about 1/10 of the time. Huh. And this was with wmode=window. The only solution I found was to listen to the ADDED-event on the parent display object and after each event go through the display list to see whether it was fired by the display object I was waiting for…

  2. Wow that’s really bad news. I was trying to avoid having to listen to the ADDED for each child as it would quickly become unruly. I hope this problem was not experienced in player 9.0.115.0? (Which is the version I’m going to be requiring for Flash 9 content).

  3. Brilliant work Adobe. AS3 is sounding like a real joy for those who still use Flash and haven’t eaten that lumpy flex cool aid residue.
    I can’t express how much this RIA only mind set annoys me. You are totally alienating your core demographic with this OTT “real” coding bull shite.

  4. Just wanted to add that the suggestion in comment 4 worked for me. Example:

    mc.gotoAndStop(2);
    addFrameScript(1, function () { });

  5. Eureka! I’ve found a dependable solution, which I’m pleased to share with you…

    private var label:String;

    public function LabelButton(_labelText:String) {
    labelText = _labelText;

    for (var i:uint = 1; i < totalframes ; i++) {
    addFrameScript(i-1, frameScript); // , false, false);
    }
    }

    protected function frameScript() {
    this["label"].text = labelText;
    }

  6. Oops. In my excitement, the sample code I threw together in my previous post had a couple of hiccups. Here’s a revised edition…

    public class LabelButton extends MovieClip {

    private var labelText:String;

    public function LabelButton(_labelText:String) {
    labelText = _labelText;

    for (var i:uint = 0; i < totalframes ; i++) {
    addFrameScript(i, frameScript);
    }
    }

    protected function frameScript() {
    this["label"]["text"] = labelText;
    }
    }

    t listeners. While scouring the internet for a solution, I found this undocumented function addFrameScript() which allows you to apply code to a frame, in the same manner as the Actions panel in the Flash IDE. This frameScript is the *ONLY* opportunity I’ve found for executing code at the proper moment. With the above code, you can have your dynamic label with keyframes.

  7. Heh heh. Okay, I ran myself round in so many circles looking for a solution, I somehow missed or forgot about the discussion of addFrameScript() above. So my solution is simply to apply a very light weight function call to *EVERY* frame–whether it’s a keyframe or not–to keep the custom label always applied. (I wouldn’t want to apply labels to every keyframe anyway.)

  8. In regard to comments 6 & 7 by Goldstone and Richard Leggett.

    “Thanks for the suggestion. Unfortunately addFrameScript() doesn’t yet support frame labels so I can’t use it in this case.”

    Why couldn’t you do this:

    mc.gotoAndStop(“frameLabel”);
    var curFrame:int = mc.currentFrame;
    addFrameScript(curFrame, function () { });

    If that is what you meant.

    Cheers.
    R.

  9. Another simple solution, is to place frame scripts on the actual frames that call functions within the class…

    This is probably the easiest way to handle it since most of the thread handling will be handled by flash..

  10. this doesn’t work for me
    mc.gotoAndStop(2);
    mc.addFrameScript(1, function () { });

    do I really have to go to frame 2
    and then add a frame script on frame 1???
    (in any case it doesn’t work)

    AS3 is a pain in the Ass…ok it’s “powerful” but who cares!!! we want fast development here!!!…no super ultra rigid constraints!! I agree with bob: too much attention to RIA dev led adobe to forget its main target…
    I am an experienced designer and programmer with a few Flash Apps on his back…i tell you: AS3 sucks! (I’m sorry to say that!)

    is it possible that such a simple thing as a mc timeline and its content (that has worked for the past 8 versions) now can’t be addressed via code…without a deprecable workaround!!!?!?! unbelievable!! they should read pragmatic programming at adobe!

    in the future I will use AS3 only when I need to use papervision!
    rants off ๐Ÿ™‚
    I’ll try the Event.RENDER solution

  11. I solved the problem without the need of any of the two workarounds

    for me the problem was that inside the mc there was a label who wasn’t accesible only the SECOND TIME that the timeline went back to frame 1.
    so mc.myTextField was yielding the infamous ERROR 1009
    Cannot access a property or method of a null object reference
    (again, only on the second round)

    To solve the issue
    – I ensured that the textfield had no blank frames..and that was “alive” for the whole timeline..
    If you have to make it disappear, bring it to alpha 0 and leave the frame alive untill the end of the mc
    – ensure that at every keyframe your child object (in this case the textField) has the same identical instance name
    (for a distraction I had one of the keyframes with a different instance name without noticing.)

    I dind’t have to use the Event.RENDER-ivalidate ignoble workaround!
    nor the addFrameAction
    nor I will try to need it in the future (screw AS3)
    ๐Ÿ˜‰

  12. Here’s a class I threw together that uses the ignoble render event and stage.invalidate() method. This class allows you to specify a complete function and pass parameters

    Get the class here:
    http://www.brucebranscom.com/classes/Utils.as

    Usage:

    //this only needs to be called once every application
    Utils.setStage( stage );

    Utils.gotoAndPlay( myMC, “frameLabel”, completeFunction );

    function completeFunction():void
    {
    you can access myMC’s children here
    }

  13. Simple solution:

    1) first frame inside of the target movieclip in library
    var fooText:String = “”;

    2) second frame inside of the target movieclip in library
    TextField(this.getChildAt(1)).text = fooText;

    3) Now from anywhere you are accessing the target
    target.fooText = barText;
    target.gotoAndStop(2);

  14. In Adobe Director, you can force the stage to render using updateStage() command. Very useful.

    Event.RENDER is passive and not as easy.

    If you’re not uptight about scripting on the timeline, it’s even easier to call a function in the document class from the frame in question, this will be called after the frame is drawn so all mc’s will be “seen”.

    For example:

    in Document class:

    function functionA():void{
    gotoAndStop(“login”);
    }

    function functionB (){
    //all movieclips on timeline will be available.
    }

    on timeline, at login frame script:

    root. functionB ();

  15. I found a related bug that has the same symptoms as above – in the case where using the render event and addFrameScript don’t work.

    It has to do with setChildIndex and I am pretty sure it is an adobe bug.

    Here is an example to illustrate:
    1. Pretend in frame 5, your movieclip has 5 children.
    2. gotoAndStop(10) where there are 10 children. Set its index to 5.
    3. return to frame 5. the movieclip that was at index 5 loses its reference, although it still appears.

Comments are closed.