as3 - addchild with drag and drop - actionscript-3

I am trying to add a child instance of an object to the stage, then allow the user to drag and drop this object (in this case, a movie clip) on the stage. However, I am getting the following error:
TypeError: Error #1009: Cannot access a property or method of a null object reference.
at working_copy_fla::MainTimeline/dragObject()
So, that is my first problem. Then second problem, is I have not found an answer as to how to make a child object (specifically, a movie clip) able to properly be dragged and dropped on the stage.
Here is my code:
// Allow buttons to bring objects to the stage
myButton.addEventListener(MouseEvent.CLICK, addImage);
function addImage(event:MouseEvent):void
{
var myImage:Image_mc = new Image_mc();
stage.addChild(myImage);
// Center the object
myImage.x = 300;
myImage.y = 300;
// Allow the object to be drag and dropped
myImage.addEventListener(MouseEvent.MOUSE_DOWN, startDragging);
myImage.addEventListener(MouseEvent.MOUSE_UP, stopDragging);
}
function startDragging(event:MouseEvent):void
{
event.target.x = event.target.parent.mouseX - event.target.mouseX
event.target.y = event.target.parent.mouseY - event.target.mouseY
stage.addEventListener(MouseEvent.MOUSE_MOVE, dragObject);
}
function dragObject(event:MouseEvent):void
{
event.target.x = event.target.parent.mouseX - event.target.mouseX
event.target.y = event.target.parent.mouseY - event.target.mouseY
}
function stopDragging(event:MouseEvent):void
{
stage.removeEventListener(MouseEvent.MOUSE_MOVE, dragObject);
}
EDIT
I figured it out, and the solution was as simple as looking to the sample code in Adobe Flash (using CS6). Here is my code now:
// Allow buttons to bring objects to the stage
myButton.addEventListener(MouseEvent.CLICK, addImage);
function addImage(event:MouseEvent):void
{
var myImage:Image_mc = new Image_mc();
stage.addChild(myImage);
// Center the object
myImage.x = 300;
myImage.y = 300;
// Allow the object to be dragged
myImage.addEventListener(MouseEvent.MOUSE_DOWN, clickToDrag);
}
function clickToDrag(event:MouseEvent):void
{
event.target.startDrag();
}
stage.addEventListener(MouseEvent.MOUSE_UP, releaseToDrop);
function releaseToDrop(event:MouseEvent):void
{
event.target.stopDrag();
}
The key here was that I have created universal functions (clickToDrag and releaseToDrop) that will accept input from any object (so I can re-use these functions with other images that I add to the stage). This code works with multiple children on the stage (all can be drag and dropped at any time).
The only problem I am having with it now is that I am getting this error whenever I spawn a child element (by clicking on the myButton button instance):
ReferenceError: Error #1069: Property stopDrag not found on flash.display.SimpleButton and there is no default value.
at working_copy_fla::MainTimeline/releaseToDrop()
This error is not stopping the application from working; everything still runs fine. But I would still like to figure out why this error is occuring. My guess is that whatever is using "stopDrag" (should just be a movie clip) is not capable of that method.

event.target.x = event.target.parent.mouseX - event.target.mouseX
The above code assumes that 'event.target' is the stage, right (because you added the listener to the stage)? So you're trying to change the x/y of the stage? No. The start/stopDragging should make a reference to the object being dragged, available as a private class variable, which is visible to the dragObject method. Also, what is the stage's parent ('event.target.parent.mouseX')? There is no parent to the stage. This is probably what the "null object reference" is refering to.
EDIT
I'm used to Object-Oriented AS3 (highly recommended), but I'm guessing that programming on the 'timeline' the following should work. Declare a variable outside of your functions, something like:
var objectCurrentDragging:DisplayObjectContainer;
Then in your 'addImage' function, use the following code to make objectCurrentDragging reference the object which you want to drag:
objectCurrentDragging = myImage;
Then in the dragObject function, simply reference objectCurrentDragging:
objectCurrentDragging.x = ...
Hope that works out for you.

So I have finally figured it out. The key was not to add an event to the stage, but rather, to add the "releaseToDrop" function to the *MouseEvent.MOUSE_UP* event on the child element in the same function that adds it to the stage. Now I get no more errors, and it works with multiple instances of the child objects (movie clips) on the stage.
Here is the code that works:
// Allow buttons to bring objects to the stage
myButton.addEventListener(MouseEvent.CLICK, addImage);
function addImage(event:MouseEvent):void
{
var myImage:Image_mc = new Image_mc();
stage.addChild(myImage);
// Center the object
myImage.x = 300;
myImage.y = 300;
// Allow the object to be dragged
myImage.addEventListener(MouseEvent.MOUSE_DOWN, clickToDrag);
myImage.addEventListener(MouseEvent.MOUSE_UP, releaseToDrop);
}
function clickToDrag(event:MouseEvent):void
{
event.target.startDrag();
}
function releaseToDrop(event:MouseEvent):void
{
event.target.stopDrag();
}

Related

using an array function for click events

I have created symbols which contain animations (mc1 - mc25). I would like to play these animations if I click on the symbols (click on mc1 -> play mc1, click on mc2 -> play mc2 etc.).
I created an array to address all my symbols in one go. It works ok.
var A:Array = [mc1, mc2, mc3, mc4,...mc25] // create array
car aClip:MovieClip;
for each (aClip in A) // stop all symbols
{aClip.stop();}
How can I get to the result below for all my symbols using an array function?
mc1.addEventListener(MouseEvent.CLICK, fl_MouseClickHandler_4);
function fl_MouseClickHandler_4(event:MouseEvent):void
{
mc1.play();
}
I tried something like this but I couldn't get it work:
aClip.addEventListener(MouseEvent.CLICK, fl_MouseClickHandler);
function fl_MouseClickHandler(event:MouseEvent):void {
aClip.play();
}
Thank you!
The simple way about it is to algorithmically figure which one was clicked. The script below is short and does not contain various checks, it assumes all the elements of A are really MovieClips.
// Assume, you filled the Array with these clips already.
var A:Array;
// Iterate all the clips.
for each (var aClip:MovieClip in A)
{
// Subscribe to each of the clips for the CLICK event.
aClip.addEventListener(MouseEvent.CLICK, clickPlay);
}
// Click event handler function. The point is, you can subscribe it
// to the multiple objects and use the function argument to figure out
// which one of them all is actually the source of the event.
// Furthermore, you can subscribe it to handle multiple events too,
// the argument also carries the id of the event. Sometimes it is
// really simpler to compose a single event-processing gate
// rather then process each one event in a separate handler.
function clickPlay(e:MouseEvent):void
{
// Use e.currentTarget because the initial e.target
// could be not the MovieClip itself, but some interactive
// element deep inside the MovieClip's hierarchy.
// Read about bubbling events (MouseEvent is bubbling)
// to get the whole picture of it.
// Typecasting, because e.target and e.currentTarget
// are of base Object type.
var aClip:MovieClip = e.currentTarget as MovieClip;
// Well, the only thing that is left.
aClip.play();
}

ActionScript 3 - use [brackets] instead of getChildByName

I have a MovieClip inside library, linkaged to MyObject and it contains a textField.
I don't know how I can access this textField without using the getChildByName method.
Apparently, the 3rd section works when object is on stage (without using addChild). But when using addChild I think there has to be some kind of casting; which I don't know how.
var childElement: MyObject = new MyObject();
childElement.name = "theChildElement";
container.addChild(childElement);
btn.addEventListener(MouseEvent.CLICK, changeText);
function changeText(event: MouseEvent): void
{
var targetBox:MovieClip = container.getChildByName(childElement.name) as MovieClip;
targetBox.textField.text = "hello"; // THIS WORKS
// This works too:
// MovieClip(container.getChildByName("theChildElement"))["textField"].text = "hello"; // THIS WORKS TOO.
// THIS DOESN'T WORK. why?
// container["theChildElement"]["textField"].text = "hello";
}
As confusing as it may seem, instance name, and name are not the same. From your code you should always be able to get to your MC by it's variable name. To get your last like to work you could just use this.
childElement["textField"].text = "hello";
There is a difference between Symbols created by the Flash IDE, which aggregate other DisplayObjects and programmatically created DisplayObjects.
When a DisplayObject is created in the Flash IDE, it's instance name can be used to resolve the instance as a property - which means it can be accessed via []. The [] can be used to access properties or keys of dynamic declared classes - like MovieClip. This necessary because you'll most likely down cast to MovieClip instead of using the symbol class created by Flash. That is not possible when simply using addChild, addChildAt or setChildAt from the DisplayObjectContainer API.
It is always the save way to access it via getChildByNameand check for null because otherwise your app, website or whatever is doomed for 1009 errors as soon as someone is changing the symbols.
I'd create a bunch of helper methods, like
// not tested
function getChildIn(parent:DisplayObjectContainer, names:Array):DisplayObject {
var child:DisplayObject, name:String;
while (names.length > 0) {
name = names.shift();
child = parent.getChildByName(name);
if (!child) {
// log it
return null;
}
if (names.length == 0) {
return child;
}
}
// log it
return null;
}
function getTextFieldIn(parent:DisplayObjectContainer, names:Array):TextField {
return getChildIn(parent, names) as TextField;
}
function getMovieClipIn(parent:DisplayObjectContainer, names:Array):MovieClip {
return getChildIn(parent, names) as MovieClip;
}
Your third method doesn't work because you are trying to call the ChildElement by it's name
without using getChildByName method. On the other hand, you shouldn't call your textField textField, because that's already an actionScript property.
Your should rather call it 'displayText' for example.
For a textField called 'displayText' contained in childElement :
function changeText(event:MouseEvent): void
{
childElement.displayText.text = "hello";
}

ActionScript BitmapData Built-in To Bitmaps?

i've used a Loader and URLRequest to download a .png from the internet and add it to my display list. since it's already a bitmap, does it have built in bitmap data already? or do i have to create the bitmap data myself?
also, why does the same trace statement return false in the mouseMoveHandler when it outputs true in the displayImage function?
var imageLoader:Loader = new Loader();
imageLoader.load(new URLRequest("http://somewebsite.com/image.png"));
imageLoader.contentLoaderInfo.addEventListener(Event.COMPLETE, displayImage);
function displayImage(evt:Event):void
{
addChild(evt.target.content);
addEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler);
trace(evt.target.content is Bitmap); //outputs 'true'
}
function mouseMoveHandler(evt:MouseEvent):void
{
trace(evt.target.content is Bitmap); //outputs 'false'
}
A quick search of the AS3 docs tells me that Bitmap has a bitmapData property.
You are getting different rusults in each trace because you are tracing different things. try just tracing the property rather that "is Bitmap", to see what is actually stored there.
Your first trace you are tracing an Event, your second a MouseEvent. Your displayImage function is a "Loader Complete handler", so target will be a LoaderInfo object. In a LoaderInfo object, target refers to "The loaded DisplayObject associated with this LoaderInfo object". But in a MouseEvent the target will be different. You will need to refer to the docs for each event to find out what the target will be.
Also, I think you will need to add the mouse move event listener to the stage, or it wont work e.g.
stage.addEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler);

Is there a way for listening for changes in flash.display.DisplayObjectContainer numChildren property?

I want to run some code whenever a DisplayObject is added as a child to a DisplayObjectContainer.
Or to put in other words, to catch the addedToStage event of all DisplayObjects, even ones I don't know about.
Is it possible? and if not, any ideas on how to do something similar?
An 'added' event is dispatched whenever a child display object is added to the display list via addChild() or addChildAt(). In the DisplayObjectContainer class add the listener:
addEventListener(Event.ADDED, onAdded);
and the handler:
private function onAdded(e:Event):void
{
trace('number of children is now ' + numChildren);
}
Using Event.ADDED_TO_STAGE on stage Object and setting useCapture to true.
More info on event here
Example:
function onAdded(e:Event):void{
trace(e.target.toString()); //use target to get the Object added
}
stage.addEventListener(Event.ADDED_TO_STAGE, onAdded, true); // set capture to true
I don't know if there is a built in way to do this.
Alternatives include the obvious,
private var _num_children:Number = 0;
addEventListener(Event.ENTER_FRAME, _checkChildren, false, 0, true);
private function _checkChildren($evt:Event):void {
if (this.numChildren != _num_children) {
_num_children = this.numChildren;
// There was a child (or more) added in the last frame execution
}
}
However, this seems like a more elegant solution...
public function _addChild($do:DisplayObject) {
$do .addEventListener(Event.ADDED_TO_STAGE, _childAdded);
addChild($do );
}
private function _childAdded($evt:Event) {
// do whatever with $evt.target
}
The difference here, is the _childAdded will get fired for each and every child added via _addChild method. This means if you are doing some costly code execution you will be doing it once for each child instance.
If you use the first method, you are only calling the method once per frame, and if 10 images are added on a single frame, then it will only run once.

actionscript-3: check if movieClip exists

I have a movieclip created with the following code:
var thumbContainer:MovieClip = new MovieClip();
thumbContainer.name = "thumbContainer";
stage.addChild (thumbContainer);
If the window gets larger/smaller I want everything back in place. So I have an stage Event Listener. Now I want to see if this mc exists to put back in place. I've tried different ways but keep getting an error that does not exist.
1120: Access of undefined property thumbContainer.
if (this.getChildByName("thumbContainer") != null) {
trace("exists")
}
and
if ("thumbContainer" in this) {
trace("exists")
}
or
function hasClipInIt (mc: MovieClip):Boolean {
return mc != null && contains(mc);
}
stage.addChild (thumbContainer);
//...
if (this.getChildByName("thumbContainer") != null)
You are adding the thumbContainer to stage and checking for its existence with this. Change stage to this or this to stage.
That said, an even more appropriate way is to keep a reference to the added movie clip and check for existence with the contains method. It determines whether the specified display object is a child of the DisplayObjectContainer instance or the instance itself. The search includes the entire display list including this DisplayObjectContainer instance, grandchildren, great-grandchildren, and so on.
Hence you can easily check using stage.contains(thumbContainer);
if you are having trouble firing errors, you can always resort to a try catch
try{
/// do something that will blow up...
}catch( e:Error ){
trace( "we had an error but its not fatal now..." );
}
the problem was that 'stage' and 'this' are not the same...that's why I couldn't talk to the mc.
this works:
var thumbContainer:MovieClip = new MovieClip();
thumbContainer.name = "thumbContainer";
addChild (thumbContainer);
if (getChildByName("thumbContainer") != null) {
trace("exists")
}