click events don't reach root flash object - actionscript-3

I started using as3 a while ago, and mostly used starling instead of native flash objects. Today I decided to give a try to raw flash, and created a simple button:
private var _button: SimpleButton;
public function Main()
{
if (stage) init();
else addEventListener(Event.ADDED_TO_STAGE, init);
}
private function init(e:Event = null):void
{
removeEventListener(Event.ADDED_TO_STAGE, init);
// entry point
_button = new SimpleButton(new BUTTON); //BUTTON is an embedded bitmap
addChild(_button);
_button.useHandCursor = true;
_button.addEventListener(MouseEvent.CLICK, OnClick);
}
private function OnClick(e:MouseEvent):void
{
trace("clicked");
}
This would definely work in starling, but I found that the click event won't dispatch to the button for some reason. Neither is hand cursor shown on mouse over. I tried mouse over, mouse up and other mouse events, but none of them work. I checked that the button is correctly added to stage. Also, when I add a mouse click event listener to the stage itself, clicks register normally. When I add a listener to Main, no events are registered again.
I'm probably misunderstanding something obvious. Maybe I need to dispatch events manually down to the children? But that would be strage, though. Or can it be a bug in FlashDevelop? Please, help.

The constructor of SimpleButton takes 4 optional parameters:
public function SimpleButton(upState:DisplayObject = null,
overState:DisplayObject = null,
downState:DisplayObject = null,
hitTestState:DisplayObject = null)`
of which you supply the first one (upState):
_button = new SimpleButton(new BUTTON); //BUTTON is an embedded bitmap
The others default to null. That includes hitTestState which is the same as property of the class hitTestState:
Specifies a display object that is used as the hit testing object for the button. [...] If you do not set the hitTestState property, the SimpleButton is inactive — it does not respond to user input events.
It also includes a solution to your problem:
For a basic button, set the hitTestState property to the same display object as the overState property.
However, if all you want is to add click functionality to that Bitmap, which it doesn't have on its own, because it's not an InteractiveObject, simply use a Sprite:
// entry point
_button = new Sprite();
_button.addChild(new BUTTON); //BUTTON is an embedded bitmap
addChild(_button);
_button.useHandCursor = true;
_button.addEventListener(MouseEvent.CLICK, OnClick);
Use SimpleButton only if you want the functionality of its 4 states.
Please be careful with the overloaded term "stage":
I checked that the button is correctly added to stage.
If you mean the "drawing area" that you can see in the Adobe Flash IDE or the main time line with "stage", then yes, you'd be correct that your button is added to that.
The stage property of DisplayObject however is something different. It's the top most element of the display list and the container that the instance of your Main class is added to, which happens at the start of the execution of your .swf in the FlashPlayer.
As your Main class adds the _button to itself by calling its ownaddChild(), the button is added to the instance of Main and not stage, which are two different objects.

Related

Why is my button instance forgetting its textfield text and event listener?

I'm working on an assignment due at midnight tomorrow and I'm about to rip my hair out. I am fairly new to ActionScript and Flash Builder so this might be an easy problem to solve. I think I know what it is but I do not know for sure...
I'm developing a weather application. I designed the GUI in Flash CS5. The interface has 2 frames. The first frame is the menu which has a zipcode input and a button instance called "submit". On the second frame, it has another button instance called "change" which takes you back to the first frame, menu.
In Flash Builder 4, I wrote a class to extend that GUI called Application. When Main.as instantiates it, the constructor function runs. However, this was my first problem.
public class Application extends InputBase {
public function Application() {
super();
// Y U NO WORK???
this.gotoAndStop(1);
this.change.tfLabel.text = "Change";
}
}
When I ran debug it tossed a #1009 error saying it could not access the property or method of undefined object. It is defined on frame 2 in Flash CS5. I think this is the problem... Isn't ActionScript a frame based programming language? Like, you cannot access code from frame 2 on frame 1? But, I'm confused by this because I'm not coding on the timeline?
Anyway, I thought of a solution. It kinda works but its ugly.
public class Application extends InputBase {
public function Application() {
super();
// Y U NO WORK???
this.gotoAndStop(1); // when app is first ran, it will stop on the first frame which is the menu frame
setButton(this.submit, "Submit", 1);
setInput(this.tfZipCode);
}
private function submitZip(e:MouseEvent):void {
this.nextFrame();
setButton(this.change, "Change", 2);
}
private function menu(e:MouseEvent):void {
this.prevFrame();
setButton(this.submit, "Submit", 1); // if I comment this out, the submit button will return to the default text label and it forgets it event.
}
private function setButton(button:ButtonBase, string:String="", action:uint=0):void {
button.buttonMode = true;
button.mouseChildren = false;
button.tfLabel.selectable = false;
button.tfLabel.text = string;
switch (action) {
case 1:
button.addEventListener(MouseEvent.CLICK, submitZip);
break;
case 2:
button.addEventListener(MouseEvent.CLICK, menu);
break;
default:
trace("Action code was invalid or not specified.");
}
}
}
Its not my cup of tea to run the set button function every time one of the button instances are clicked. Is this caused by frames or something else I maybe looking over?
I believe you have two keyframes & both have an instance of the button component called button.
If you are working on the timeline try putting the button in a separate layer, with only one key frame.
Something like the following:
Ensures that you are referencing the same button every time & not spawning new ones on each frame.

MovieClip doesn't dispatch mouse event after nesting MovieClip loaded from external SWF

Mostly, problem is described in the title... I tried to load an external SWF file that contains some named MovieClip instances (exporting and naming is done by Flash CS5 software) and to add some of externaly loaded (named) MovieClip-s in MovieClip object which is created in my code. Problem appears when i add MOUSE_CLICK listener to parent MovieClip. Simply, it does not dispatch event when i click on it at the stage...
private var loader:Loader;
public function Example(){
loader = new Loader();
var request:URLRequest = ... // URL to external SWF
loader.contentLoaderInfo.addEventListener(Event.COMPLETE, loadingCompleted)
loader.load(request);
}
private function loadingCompleted(event:Event):void{
var mc:MovieClip = loader.content as MovieClip;
var myMovie:MovieClip = new MovieClip();
myMovie.addChild(mc.getChildByName("object_name"));
myMovie.addEventListener(MouseEvent.CLICK, myMovieClicked);
addChild(myMovie); // myMovie (with nested mc) appears on the stage
}
private function myMovieClicked(evt:Event):void{
//never reached
}
EDIT: I didn't mention that i'm working in Flex using FlashBuilder 4.5 where i created ActionScript project. Code above is body of Example class, which is main SWF class.
EDIT AFTER ANSWER: myMovie.mouseChildren = false solves the problem. Earlier i tried to set mouseEnabled = true, and it didn't fix the problem. But i'm confused about event flow now... Even if child is target node, why mouse listener on parent MovieClip doesn't recieve event (in capture phase) when parent is still on event flow? Moreover, when i create another movie clip in my code (whit some simple shape inside) and add it to myMovie, everything works fine. What is so special when i obtain movie clip from externaly loaded SWF?
Have you tried doing myMovie.mouseEnabled = true and myMovie.mouseChildren = false ?

Stage and classes

I am new to AS3 and am trying to lean its OOP ways. What I am having problems with is understanding how to access the stage with separate classes.
Here is an example of what I am trying to do:
package game{
import flash.display.*;
public class Main extends MovieClip{
function Main(){
var player = new Player();
var playerBullets = new playerBullet();
addChild(player.players);
}
}
package game{
import flash.display.*;
public class Bullet extends Main // also tried with MovieClip and Sprite{
function Bullet(){
// empty
}
function blah(){
var someSprite = new someSprite();
Main.addChild(someSprite);
stage.addChild(someSprite);
root.addChild(someSprite);
}
}
}
I have Omitted another class which calls the blah method as I feel it is not relevant.
Basically what I want to know is how to add things to the stage in classes as it lookes like I am missing something crucial.
*EDIT TO INCLUDE ERROR*
TypeError: Error #1009: Cannot access a property or method of a null object reference.
at game::Bullet/blah()
at game::Player/fire()
You shouldn't necessarily be extending main to create something like a bullet class, this can be it's own class that extends Sprite or MovieClip. The stage object is considered a global object, as it is a singleton (except in the case of Adobe AIR where you can have one stage per NativeWindow that you spawn). So any object that extends DisplayObject or has DisplayObject in it's inheritance chain will by default have a reference to the stage via a getter, which is populated automatically when a displayObject is added to the display list. This can happen by either adding a clip directly to the root stage object or by adding a clip as a child of another clip, that eventually connects to the stage. For example:
var clip1:MovieClip = new MovieClip();
stage.addChild(clip1); //Clip 1 can now access the stage reference internally.
ver clip2:MovieClip = new MovieClip(); //Right now, clip2 cannot access the stage reference interally.
clip1.addChild(clip2); //Now clip2 can access the internal stage reference because it has been connected to the display list through clip1.
The other mistake people make is accessing stage within a DisplayObject typed class (such as your Main class) without first ensuring that the object itself has been added to the stage. You do this by listening for the Event.ADDED_TO_STAGE event within the constructor of the class, like so:
public class Main extends MovieClip{
function Main(){
if(stage){
//The stage reference is present, so we're already added to the stage
init();
}else{
addEventListener(Event.ADDED_TO_STAGE, init);
}
var player = new Player();
var playerBullets = new playerBullet();
addChild(player.players);
}
private function init(e:Event = null)
{
trace("Added to stage, the stage reference is now populated and stage can be accessed");
}
}
This could be the problem you're having, but it's hard to say since you have not specified any errors. However, this is likely an issue or will be for you, since it's quite common. Inside the init() method you can then set a flag so that when external classes call your Main.blah() method, you can ensure that the stage reference exists before attempting to add something to the stage. Take note however that within your Main class when you simply say:
addChild(someChild);
or
this.addChild(someChild);
you're not adding that child to the stage, but rather to the Main object, which is a MovieClip or Sprite based object that is itself attached to the stage automatically when you set it as the Document class. Hope this info helps.
Update
To explain the display list a little more:
Think of all your movieclips as dishes, and the stage as the table. You can only access the table from the dish, if the dish is placed directly on the table, or if a dish is stacked on top of another dish that touches the table. If you have 10 plates stacked on top of each other, they all touch the table eventually, via their connection to each other. This is essentially a visualization of the flash display list. The way you put dishes on the table is by using addChild(dish). If you have not placed an object somewhere on the table, and try to access the table from that object, you're going to fail. You're getting the "access to undefined" error because you're calling the "blah()" method, which accesses the stage (table) before the bullet (dish) has been added to the stage (table). So you must first either directly add the bullet to the stage, or add it to another object that has already been added to the stage. Change your code like so:
var myBullet:Bullet = new Bullet();
stage.addChild(myBullet);
//Or, if this class, assuming it's the player class, has already been added to the stage, just do this:
this.addChild(myBullet);
myBullet.blah();
Even so, you should still have some error checking within your "blah" method to ensure that the stage is available:
function blah(){
var someSprite = new someSprite();
if(stage){
Main.addChild(someSprite);
stage.addChild(someSprite);
root.addChild(someSprite);
}else{
trace("error, stage not present");
}
}
However you should also note that by adding this child to Main, then stage, then root all in sequence, this does not duplicate the someSprite object. When you add a display object to a new parent object, the object is automatically pulled from it's current parent and moved to the new one. So all this code will do is eventually add someSprite to root, which I believe will fail because root is not a display object, but rather a global reference mainly used to access global objects such as the stage and the Loader object used to load the SWF.
You shouldn't ever be calling stage.addChild. There should be only one child of the Stage, and that's the document class.
You make a MovieClip display on the screen by adding it to the stage's display list.
Stage
+ Main Timeline
+Anything
+Else
+You
+Want
So assuming that Main is your document class for the main timeline...
// inside of Main's constructor...
public function Main(){
var anything:MovieClip = new MovieClip();
var Else:TextField = new TextField();
var you:SimpleButton = new SimpleButton();
var want:Sprite = new Sprite();
this.addChild(anything);
this.addChild(Else);
this.addChild(you);
this.addChild(want);
}
Then in order to add children even lower, for example if you want something to be a child of "Anything" such that you have....
Stage
+ Main Timeline
+Anything
+And
+Everything
+Else
+You
+Want
public function Main(){
var anything:MovieClip = new MovieClip();
var Else:TextField = new TextField();
var you:SimpleButton = new SimpleButton();
var want:Sprite = new Sprite();
this.addChild(anything);
this.addChild(Else);
this.addChild(you);
this.addChild(want);
var And:Sprite = new Sprite();
var everything:Sprite = new Sprite();
anything.addChild(And);
anything.addChild(everything);
}
EDIT: Ascension Systems asks why you should never add any display object directly as a child of the stage. The simplest answer is that you can't ever guarantee that what you believe you're creating as a document class, or as a main timeline in fact actually is going to be used as such. Your use of the stage may later preclude your swf from being loaded as a child of a larger application depending on what it is you've done, exactly. Relying directly on the stage can mean that you're making some assumptions about the nature of the display list that may not hold in the future. That's the way in which it breaks modularity (which is not the same as breaking oop).
Why add to the stage when you could just create your entire application as a MovieClip that is completely self-contained with no reliance on the concept of a "stage" beyond that which is required for learning world coordinates? That way you can be much more modular in your design and you sacrifice nothing.
In some people's work this may be considered an edge case. In my work this has happened both to me when I've created applications that I thought at the time were purely stand-alone that ended up being repurposed later to be a module, and also to swfs that other people created that were intended to be strictly stand-alone, but that I was then to integrate as a module into a larger application. In all cases there were some nasty side effects to contend with. That's where I learned not to rely too closely on the stage for much beyond world coordinates.
Every display object has a property called stage, which is null until that object is added to the display tree.
When you are unsure if an object has been added to the stage, there is a listener you can employ for that purpose:
public class Main extends MovieClip
{
import flash.events.Event;
public function Main():void
{
if(stage) {
init();
} else {
this.addEventListener(Event.ADDED_TO_STAGE,init);
}
}
private function init(evt:Event = null):void
{
this.removeEventListener(Event.ADDED_TO_STAGE,init);
//object is now definitely on the display tree
}
}
I'm gonna take a wild stab in the dark here.
stage is a property implemented something like so:
public function get stage():Stage {
var s:DisplayObject = this;
while(s.parent) s = s.parent;
return s as Stage;
}
root is very similar but stops a level below stage (root is a child of stage).
These properties only work when the object you're calling them on is on the stage somewhere. Doesn't matter where, because the while loop will walk up the hierarchy to get to the stage node at the top. But if it's not on the stage, then parent will be null.
So if your movieclip is not on the stage, then its reference to stage will be null. Same goes for root.
I'm guessing that you're calling blah before the bullets are added to the stage? In which case your call stage.addChild(someSprite) will be a Null Reference error (stage is null).
So you either need to add the bullets to stage first, or you need to pass stage in as a parameter:
function blah(s:Stage){
var someSprite = new someSprite();
s.addChild(someSprite);
}

Is it possible to remove an eventListener temporarily (i.e. until a function has been executed)

I am doing actionscript 3 at the moment and was wondering if it was possible to remove and eventListner temporarily. I know of removeEventListener, however, that removes the eventListener completely and i cannot click the button again.
If you want some more details, here the exact problem. I have function that when a button is pressed, an object appears. In the function which makes this object there is an eventListener which leads to a function which allows the user to press that object. When you press that object it disappear and the button will animate. However, since the original eventListener still exists, you can press the object while in motion and create a new object. So to the point: What i want to do is disable the eventListener while the button is moving, and reactivate it when it stops.
The best way is to simply use a flag which tells the function if the animation is complete or not. Here's an example of what I'm talking about using TweenLite as tween library:
public class CreateButton extends Sprite{
private var animating:Boolean = false;
public function CreateButton(){
this.addEventListener(MouseEvent.CLICK, onClick, false, 0, true);
}
private function onClick(event:MouseEvent):void{
if(this.animating == false){
// Trigger creation functionality
TweenLite.to(this, 0.5, {/* Parameters for the actual animation */ onComplete:animationComplete});
this.animating = true;
}
}
private function animationComplete():void{
this.animating = false;
}
}
It is best practice to remove the listener if it's functionality is to be disabled. You could however set the .mouseEnabled to false if you want to disable it's click functionality without removing the listener.

Is it possible to load a non-Document Class before preloader starts?

public class Framework extends MovieClip
{
var _loadingSystem:LoadingSystem;
public function Framework()
{
_loadingSystem = new LoadingSystem(this);
loaderInfo.addEventListener(ProgressEvent.PROGRESS,progressHandler);
loaderInfo.addEventListener(Event.COMPLETE, completeListener);
}
...
public class LoadingSystem extends MovieClip
{
public function LoadingSystem(parent:DisplayObjectContainer)
{
parent.addChild(this);
myLogo.buttonMode = true;
myLogo.addEventListener(MouseEvent.CLICK, gotoMySite);
}
As you can see, Framework is my Doc class which is creating _loadingSystem which is basically a movieclip that contains the preloader graphics. When I debug I get the following error "TypeError: Error #1009: Cannot access a property or method of a null object reference." pointing to myLogo.buttonMode = true;
From what I understand this is due to LoadingSystem not being fully loaded before being created in Framework. Is there any way for me to make this work? I have tried adding listeners for Event.ADDED but it didn't work.
Additional info: 3-frame FLA, first empty with a stop, second holding an AssetHolder movieclip, third for the application. I have export on 2nd frame set in publishing settings, all checkboxes for export on 2nd frame unchecked in the assets, and this all worked before I changed the export on 2nd frame setting except it wasn't preloading 50% of the file.
i think what's happening is this:
A document class is ALWAYS loaded in the first frame, because it represents your swf root class and thus has to be there in the first frame. Now, since you export all the other classes to frame 2, i would imagine, that LoadingSystem is existing only beginning with frame two, but you try to instantiate it in the constructor of your document class Framework.
What you could try out is, create a method "initialize" in Framework and call that from the timeline in frame 2. And in that method you would do the stuff, you currently do in the constructor of Framework.
if myLogo is a sprite/movieclip on the stage, it wont exist until LoadingSystem is added to the stage.
Now your first reaction should be "but I added it to the stage with parent.addChild(this)!". What you didn't take into account is that the document class isn't on the stage when the constructor is called. Flash basically executes like this:
docClass = new DocumentClass();
stage.addChild(docClass);
Which means that the stage property of the document class will be null until after the constructor is finished. It also means that any children added during the constructor wont have access to the stage or objects located on the stage until after the docClass is added to the stage.
There is a simple fix; listen for the ADDED_TO_STAGE event.
public function LoadingSystem(parent:DisplayObjectContainer)
{
parent.addChild(this);
addEventListener(Event.ADDED_TO_STAGE, initialize);
}
private function initialize(e:Event):void
{
removeEventListener(Event.ADDED_TO_STAGE, initialize);
addEventListener(Event.REMOVED_FROM_STAGE, uninitialize);
//attach stage listeners etc
myLogo.buttonMode = true;
myLogo.addEventListener(MouseEvent.CLICK, gotoMySite);
}
private function uninitialize(e:Event):void
{
removeEventListener(Event.REMOVED_FROM_STAGE, uninitialize);
addEventListener(Event.ADDED_TO_STAGE, initialize);
//detach stage listeners etc.
}