Get composed MovieClip's containing (parent) class after event - actionscript-3

I have a MovieClip that is composed in a parent (non-display object) class. We register an event listener against that movieclip - a CLICK handler for example.
With event.target I can get a reference to the MovieClip from within the event handler. But how can I pull a reference to its composing class?
I could simply add a "parentClass" property on the dynamic MovieClip class, but I'm wondering if there's a more elegant/idiomatic way of doing it that I should consider?

If the class that creates your MovieClip is not a display object then it cannot really be considered its parent. The parent element will be that which your MovieClip is attached to. All that the creating class contains is a reference to an object, just the same as if you then refer to the MovieClip elsewhere.
My preferred way would be to create a descendant class of MovieClip that can contain a reference to the creating class, which you would then use instead of MovieClip.
package {
import flash.display.MovieClip;
public class MovieClipWithRef extends MovieClip
{
private var _parentRef:Object; //obviously cast this as accurately as possible
public function MovieClipWithRef($ref:Object):void
{
_parentRef = $ref;
}
public function get parentRef():Object
{
return _parentRef;
}
//no setter makes this property read-only once set by the constructor
}
}

This answer is essentially the same as shanethehat's answer but also incorporates the composite design pattern. Look at the following example:
ICompositeObject.as:
package com.flashdevelopprojects.patterns.composite
{
public interface ICompositeObject
{
function get parentObject():Object
}// end interface
}// end package
CompositeObject.as:
package com.flashdevelopprojects.patterns.composite
{
public class CompositeObject implements ICompositeObject
{
private var _parentObject:Object;
public function get parentObject():Object { return _parentObject }
public function CompositeObject(parentObject:Object)
{
_parentObject = parentObject;
}// end function
}// end class
}// end package
Main.as(document class):
package
{
import com.flashdevelopprojects.patterns.composite.CompositeObject;
import com.flashdevelopprojects.patterns.composite.ICompositeObject;
import flash.display.Sprite;
import flash.events.Event;
public class Main extends Sprite
{
public function Main():void
{
if (stage) init();
else addEventListener(Event.ADDED_TO_STAGE, init);
}// end function
private function init(e:Event = null):void
{
removeEventListener(Event.ADDED_TO_STAGE, init);
var a:A = new A();
a.b.addEventListener(Event.ADDED_TO_STAGE, onBAddedToStage);
addChild(a.b);
}// end function
public function onBAddedToStage(e:Event):void
{
var b:ICompositeObject = ICompositeObject(e.target);
trace(b.parentObject); // output: A
}// end function
}// end class
}// end package
import com.flashdevelopprojects.patterns.composite.CompositeObject;
import com.flashdevelopprojects.patterns.composite.ICompositeObject;
import flash.display.MovieClip;
internal class A
{
private var _b:B;
public function get b():B { return _b }
public function A()
{
_b = new B(this);
}// end function
public function toString():String { return "A" }
}// end class
internal class B extends MovieClip implements ICompositeObject
{
private var _compositeObject:CompositeObject;
public function get parentObject():Object { return _compositeObject.parentObject }
public function B(parentObject:Object)
{
_compositeObject = new CompositeObject(parentObject);
}// end function
}// end class

Related

Read a variable from a different Class in Actionscript 3

I have already set a variable in my document class "Main.as". I am now trying to access that variable and read its value from a different Class and Function, take that value and email it.
For example in my "Main.as" file I have this function:
public var _myVar:String;
function create() {
_myVar = "hello";
}
Now from my other class "EmailtoFriend.as" I have a new function to try and get the value of that pre set variable:
function getVar() {
trace(_myVar);
}
Why will it not output "hello"? Instead I get an error saying: Access of undefined property _myVar. If I could just get this simple example working, I think it will help me understand a lot of things. Thanks!
The error your getting really says it all. Although _myVar is defined in your Main class public var _myVar:String;, it isn't defined in your Emailtofriend class. If you want access to _myVar you need to do one of the following:
Parse a reference of your Main object(using this) to your EmailToFriend class:
Main.as(document class):
package
{
import flash.display.Sprite;
import flash.events.Event;
public class Main extends Sprite
{
public var _myVar:String;
public function Main():void
{
if (stage) init();
else addEventListener(Event.ADDED_TO_STAGE, init);
}// end function
public function create():void
{
_myVar = "hello";
}// end function
private function init(e:Event = null):void
{
removeEventListener(Event.ADDED_TO_STAGE, init);
create();
var emailToFriend:EmailToFriend = new EmailToFriend(this);
emailToFriend.getVar();
}// end function
}// end class
}// end package
internal class EmailToFriend
{
private var _main:Main;
public function EmailToFriend(main:Main)
{
_main = main;
}// end function
public function getVar():void
{
trace(_main._myVar);
}// end function
}// end class
Or to make _myVar a public static property of Main and access it via Main._myVar:
Main.as(document class):
package
{
import flash.display.Sprite;
import flash.events.Event;
public class Main extends Sprite
{
public static var _myVar:String;
public function Main():void
{
if (stage) init();
else addEventListener(Event.ADDED_TO_STAGE, init);
}// end function
public function create():void
{
_myVar = "hello";
}// end function
private function init(e:Event = null):void
{
removeEventListener(Event.ADDED_TO_STAGE, init);
create();
var emailToFriend:EmailToFriend = new EmailToFriend();
emailToFriend.getVar();
}// end function
}// end class
}// end package
internal class EmailToFriend
{
public function EmailToFriend() {}
public function getVar():void
{
trace(Main._myVar);
}// end function
}// end class
Also one small thing, when using underscores for class properties, you should only use them for private properties, not public. Well I say only but I really mean it's more common.
[UPDATE]
This is in response to your comment:
Main.as:
package
{
import EmailToFriend;
import flash.display.Sprite;
import flash.events.Event;
public class Main extends Sprite
{
public static var _myVar:String;
public function Main():void
{
if (stage) init();
else addEventListener(Event.ADDED_TO_STAGE, init);
}// end function
public function create():void
{
_myVar = "hello";
}// end function
private function init(e:Event = null):void
{
removeEventListener(Event.ADDED_TO_STAGE, init);
create();
var emailToFriend:EmailToFriend = new EmailToFriend();
emailToFriend.getVar();
}// end function
}// end class
}// end package
EmailToFriend.as:
package
{
import Main;
public class EmailToFriend
{
public function EmailToFriend() {}
public function getVar():void
{
trace(Main._myVar);
}// end function
}// end class
}// end package
All variables implicitly have a target, unless explicitly specified. Variables without an explicit target will commonly look in the local scope of the function (in this case, getVar()) and the global scope of the class (in this case, EmailToFriend).
I assume that these don't exist in your code, judging by the error. You will need something like the following to access the var:
function getVar() {
var main:Main = new Main();
main.create();
trace(main._myVar);
}
Assuming Main.as is your document class:
public var _myVar:String;
public function create():String {
//we specify that this function will return a String using the :String in the above line
//now give the variable a value
_myVar = "hello";
//and send it back to the caller of the function
return _myVar;
}
Within your other class
function getVar():void {
//MovieClip(root) is another way of referencing your document class.
trace(MovieClip(root).create());
}
OR...
public var _myVar:String;
public function create():void {
//now give the variable a value
_myVar = "hello";
}
Within your other class
function getVar():void {
//call the function that actually gives myVar a value
MovieClip(root).create();
//now you can trace the value
trace(MovieClip(root)._myVar);
}
package {
import flash.display.MovieClip;
public class Main extends MovieClip {
public var _myVar:String;
public function Main(){
create();
}
private function create() {
_myVar = "hello";
}
}
}
}
in EmailtoFriend.a
import Main.as
var myMain = new Main();
trace(myMain._myVar);
What you should do is have a OOP approach, meaning use encapsulation in your classes. If you don;t know what that means, its ok. For instance, if you have a variable, that you want to be accessible, then you should really make it private, and the set up its own public function that returns the variable. Like this:
package {
public class SomeClass {
private var someVar:Number = 12; // A private variable, which means only this class can
// use the reference someVar, and only other outiside classes can use the function getSomeVar.
... // skip some regular class stuff
public function getSomeVar():Number {
return this.someVar; //returns the variable someVar from this class to whoever is accessing it.
//This function is public which means that anyone can call it and get the variable someVar.
}
}
}
To access that variable, you just reference a class instance:
var someClass:SomeClass = new SomeClass(); // create the instance using the variable someClass
var myVar:Number = someClass.getSomeVar(); // ACCESSES the variable that you want from the class,
//by first using the class instance reference, and then calling its public function that returns the value you want.

AS3 Cannot access stage from custom class

How can I access the stage and especially the width and mouse position of the flash Movie from a custom class?
package classes
{
import flash.events.*;
import flash.display.*;
public class TableManager extends Sprite
{
public function TableManager() {
sayStage();
}
public function sayStage():void
{
trace(stage);
}
}
}
This will only return nill. I know that DisplayObjects don't have any stage until they have been initiated so you can't access the stage in your constructor but even if I call sayStage() later as an instance method it won't work.
What am I doing wrong?
If TableManager is on the stage you can access the stage with this.stage.
The trick is you have to wait for the instance to be added to the stage. You can listen for the ADDED_TO_STAGE event so you know when that's happened.
package classes {
import flash.events.*;
import flash.display.*;
public class TableManager extends Sprite {
public function TableManager() {
this.addEventListener(Event.ADDED_TO_STAGE, onAddedToStage);
}
private function onAddedToStage(e:Event):void {
this.removeEventListener(Event.ADDED_TO_STAGE, onAddedToStage);
sayStage();
}
public function sayStage():void {
trace(this.stage);
}
}
}
The most defensive way to write this is:
public function TableManager() {
if(this.stage) init();
else this.addEventListener(Event.ADDED_TO_STAGE, init);
}
private function init(e:Event = null):void {
if(e != null) this.removeEventListener(Event.ADDED_TO_STAGE, init);
sayStage();
}
If the object is already on the stage at the time of initialization, then immediately call the init function with no arguments. If not wait until its been added to the stage. Then when the init function gets called, if it was called as the result of an event, then detach the event handler, and move along.
You can pass a reference of the root movieclip (i.e. the stage) to your custom class.
e.g.
package classes
{
import flash.events.*;
import flash.display.*;
public class TableManager extends Sprite
{
private var _rootMC:MovieClip;
public function TableManager(rootMC:MovieClip) {
_rootMC = rootMC;
sayStage();
}
public function sayStage():void
{
trace(_rootMC.stage);
trace(_rootMC.stage.stageWidth);
}
}
}
Then when instantiating your instance of TableManager from the root timeline:
//the keyword 'this' is the root movieclip.
var newTM:TableManager = new TableManager(this);
stage will be null as long as the Sprite hasn't been added to the display list - it's nothing to do with initiation. E.g.
var t:TableManager = new TableManager;
stage.addChild( t ); // or your root class, or any class that's already on the displaylist
trace( t.stage ); // [Stage stage]
t.parent.removeChild( t );
trace( t.stage ); // null
As #crooksy88 suggests, either pass in the stage to the constructor, or keep it as a static somewhere, say your main document class, so that you can access it everywhere.
i think usefull for You should be create static reference to stage :
in Your main class add line and set stage :
public static var stage:Stage;
...
public function Main():void { // constructor
Main.stage = stage;
...
and than in custom class :
public function sayStage():void
{
trace(Main.stage);
trace(Main.stage.stageWidth);
}
you may access this.stage when the current object(also a sprite) is already attached to the stage.
public class TableManager extends Sprite{
public function TableManager()
{
}
public function sayStage():void
{
trace(stage);
}
}
TableManager tm=new TableManager();
//tm.sayStage(); // no
addChild(tm);
tm.sayStage(); // fine
hope this could help
here is a pretty good solution you only need to reference the stage inside your class you just pass it as a simple object, here how to do that
package {
public class Eventhndl{
private var obj:Object;
public function Eventhndl(objStage:Object):void{
obj = objStage;
trace(obj); // now the obj variable is a reference to the stage and you can work as normal you do with stage (addChild, Events Etc..)
}
}
this is how you make instance to run it, i have used the constructor method but you can change it to any function as you wish and call it whenever you need it.
import Eventhndl;
var EH:Eventhndl = new Eventhndl(stage);
here is some few Examples how to access stage from class
https://stackoverflow.com/a/40691908/1640362
https://stackoverflow.com/a/40691325/1640362

Actionscrpipt 3 OOP

I am developing player which has several my own custom developed buttons which has their own classes. Also the player has its own class which is the main class and instansiate all the buttons I have.
So this is a simple example of "has a" relationship i.e composition.
I used to past a reference of the player trought every buttons constructor in order buttons to be able to access properties and methods from the players class. This method is working good, but the problem is it has a lot of duplicate code than needs to be added to every button class.
So I tried to workit out by using inheritance, i.e buttons extend the playes main class.
But this way although i declare all properties protected I get the final swf blank white. So there must a problem.
Am I doing the structure wrong or what? Any idea?
Here is a sample code
public class Player extends MovieClip{
protected var prop1:Number;
protected var prop2:String;
protected var playButton:PlayButton;
....
public function Player(){
// with composition
playButton=new PlayBUtton(this);
// with inhgeritance
playButton=new PlayButton();
addChild(playBUtton);
}
}
//first implementation with composition
public class PlayButton extends MovieCLip{
public function PlayButton(player:Player){
//access Player trough player parameter
}
}
//second implementation with inheritance
public class PlayButton extends Player{
public function PlayButton(){
//access Player trough inheritance
}
}
You could use the state design pattern like in the following example I made:
package
{
import flash.display.Sprite;
import flash.events.Event;
public class Main extends Sprite
{
public function Main():void
{
if (stage) init();
else addEventListener(Event.ADDED_TO_STAGE, init);
}// end function
private function init(e:Event = null):void
{
removeEventListener(Event.ADDED_TO_STAGE, init);
var player:Player = new Player();
addChild(player);
}// end function
}// end class
}// end package
import flash.display.Sprite;
import flash.events.MouseEvent;
import flash.text.TextField;
internal class TextButton extends Sprite
{
public function TextButton(text:String)
{
graphics.beginFill(0xFF0000);
graphics.drawRect(0, 0, 100, 25);
graphics.endFill();
var textField:TextField = new TextField();
textField.text = text;
textField.mouseEnabled = false;
textField.x = this.width / 2 - textField.textWidth /2;
textField.y = this.height / 2 - textField.textHeight / 2;
addChild(textField);
}// end function
}// end class
internal interface IState
{
function play():void
function stop():void
}// end interface
internal class Player extends Sprite
{
private var _playState:IState;
private var _stopState:IState;
private var _state:IState;
private var _playButton:TextButton;
private var _stopButton:TextButton;
public function get playState():IState { return _playState }
public function get stopState():IState { return _stopState }
public function get state():IState { return _state }
public function set state(state:IState):void { _state = state }
public function Player()
{
_playState = new PlayState(this);
_stopState = new StopState(this);
_state = stopState;
_playButton = new TextButton("PLAY");
_playButton.addEventListener(MouseEvent.CLICK, onClick);
addChild(_playButton);
_stopButton = new TextButton("STOP");
_stopButton.addEventListener(MouseEvent.CLICK, onClick);
_stopButton.x = 110;
addChild(_stopButton);
}// end function
private function onClick(e:MouseEvent):void
{
var textButton:TextButton = e.target as TextButton;
switch(textButton)
{
case _playButton : _state.play(); break;
case _stopButton : _state.stop(); break;
}// end function
}// end function
}// end class
internal class PlayState implements IState
{
private var _player:Player;
public function PlayState(player:Player)
{
_player = player;
}// end function
public function play():void
{
trace("player already playing");
}// end class
public function stop():void
{
_player.state = _player.stopState;
trace("stopping player");
}// end function
}// end class
internal class StopState implements IState
{
private var _player:Player;
public function StopState(player:Player)
{
_player = player;
}// end function
public function play():void
{
_player.state = _player.playState;
trace("playing player");
}// end function
public function stop():void
{
trace("player already stopped");
}// end function
}// end class

How to manage loaded assets?

I'm working on an AS3 project that must load a lot of external files; images, sound clips, movie clips etc. The program first loads an XML file that contains the file names of all assets needed by each class. From that point on I'm not sure what would be the best way to distribute those assets.
I thought about making a dedicated assets class that will load everything the XML object describes, and then pass it to each class' constructor so they can access the objects they need. This seems like the "tidiest" option, but is there a best practice way for managing loaded assets?
You could create a class that handles loading the assets and storing them as bytes. Then you can get an asset from that class via a reference(e.g. name) and do what you need with it. I've created an example to demonstrate this:
package
{
import flash.display.Loader;
import flash.display.Sprite;
import flash.utils.ByteArray;
import flash.events.Event;
public class Main extends Sprite
{
private var _assets:Assets;
private var _counter:int;
private var _assetsXml:XML;
public function Main():void
{
if (stage) init();
else addEventListener(Event.ADDED_TO_STAGE, init);
}// end function
private function init(e:Event = null):void
{
removeEventListener(Event.ADDED_TO_STAGE, init);
_assetsXml = <assets>
<asset name="chrysanthemum" url="assets/chrysanthemum.jpg"></asset>
<asset name="kalimba" url="assets/kalimba.mp3"></asset>
<asset name="wildlife" url="assets/wildlife.wmv"></asset>
</assets>;
_assets = new Assets();
for each(var asset:XML in _assetsXml.children())
{
_assets.addEventListener(Event.COMPLETE, onAssetComplete);
_assets.load(asset.#name, asset.#url);
}// end for each
}// end function
private function onAssetComplete(e:Event):void
{
if (++_counter == _assetsXml.children().length()) addImage();
}// end function
private function addImage():void
{
var loader:Loader = new Loader()
loader.loadBytes(_assets.getAssetBytesByName("chrysanthemum"));
addChild(loader);
}// end function
}// end class
}// end package
import flash.events.Event;
import flash.events.EventDispatcher;
import flash.net.URLLoader;
import flash.net.URLLoaderDataFormat;
import flash.net.URLRequest;
import flash.utils.ByteArray;
internal class Assets extends EventDispatcher
{
private var _assetLoaders:Vector.<AssetLoader>;
public function Assets():void
{
_assetLoaders = new Vector.<AssetLoader>();
}// end class
public function load(name:String, url:String):void
{
var assetLoader:AssetLoader = new AssetLoader(name);
assetLoader.addEventListener(Event.COMPLETE, onAssetLoaderComplete);
assetLoader.load(url);
}// end function
public function getAssetBytesByName(name:String):ByteArray
{
for each(var assetLoader:AssetLoader in _assetLoaders)
{
if (assetLoader.name == name) break;
}// end for each
return assetLoader.bytes;
}// end function
private function onAssetLoaderComplete(e:Event):void
{
var assetLoader:AssetLoader = e.target as AssetLoader;
_assetLoaders.push(assetLoader);
dispatchEvent(new Event(Event.COMPLETE));
}// end function
}// end class
internal class AssetLoader extends EventDispatcher
{
private var _name:String;
private var _url:String;
private var _bytes:ByteArray;
public function get name():String { return _name }
public function get url():String { return _url };
public function get bytes():ByteArray { return _bytes };
public function AssetLoader(name:String):void
{
_name = name;
}// end function
public function load(url:String):void
{
_url = url;
var urlLoader:URLLoader = new URLLoader();
urlLoader.dataFormat = URLLoaderDataFormat.BINARY;
urlLoader.addEventListener(Event.COMPLETE, onUrlLoaderComplete);
urlLoader.load(new URLRequest(_url));
}// end function
private function onUrlLoaderComplete(e:Event):void
{
var urlLoader:URLLoader = e.target as URLLoader;
_bytes = ByteArray(urlLoader.data);
dispatchEvent(new Event(Event.COMPLETE));
}// end function
}// end class

Get the stage dimensions without using a Singleton?

I have a few classes in my project which aren't display objects, but they need to know about the stage of my project (stage.stageWidth, stage.stageHeight).
Is there a simple way to pass this information along to my classes without using a Singleton or passing these items in as parameters into the constructor??
You can store a reference to stage in a class's static property which can be accessed from any class in your application. The following is an example of this:
GlobalVars.as:
package
{
import flash.display.Stage;
public class GlobalVars
{
public static var stage:Stage;
}// end class
}// end package
Main.as:
package
{
import flash.display.Sprite;
public class Main extends Sprite
{
private var _stageAccessor:StageAccessor;
public function Main():void
{
init();
}// end function
public function init():void
{
GlobalVars.stage = stage;
_stageAccessor = new StageAccessor();
_stageAccessor.traceStageWidth(); // output:
_stageAccessor.traceStageHeight(); // output:
}// end function
}// end class
}// end package
StageAccessor.as:
package
{
import flash.display.Stage;
public class StageAccessor
{
public function StageAccessor():void {};
public function traceStageWidth():void
{
trace(GlobalVars.stage.stageWidth);
}// end function
public function traceStageHeight():void
{
trace(GlobalVars.stage.stageHeight);
}// end function
}// end class
}// end package
There is no way to do it without a singleton if you don't have a reference to a displayed graphic object.