| « Adobe Sponsored Poker Event - Part Deux | Update: Using a GPRS, 3G or HSDPA Mobile with eee PC » |
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 on IM 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 ![]()
Follow me on Twitter
http://flashmove.com/forum/showthread.php?t=33081
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.
mc.gotoAndStop(2);
addFrameScript(1, function () { });
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;
}
totalframes>
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;
}
}
totalframes>
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.
"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.
This is probably the easiest way to handle it since most of the thread handling will be handled by flash..
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
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)
;)
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
}
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);
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 ();