0

I've searched around and found similar questions to this, but the answers (accepted ones, even!) are either hacky, appear to have rather little to do with the actual question, or just aren't working when I try them.

I'm trying to reference objects on the stage in the current frame from a document class in Actionscript 3 in Flash CS3. In this example, I'm trying to get at a dynamic text field with the instance name "question_txt", but there are also buttons and other things I'll need to get at to put event listeners on and such.

I have "Automatically Declare Stage Instances" checked in the publish settings, so the references should be there -- in fact, if I try to declare them in the class, I get errors about a conflict with the name -- but when I try to reference these objects (in any of several ways I've now tried!) I ALWAYS GET NULL.

package {
    import flash.display.MovieClip;
    import flash.display.SimpleButton;
    import flash.display.Stage;
    import flash.text.TextField;
    import flash.net.navigateToURL;
    import flash.net.URLRequest;
    import flash.net.URLLoader; 
    import flash.events.Event;

    public class DecisionTree extends MovieClip {

        private var tree_xml:XML;
        private var current_node:String;

        public function DecisionTree():void {
            trace("Constructor");
            tree_xml = new XML();
            tree_xml.ignoreWhitepace = true;
            loader = new URLLoader();
            loader.addEventListener(Event.COMPLETE,
                function(e:Event):void {
                    tree_xml = new XML(e.target.data);
                    goToNode('main');
                });
            loader.load(new URLRequest("decision_tree.xml"));
        }

        private function goToNode(name:String):void {
            trace("goToNode " + name);
            current_node = name;
            node_xml = tree_xml.node.(attribute('name') == name)[0];
            node_frame = parseInt(node_xml.attribute('frame'));
            node_question = node_xml.question.text();
            gotoAndPlay(node_frame); // will goto frame 35 where the stuff is

            // ARRRRRGH EVERYTHING'S COMING UP NULL!!!
            // TypeError: Error #1009: Cannot access a property or method of a null object reference.
            question_txt.text = node_question;

            //q = question_txt as TextField;
            //q.text = node_question;

            //qtxt = stage.getChildByName('question_txt') as TextField;
            //qtxt.text = node_question;

        }
    }
}
hoff2
  • 774
  • 3
  • 9
  • 25

4 Answers4

4

The simple fact is that you cannot rely on this to work:

gotoAndStop(2);
thing_on_frame_2.foo(); // no guarantee this will work.

Chances are that the other answers you've seen look hacky because it is but there's no better way around it. The playhead will not actually be guaranteed to be on frame 2 immediately after you call gotoAndFoo(), and if it is there's no guarantee that the objects placed there in the fla will have been instantiated by the time you attempt to access them. This used to be legal and valid in AS2, but in AS3 it is no longer.

Typically, you would do one of the following to solve this:

  1. Make sure the referenced object exists on all frames of the timeline.
  2. Add a framescript to act as a callback that lets you know when you've reached the frame in question. This can either be a framescript written in the fla, or one that you add at the class level using the addFrameScript function.

Optionally, if you're only concerned about listening for button clicks then instead of adding the listeners to the buttons directly you could add the listener to the topmost container of the buttons and then check the instance name of the object that dispatched the event before deciding what to do about the event. I favor this method as it's easier to keep track of what is and is not a listener.

Hacky or not, that's just how it's done.

EDIT with more info:

I know that if you trace the current frame the number returned will be the one you just instructed the playhead to move to. The problem is that there is no guarantee that objects on the stage in the fla will or will not be instantiated by the time you request them in the code. That's why you use a framescript, as the objects are guaranteed to have been instantiated by that time.

As to the question of whether there are events you can listen for that are guaranteed to dispatch after construction, the answer is "maybe. What version of Flash Player are you targeting?" The most commonly known frame event is ENTER_FRAME because it's the one that's been supported from the beginning. Over time we've been given EXIT_FRAME, FRAME_CONSTRUCTED, and the like that all dispatch at different times. FRAME_CONSTRUCTED is the one that you'd want to listen for, but it's not very wieldy or practical to add a FRAME_CONSTRUCTED listener for just the one split second you need it and then remove it immediately, and of course will not compile if you are forced to work in CS3 (and will crash FP9).

I stand by using addFrameScript as the best way to do this. It was undocumented for a long time (it may still be, in fact), but it's the way that the flash compiler internally attaches the script you write in the fla on the timeline to a movie clip's "frames".

scriptocalypse
  • 4,942
  • 2
  • 29
  • 41
  • I thought about that too, and even threw in a trace(currentFrame) to see if I was reaching the expected frame in time, and -- and the frame number output was the expected one (35) but the reference was still null. Is there an event I can listen for to make sure I'm on the frame I want? (the frames where the stuff I want is all have calls to stop() on the timeline) Otherwise, suggestion 2 sounds do-able, though I haven't used addFrameScript before. – hoff2 Jun 08 '11 at 16:57
  • @centipedefarmer I'll edit my question with some more info related to this. – scriptocalypse Jun 08 '11 at 17:01
  • addFrameScript is doing the job nicely. Thanks! – hoff2 Jun 08 '11 at 17:07
  • ah, okay so now I'm hitting another weird problem occurring within the function I'm passing to addFrameScript. I'm trying to add event listeners to several buttons dynamically based on my XML data, but variables I'm expecting to be closed over the listener function are instead all ending up with the value of the last one, such that all my buttons now do the same thing instead of different things. Does variable scoping work differently in frame scripts or something? – hoff2 Jun 08 '11 at 20:03
  • No, variable scoping follows the same rules when you use addFrameScript as they do with any other function. Funny enough I think you're encountering the same issue as this other post I answered today: http://stackoverflow.com/questions/6283126/how-do-you-bind-a-variable-to-a-function-in-as3/6283506#6283506 It sounds like you're running into Flash's propensity to late-bind somehow. This is why I say just add the click listener at the "top" of the display list and you don't ever have to worry about these issues. Let the mouse events bubble up and sort out the sender at runtime. – scriptocalypse Jun 08 '11 at 20:07
0

An object needs to exist on the stage for one whole frame before you can expect to have any sort of meaningful interaction with it via code. This is true always. When in doubt, wait one frame.

Dela Torre
  • 51
  • 8
  • In my constructor I'll make an OnEnterFrame event listener and have it trigger my init function for that object, the call back function destroys then listener when it's called. – Dela Torre Jul 08 '15 at 01:43
0

U can't access elements, which are on other frame than script/code itself. In this case, main class resides on first frame. So basicly best guide is that you put all elements on 1. frame and then hide/show them when needed.

But if u can't do that for any reason, my advice is, that on any frame that there is elements set some variable in main document, pointing to that element.

For instance:

on frame 2. you have "question_txt" element, and on that frame put code:

MovieClip(root).question_txt = question_txt

and in main class put variable question_txt:TextField :

public var question_txt:TextField;

And so on, for every frame and every element. U can also store them in array or dictionary or somethink similar, as long u can change the reference

Urosan
  • 321
  • 1
  • 11
  • I'd have preferred to have everything on frame 1 and all actionscript-driven, but I'm basically being tapped to add magic actionscript pixie-dust to something that was already built by a designer. I'm not wizardly enough in the Flash IDE to reliably restructure the whole .fla, nor is it worth that much time :D – hoff2 Jun 08 '11 at 17:09
0

I tend to avoid using the document class and put an empty symbol on the stage with a base class and then treat that class like a document class.

grmdgs
  • 585
  • 6
  • 17