AS3: Access my Main.as from a movieclip on stage - actionscript-3

Using Adobe Flash Professional CS6, AS3
Please let me know if I need to provide any more info
I am trying to set up a navigation menu and my question is, "How do I get a MovieClip to call a function in my Main.as file from the Stage?" There is only one frame (I do not want more) and the different menu screens, which are just MovieClips, are added to the stage with AS when needed. I have a public function in the file Main.as called _About(), that my MovieClip, "MenuScreen", cannot access. I can successfully have _Menu() add a MovieClip to the stage with eventListeners, but when the mc makes the call back to change screens I get this error:
TypeError: Error #1034: Type Coercion failed: cannot convert flash.display::Stage#51ca0d1 to flash.display.MovieClip. at MenuScreen/clickAbout()[MenuScreen::frame1:32]
Frame1:32's code is:
MovieClip(parent)._About();
Line 51 in my Main.as is:
public function _About():void
{
trace("The About Function");
}
Below I have detailed more about Main.as with most of the fat trimmed.
package
{
import stuff
public class Main extends MovieClip
{
//Load the screen MCs onto the stage
public var _menu:MenuScreen = new MenuScreen();
public var _about:AboutScreen = new AboutScreen();
public var isMenu:Boolean = true;
public var isAbout:Boolean = false;
public function Main()
{
_Menu();
}
public function _Menu():void
{
isMenu = true;
stage.addChild(_menu);
}
public function _About():void
{
trace("The About Function");
}

An easy solution to your problem would be to add the menu items not to the stage! Instead add them to your main class. This way the parent of your items is instead main.as
But then you need to cast the parent to Main
Main(parent)._About();
Also not very nice. The items should not now what is behind them.
The best way is to do it, is to dispatch events from the different screens.
Means: you create your screen objects an there are dispatching custom events when a screnn change should happen.
dispatchEvent(new Event("showAbout"));
in your main class you handle the events like:
public function Main()
{
_Menu();
_menu = new MenuScreen();
_menu.addEventHandler("showAbout", showAboutHandler);
}
public function showAboutHanlder(e:Event):void
{
_About();
}
Even more better is a custom event with a screen identifier as a param. This way you just add one handler and decide in the handler code which screen to be displayed.
With the event handling in place, your menu items have no direct connection to the main. Also the main needs no further information about the screen classes.

Set static property :
public static var instance:Main;
public function Main(){
instance = this;
_Menu();
}
and then from anywhere You can use code :
Main.instance._About();

It would help to see MenuScreen's complete class, but here's what's probably going on:
You're instantiating at public var _menu:MenuScreen = new MenuScreen(); and when this happens it's probably making the call to MovieClip(parent)._About() before you've added MenuScreen to the stage at stage.addChild(_menu); - It doesn't have a parent yet when this occurs and the error incurs.
Two ways to get around this:
Add a required parameter in MenuScreen's constructor that references your Main class. Your constructor in MenuScreen would start with public function MenuScreen($main:Main){ and when you instantiate MenuScreen from Main class you would write public var _menu:MenuScreen = new MenuScreen(this); now you can use $main._About() in MenuScreen's constuctor.
In MenuScreen's constructor add a listener that checks when it's been added to the stage: addEventListener(Event.ADDED_TO_STAGE, addedStage). In the function addedStage you can properly call for a parent or the stage, and MovieClip(parent)._About() will probably work here.

Related

Eventlisteners Not Working On Main Sprite AS3

I have been looking around at a few articles and SO questions to get a grasp on what exactly I am experiencing with my project, but have not found an answer.
I am creating a main sprite that acts as my root for this particular project (I am using terminology as I currently understand it. If I am in error please correct me). In that sprite class, I add a MouseEvent listener so that I can operate in some way with the mouse. The problem is, the main sprite acts as if it either can not read my mouse events or it has no size and therefore can not register mouse events. Here are my classes and the tests I have preformed.
============
Main Class =
============
package
{
import flash.display.Sprite;
import Fu;
[SWF(backgroundColor = "0xffffff", width = "550", height = "400")]
public class Main extends Fu
{
public function Main():void
{
super();
addMouseListener();
}
}
}
==========
Fu Class =
==========
package
{
import flash.display.Sprite;
import flash.events.Event;
import flash.events.MouseEvent;
public class Fu extends Sprite
{
private var display_height:uint;
private var display_width:uint;
private var default_background_color:uint;
public function Fu(display_height:uint = 400, display_width:uint = 550, default_background_color:uint = 0x2e2e2e)
{
this.display_height = display_height;
this.display_width = display_width;
this.default_background_color = default_background_color;
if (stage) {
init();
}
else {
addEventListener(Event.ADDED_TO_STAGE, init);
}
}
private function init(e:Event = null):void {
removeEventListener(Event.ADDED_TO_STAGE, init);
graphics.beginFill(default_background_color, 1);
graphics.drawRect(0, 0, display_width, display_height);
}
public function addMouseListener():void {
trace("Added Mouse Listeners");
addEventListener(MouseEvent.CLICK, onMouseEvent);
// Also tried MouseEvent.MOUSE_OVER, MouseEvent.ROLL_OVER
// with no results either.
}
private function onMouseEvent(e:Event):void {
trace("Mouse Event Fired.");
}
}
}
Everything compiles ok and runs just fine until I try to activate a MouseEvent such as a click, in which case, nothing is output to the console.
================
Console Output =
================
Added Mouse Listeners
//Event messages should appear here...
If I then add a second sprite (s) to the Main class with the following code...
===========================
Added to Main constructor =
===========================
var s:Sprite = new Sprite();
s.name = "s";
s.graphics.beginFill(0x000000);
s.graphics.drawRect(0, 0, 200, 200);
addChild(s);
and click on s I get the "Mouse Event Fired." in the console window, but not when I click outside of s' 200x200 box.
I also tried adding the listener to the stage (which is not ideally what I want since I want these listeners to only affect the main sprite of this specific SWF file) and tracing out...
trace(e.target, " ", (e.target as DisplayObject).name);
The areas not covered by sprite s display "[object Stage] null" in the console and "[object Sprite] s" is displayed when s is clicked.
An obvious fix would be to use s as my "stage" and call it a day, but I an curious why event listeners do not work on the main sprite and why it does not have a default instance name like other sprites do.
Any ideas?
You document class cannot extend Fu since Fu require two parameters and a document class cannot have one. Besides it makes no sense for a document class to extend anything other than Sprite or MovieClip since there cannot be any object higher in the hierarchy of your app. But if you have to extend a custom super class that super class cannot take parameter. Instead create a separate init method that you call explicitly yourself to register your events and draw your content.
Document classes are a special case. When used as the main entry in your app (as opposed to an externally loaded swf), they are set to not receive interactive event directly and behave much like a Singleton. They are also set as the root of your app and are the only DisplayObject that has the stage directly available in constructor. The design idea is that it should be a container for your app and not a part of it directly. The hierarchy is stage -> root (document class) -> your app elements.
In your case for example if you were to add a Fu instance as a child of the document class instance then it will receive mouseevent normally and the document class would also catch those event as they propagate.

AS3 - How to get a reference to the container of an aggregated object?

Simple enough.
If I have a container class that holds a Sprite object, and I attach a touch listener to said Sprite, is there a reliable and cheap method of getting the object that contains the Sprite when it is touched? I realize I could just inherit the Sprite, but that is not what I want to do.
Failing that, if I add the event listener to said Sprite object within the class that contains it, is there a way to dispatch an event that would allow me to get the reference to the container that holds the Sprite object that was touched?
Thanks for any help.
Reply to loxxxy:
When I said "held", I meant in terms of aggregation. For example:
public class Container
{
[Embed(source = "img1.jpg")] private var img:Class;
private var sprite:Sprite;
private var bitmap:Bitmap;
public function Container()
{
bitmap = new img();
sprite = new Sprite();
sprite.addChild(bitmap);
}
public function GetSprite():Sprite
{
return sprite;
}
}
Which is perfectly legal code. What I wanted to do was, when the Sprite object is touched outside of the Container class, that I could access other properties within the Container class through said Sprite object. However, a solid workaround would be something like the following, I think:
public class Container extends InteractiveDisplayObject
{
[Embed(source = "img1.jpg")] private var img:Class;
private var bitmap:Bitmap;
public function Container()
{
bitmap = new img();
this.addChild(bitmap);
}
}
Then, I could access the aggregate objects of the Container class by listening to touch events on the Container class, while making it fully extendable to any other DisplayObject class (TextField, Sprite, etc.).
There's a very specific reason I want to do this, I just don't feel it's relevant to the actual question. I'll try this approach when I get some time to test it out, and see how it goes. Thanks!
You don't really need to dispatch events just for this purpose.
Add the event listener to the container & you can get reference to both container & sprite. For eg:
container.addEventListener(MouseEvent.CLICK, container_touched, false, 0, true);
function container_touched(e) {
trace(e.target.name); // Output : sprite
trace(e.currentTarget.name); // Output : container
}
EDIT :
Or you could have rather exposed the sprite event to others by adding a function like :
public function registerCallback( callback:Function) {
var thisRef = this;
sprite.addEventListener(MouseEvent.CLICK, function(e) {
callback(thisRef);
},false, 0, true);
}

AS3: Loading swf as Custom Class that extends MovieClip - getting null object reference

I followed the example from a previous question and I am loading an external swf using a loader and inside the loader event handler I am trying to cast the loader.content as my custom class PanelReferenceClip which extends MovieClip
When I publish I receive a this error:
TypeError: Error #1009: Cannot access a property or method of a null object reference.
Just to make sure and test that the swf location was correct and the swf was actually being loaded, I changed the type of the content to as MovieClip and it worked fine.
EDIT: I also wanted to add that these swfs are being stored locally and not being pulled across the internet, multiple networks or servers.
I am not sure if I did something quirky in my class so I am providing the source to my custom class PanelReferenceClip
package com.components
{
import com.UI.DevicePanel;
import flash.display.MovieClip;
/**
* ...
*
* used to store the loaded swf inside the panel
*
* *parentPanel is set so that it is able to access it's parent Panel when needing
* to set parameters.
*/
public class PanelReferenceClip extends MovieClip
{
private var _parentPanel:DevicePanel;
private var _bg_mc:MovieClip;
private var _oldY:Number = 0;
private var _oldX:Number = 0;
private var _IsDragging:Boolean = false;
public function PanelReferenceClip() {
super();
}
/*--------------------------------------------------------------------------
* GETTERS AND SETTERS
* -----------------------------------------------------------------------*/
public function set parentPanel(p:DevicePanel):void {
_parentPanel = p;
}
public function get parentPanel():DevicePanel {
return _parentPanel;
}
public function get bg_mc():MovieClip {
try {
return getChildByName("bg_mc") as MovieClip;
} catch (e:Error) {
trace("could not find bg_mc in " + _parentPanel.DeviceName + " panel");
}
return null;
}
public function set oldY(n:Number):void {
_oldY = n;
}
public function get oldY():Number {
return _oldY;
}
public function set oldX(n:Number):void {
_oldX = n;
}
public function get oldX():Number {
return _oldX;
}
public function set IsDragging(b:Boolean):void {
_IsDragging = b;
}
public function get IsDragging():Boolean {
return _IsDragging;
}
}
}
Here is the part of another class that is loading the swfs and then trying to assign them as the class prop _reference which is of type PanelReferenceClip . I am doing this so I am able to get ahold of the swf and it's children because when you import a swf you do not get to set the instance name of the imported swf. So I am assigning it a custom class that extends MovieClip so I can store have some custom properties.
private function handleLoad(e:Event):void
{
e.target.removeEventListener(Event.COMPLETE, handleLoad, false);
// keep reference to the content
_reference = e.target.content as PanelReferenceClip;
// ** BREAKS ON THE NEXT LINE **/
trace(_reference.numChildren);
// add loader to the display list so we can see the external SWF.
addChild(e.target.loader);
// signal the sim engine that the swf has loaded
// and to go ahead and wire up the components
dispatchEvent(new DataEvent(DataEvent.COMPLETE));
initPanel();
}
Here is the method used to load the swf. I added in the application context part to try it out but I am still not getting anywhere.
public function loadSWF(theSWF:String):void
{
var url:String = theSWF;
var urlReq:URLRequest = new URLRequest(url);
_urlError = url;
_loader.contentLoaderInfo.addEventListener(Event.COMPLETE, handleLoad);
_loader.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, ioErrorHandler);
var context:LoaderContext = new LoaderContext(false, ApplicationDomain.currentDomain);
_loader.load(urlReq,context);
_loader.mouseEnabled = false;
}
This might be caused by converting between types that can't be typecast. Instead of an error occurring when performing someVariable as OtherClass, the variable will just become null. (Example Shown)
I would store a reference to the original movieclip as one of the properties in PanelReferenceClip, and just use that reference when you need to access things like .numChildren
//MovieClips are subclasses of Sprites... this conversion works
var originalMC:MovieClip = new MovieClip();
var sprite:Sprite = originalMC as Sprite;
trace("sprite: " + sprite); //defined
//Sprites are not subclasses of MovieClips... this conversion wont work
var originalSprite:Sprite = new Sprite();
var mc:MovieClip = originalSprite as MovieClip;
trace("mc: " + mc); //null
//MovieClips are not subclasses of PanelReferenceClips (quite the opposite)
//this conversion wont work, just as the one before
var panelRef:PanelReferenceClip = originalMC as PanelReferenceClip;
trace("panelRef: " + panelRef); //null
As mentioned by #nox-noctis, the problem may be due to the applicationDomain. I answered a similar question here.
The underlying cause of the problem is that there are actually two different classes defined for PanelReferenceClip. Even though they have they may have same name, and contain the same code, Flash sees them as two different objects.
If you give the classes two different names, this will more clearly convey how the classes appear to Flash. In essence you are telling Flash to cast one object type to a different type, eg:
var foo:Foo = new Foo();
var bar:Bar = foo as Bar(); // where Bar does not inherit Foo
What would work though, is to case the panel as a MovieClip, since the panel does extend MovieClip. That would let you add it to the display list, but won't provide an API to the panel's implementation.
One solution is to specify an interface for the classes. This tells Flash that even though the code may be different, the two classes can be used in the same way. This also has the advantage that you only need to compile the interface into the parent SWF, and not the whole class, reducing the file size of the parent.
An interface for the panel might look like this:
public interface IPanelReference
{
function set parentPanel(p:IDevicePanel):void;
function get parentPanel():IDevicePanel;
function get bg_mc():MovieClip;
function set oldY(n:Number):void;
function get oldY():Number;
function set oldX(n:Number):void;
function get oldX():Number;
function set IsDragging(b:Boolean):void;
function get IsDragging():Boolean;
}
Apply the interface to the panel like this:
public class PanelReferenceClip extends MovieClip implements IPanelReference
{
...
}
The parent would then reference the loaded class by the interface and known ancestors:
private function handleLoad(e:Event):void
{
...
_reference = e.target.content as IPanelReference;
trace((_reference as DisplayObjectContainer).numChildren);
trace(_reference.oldX);
addChild(_reference as DisplayObject);
....
}
The facts provided are not conclusive, as it is very important where is the loader SWF and from where does it load external SWFs.
My guess: your loaded content resides in a different ApplicationDomain. See LoaderContext.applicationDomain and second parameter to Loader.load() method.
Some details on the mechanics. Apparently, you compile PanelReferenceClip class into two different SWF files. When you load some external SWF file with code in it, VM decides whether to mix or not any incoming declarations with those of loader SWF depending on loader context you provide. If the context you specified allows mixing, then incoming SWF uses the same declarations with coinciding qualified names that parent SWF has. If not -- the classes being initialized are different even if their fully qualified names are identical. In the latter case you will not be able to cast loaded content to what you like.
Try the following tests in handleLoad() method:
trace(getQualifiedClassName(e.target.content)); // This is the incoming declaration
trace(getQualifiedClassName(PanelReferenceClip)); // This is the parent declaration
Even if the output is identical, that doesn't necessarily mean, that the classes are the same. If you are using some kind of debugger, you may look at the memory addresses in the following test lines.
var incomingClass:Class = e.target.content["constructor"];
var residentClass:Class = PanelReferenceClip;
trace(incomingClass == residentClass); // toggle breakpoint here and compare memory addresses

Controlling objects of another class that are already on the stage

I'm quite ashemed to ask this question here because I'm sure that I'm missing something very basic. I'm not even sure what should be the correct title for this question.
Let's say that I've a button object (instance of Flip) and a coin object (instance of Coin) on the stage. The coin object has two frames: one showing Heads and one for Tails.
MyCoin class is as following:
package
{
import flash.display.MovieClip;
public class Coin extends MovieClip
{
protected var _coinFace:uint;
public function Coin()
{
stop();
}
public function get coinFace():uint {
return _coinFace;
}
public function set coinFace(value:uint):void {
_coinFace = value;
}
public function show():void {
gotoAndStop(_coinFace);
}
}
}
Objective: When user clicks the button, the coin should flip and show a random coinFace. I've added an eventListener to the Flip class as follows:
public function Flip()
{
this.addEventListener(MouseEvent.CLICK, onMouseClick);
}
Problem: How do I reach the coin object on the screen via onMouseClick function? Let's say that the object on the stage has instance name of myCoin. I suppose that had I not done this with an external class and simply used actions from the frame I could just use the instance name as a variable. I couldn't figure to do the same it in an external class. Do I first create the object which is already on the stage?
Where you create the instance of each, the flip object needs to be passed an instance of the coin object.
var myCoin:Coin = new Coin();
var myFlip:Flip = new Flip(myCoin);
Then inside the Flip class:
private var _coin:Coin;
public function Flip(coin:Coin) {
_coin = coin;
this.addEventListener(MouseEvent.CLICK, onMouseClick);
}
private function onMouseClick(e:MouseEvent):void {
_coin.gotoAndStop(2); // Or what ever needs to be done to the coin on click
}
Alternatively, depending on the complexity of the overall structure, you can a build a control class that acts as a link between the two.

AS3 - passing the main timeline/stage into an external class

Suppose I have the following class
package {
import flash.display.Stage;
public class CustomObject {
private var stage:Stage;
public function CustomObject(stageRef:Stage) {
// stage access through
// constructor argument
stage = stageRef;
}
}
}
Which is not a document class. I wish to pass the stage of the main timeline into the class, say on frame 1
stop();
var c:CustomObject = new CustomObject(this.stage);
Is this the right way of passing the stage of the main timeline into another class?
That will work perfectly well - but if your custom class is extending a display object of any kind (Sprite, MovieClip, etc) it will have it's own stage property which is populated automatically if your object is in the display tree. I believe this would also mean your private variable would result in a compiler error.