I know the different between event capturing and bubbling and how stopPropagation work.
So why capturing not worked in my simple test? There is no stopPropagation in the code.
import flash.display.Sprite;
import flash.events.MouseEvent;
public class CapturingNotWork extends Sprite {
public function CapturingNotWork() {
var rect:Sprite = new Sprite();
rect.graphics.beginFill(0x000000);
rect.graphics.drawRect(0, 0, 100, 100);
rect.graphics.endFill();
// CAPTURING NOT WORKED
rect.addEventListener(MouseEvent.MOUSE_DOWN, mouseDown, true);
// BUT THE BUBBLING WORKED
// rect.addEventListener(MouseEvent.MOUSE_DOWN, mouseDown);
stage.addChild(rect);
}
function mouseDown(e:MouseEvent): void {
trace("It's worked");
}
}
The event starts with the topmost parent (stage) and works down the display object hierarchy until reaching the original target.
When the useCapture parameter is set to true, the listener processes the event only during the capture phase and not in the target or bubbling phase. Your event never reaches the target (rect) as you can see on this image:
Related
I was looking for this answer and had no luck. One place I looked actually had a very discouraging answer: "You cannot force mouse or keyboard events - they HAVE TO come from mouse or keyboard."
Huh?
I tried 'brute force' and came up with this solution. Maybe I'm going about it wrongly or stupidly; is there a better way?
I had a keyboard event that launched a class and wanted to put a sprite on the stage that would initiate this same action -- clicking on the sprite would launch the keyboard event (Escape key).
In the eventListener function, I traced the event e itself:
private function keys(e:KeyboardEvent):void {
trace("EscapeKey: ",e);
if (e.keyCode == 27) {
...
}
}
The output was
EscapeKey: [KeyboardEvent type="keyDown" bubbles=true cancelable=false eventPhase=2 charCode=27 keyCode=27 keyLocation=0 ctrlKey=false altKey=false shiftKey=false]
I then had the mouseClick listener create and dispatch a new keyboardEvent using the values I got from the above trace:
private function pauseClick(e:MouseEvent):void {
var a:KeyboardEvent = new KeyboardEvent("keyDown", true, false, 27, 27, 0, false, false, false);
stage.dispatchEvent(a);
}
Presto!
Hopefully, this post will come in handy to others looking for these types of mouse/keyboard event redundancies.
EDIT --- A complete class example requested in comments:
package{
import flash.display.Sprite;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.events.KeyboardEvent;
public class KeyboardMouse extends Sprite {
private var pauseInfo:PauseInfo;
private var escapeKey:EscapeKey;
public function KeyboardMouse() {
if (stage) init();
else addEventListener(Event.ADDED_TO_STAGE, init);
}
private function init(e:Event = null):void {
removeEventListener(Event.ADDED_TO_STAGE, init);
stage.addEventListener(KeyboardEvent.KEY_DOWN, keys);
escapeKey = new EscapeKey();
stage.addChild(escapeKey);
pauseInfo = new PauseInfo();
pauseInfo.x = stage.stageWidth;
pauseInfo.y = stage.stageHeight;
pauseInfo.addEventListener(MouseEvent.CLICK,pauseClick);
addChild(pauseInfo);
}
private function keys(e:KeyboardEvent):void {
trace("KeyboardEvent ",e);
if (e.keyCode == 27) { // esc key
if (stage.contains(escapeKey)){
trace("remove escape");
escapeKey.visible = false;
}
else {
trace("show escape");
escapeKey.visible = true;
}
}
}
private function pauseClick(e:MouseEvent):void {
// The trace in 'keys' gives this:
//[KeyboardEvent type="keyDown" bubbles=true cancelable=false eventPhase=2 charCode=27 keyCode=27 keyLocation=0 ctrlKey=false altKey=false shiftKey=false]
var a:KeyboardEvent = new KeyboardEvent("keyDown", true, false, 27, 27, 0, false, false, false);
stage.dispatchEvent(a);
}
}
}
Alright, now I get this. I just don’t get it’s purpose. Also there is, I think, an annoying error in the code.
First: you’ve shown that you can essentially ‘cast’ one type of event (e.g. MouseEvent) as another (KeyboardEvent). But you still have a listener function registered for each Event type (which, at some point, you’ll have to remove for memory management purposes) and a newly dispatched Event, so you’re not exactly minimizing code. What, exactly, is the value of this trick? Why not just register a single listener function with multiple event types? Within that function you can discriminate between the Event types and do stuff accordingly, for instance with a line like this:
if(e.type == 'keyDown' || e.type == 'click')
Second: you say: if (stage.contains(escapeKey)) and then you try to make the ‘visible’ property of escapeKey dependent on that. But by making the escapeKey.visible = false you’re not changing the fact that the stage still contains escapeKey. So the escapeKey will never become visible again because the 'else' condition doesn't exist. I believe that you want to say “removeChild(escapeKey)” and then “addChild(escapeKey)” instead of setting “escapeKey.visible = false” and then “escapeKey.visible = true”. Then you’re little program does what I think you want it to.
+1 and big props to #Neal Davis for his comments!
As he suggests, having one listener for both events with an argument of (e:Event) is a cleaner, less convoluted way of achieving the same result:
pauseInfo.addEventListener(MouseEvent.CLICK,keys);
stage.addEventListener(KeyboardEvent.KEY_DOWN, keys);
private function keys(e:Event):void {
trace("EscapeKey: ",e);
if (e.keyCode == 27) {
...
}
}
Both listeners must still be removed at some point, though, even though only one Listener Function is handling both, of course!
I have created a custom Sprite object which is not dispatching the MouseEvent.MOUSE_DOWN or MouseEvent.MOUSE_UP events. It is properly dispatching MouseEvent.MOUSE_MOVE events.
All event listeners are verified to be registered.
I apologize if this is a common or excessively simple question, but I have spent the last hour Googling and reading StackOverflow for questions or answers that fit my situation and simply have not found one. As far as I know, Sprite is an InteractiveObject and therefore should be dispatching all three of these events rather than only the MouseEvent.MOUSE_MOVE events.
Class Declaration:
import flash.display.*;
import flash.events.*;
import flash.geom.*;
public class CustomSprite extends Sprite {
Object Initialization in main.as
var circle:CustomSprite = new CustomSprite();
circle.graphics.lineStyle(3,0x00FF00);
circle.graphics.beginFill(0x0000FF,.5);
circle.graphics.drawCircle(0,0,50);
circle.graphics.endFill();
circle.x = 100;
circle.y = 100;
Event Listener Registration in Constructor:
addEventListener(MouseEvent.MOUSE_DOWN,mouseDownListener);
if (hasEventListener(MouseEvent.MOUSE_DOWN)) trace("MOUSE_DOWN listener exists");
addEventListener(MouseEvent.MOUSE_UP,mouseUpListener);
if (hasEventListener(MouseEvent.MOUSE_UP)) trace("MOUSE_UP listener exists");
addEventListener(MouseEvent.MOUSE_MOVE,mouseMoveListener);
if (hasEventListener(MouseEvent.MOUSE_MOVE)) trace("MOUSE_MOVE listener exists");
Event Listener Registration Output:
MOUSE_DOWN listener exists
MOUSE_UP listener exists
MOUSE_MOVE listener exists
Listener Functions as Part of CustomSprite Class:
private function mouseDownListener(e:MouseEvent):void {
trace("mouseDownListener");
}
private function mouseUpListener(e:MouseEvent):void {
trace("mouseUpListener");
}
private function mouseMoveListener(e:MouseEvent):void {
trace("mouseMoveListener");
}
Output after hovering over object and clicking several times:
mouseMoveListener
mouseMoveListener
mouseMoveListener
mouseMoveListener
Thanks in advance for any help you can offer - it is greatly appreciated!
What is looks like from the source you have provided is that you aren't adding the event listeners to the circle itself. This snippet I tested worked, provided I don't have your CustomSprite, but just the Sprite.
var circle:CustomSprite = new CustomSprite();
circle.graphics.lineStyle(3,0x00FF00);
circle.graphics.beginFill(0x0000FF,.5);
circle.graphics.drawCircle(0,0,50);
circle.graphics.endFill();
circle.x = 100;
circle.y = 100;
circle.addEventListener(MouseEvent.MOUSE_DOWN, function(event:MouseEvent):void{
trace("mouseDown");
});
circle.addEventListener(MouseEvent.MOUSE_UP, function(event:MouseEvent):void{
trace("mouseUp");
});
circle.addEventListener(MouseEvent.MOUSE_MOVE, function(event:MouseEvent):void{
trace("mouseMove");
});
I am new to actionscript ,
My document class is ,
package
{
//list of our imports these are classes we need in order to
//run our application.
import flash.display.MovieClip;
import flash.display.Stage;
import flash.events.Event;
public class engine extends MovieClip
{
// moved ourShip to a class variable.
private var Circle:circle = new circle()
//our constructor function. This runs when an object of
//the class is created
public function engine()
{
addFrameScript(0, frame1);
addFrameScript(1, frame2);
}
// frame 1 layer 1 --------------------------------------------------
public function frame1()
{
stop();
}
//-------------------------------------------------------------------
// frame 2 layer 1 --------------------------------------------------
public function frame2()
{
Circle.x = stage.stageWidth / 2;
Circle.y = stage.stageHeight / 2;
addChild(Circle);
}
//-------------------------------------------------------------------
}
}
i made two frames first contains button and the other circle which i want to move but it not moves and it stays in the middle on second frame
My button class is
package
{
//imports
import flash.events.MouseEvent;
import flash.display.SimpleButton;
import flash.display.MovieClip;
//-------
public class start extends SimpleButton
{
public function start()
{
addEventListener(MouseEvent.CLICK, onTopClick);
addEventListener(MouseEvent.MOUSE_OVER, onBottomOver);
}
function onTopClick(e:MouseEvent):void
{
MovieClip(root).gotoAndStop(2)
}
function onBottomOver(e:MouseEvent):void
{
}
}
}
And my as of circle movieclip is
package
{
//imports
import flash.display.MovieClip;
import flash.display.Stage;
import flash.ui.Keyboard;
import flash.events.Event;
import flash.events.KeyboardEvent;
public class circle extends MovieClip
{
private var speed:Number = 0.5;
private var vx:Number = 0;
private var vy:Number = 0;
private var friction:Number = 0.93;
private var maxspeed:Number = 8;
public function circle()
{
addEventListener(Event.ENTER_FRAME, loop, false, 0, true);
}
public function loop(e:Event) : void
{
addEventListener(KeyboardEvent.KEY_DOWN, keyHit);
x+=vx;
y+=vy
}
function keyHit(event:KeyboardEvent):void {
switch (event.keyCode) {
case Keyboard.RIGHT :
vx+=speed;
break;
case Keyboard.LEFT :
vx-=speed;
break;
case Keyboard.UP :
vy-=speed;
break;
case Keyboard.DOWN :
vy+=speed;
break;
}
}
}
}
I am sorry to post so much for you guys to read but stackoverflow is the only website where anyone helps me !
You have made several major errors. First, addFrameScript() isn't a proper way to place code on frames, use Flash's editor to place code on timeline. (IIRC you will have to make a single call out of your two in order to have all the code you add to function) And, whatever code you added to a frame of a MC is executed each frame if the MC's currentFrame is the frame with code. Thus, you are adding a function "frame2()" that places the Circle in the center of the stage each frame! You should instead place it at design time (link it to a property) into the second frame, or in a constructor, or you can use one single frame and Sprite instead of MovieClip, and instead of using frames you can use container sprites, adding and removing them at will, or at an action.
The other major mistake is adding an event listener inside an enterframe listener - these accumulate, not overwrite each other, so you can have multiple functions be designated as listeners for a particular event, or even one function several times. The latter happens for you, so each frame another instance of a listening keyHit function is added as a listener. The proper way to assign listeners is either in constructor, or in any function that listens for manually triggered event (say, MouseEvent.CLICK), but then you have to take precautions about listening for more than once with each function, and listening only with those functions you need right now.
EDIT:
Okay. Your code was:
addFrameScript(0, frame1);
addFrameScript(1, frame2);
The more correct way should be:
addFrameScript(0,frame1,1,frame2);
The reason is, the call to addFrameScript replaces all the timeline code with what you supply within here. The function is undocumented, perhaps by the reason of its affects on the stage and AS3 environment. The closest thing to the documentation on addFrameScript() so far is this link.
Next: Your code is:
public function circle()
{
addEventListener(Event.ENTER_FRAME, loop, false, 0, true);
}
public function loop(e:Event) : void
{
addEventListener(KeyboardEvent.KEY_DOWN, keyHit);
x+=vx;
y+=vy
}
The correct way of writing this is as follows:
public function circle()
{
addEventListener(Event.ENTER_FRAME, loop, false, 0, true);
if (stage) init();
else addEventListener(Event.ADDED_TO_STAGE,init);
}
private function init(e:Event=null):void
{
removeEventListener(Event.ADDED_TO_STAGE,init);
stage.addEventListener(KeyboardEvent.KEY_DOWN, keyHit);
}
public function loop(e:Event) : void
{
x+=vx;
y+=vy
}
The listeners should be assigned in constructor, if they are permanent or you want them to be active as soon as you create an object. The KeyboardEvent listeners are separate case, as in order for them to function you have to assign them to stage, which is not available right at the time of creating the object, so you need an intermediate layer - the init() function, that is only called when the object is added to stage. At this point stage is no longer null, and you can assign an event listener there. Note, if you want to make your circles eventually disappear, you have to remove the listener you assigned to stage at some point of your removal handling code.
Next: Your code:
public function frame2()
{
Circle.x = stage.stageWidth / 2;
Circle.y = stage.stageHeight / 2;
addChild(Circle);
}
Correct code should be:
public function frame2():void
{
if (Circle.parent) return; // we have added Circle to stage already!
Circle.x = stage.stageWidth / 2;
Circle.y = stage.stageHeight / 2;
addChild(Circle);
}
See, you are calling this every time your MC is stopped at second frame, thus you constantly reset Circle's coordinates to stage center, so you just cannot see if it moves (it doesn't, as you have assigned the keyboard listener not to stage).
Perhaps there are more mistakes, but fixing these will make your MC tick a little bit.
Is there any way to detect if the user click outside a MovieClip?
For instance, I need to detect it to close a previously opened menu (like Menu bar style: File, Edition, Tools, Help, etc).
How can I detect this kind of event? Thanks!
Add a listener to stage and check if stage is the target of the event.
Example of code here:
http://wonderfl.net/c/eFao
package
{
import flash.display.Sprite;
import flash.events.MouseEvent;
public class FlashTest extends Sprite
{
private var _menu : Sprite;
public function FlashTest()
{
_menu = new Sprite();
_menu.x = 100;
_menu.y = 100;
_menu.alpha = 0.5;
with(_menu.graphics)
{
beginFill(0xFF0000, 1);
drawRect(0, 0, 300, 300);
endFill();
}
addChild(_menu);
_menu.addEventListener(MouseEvent.CLICK, onClickHandler);
stage.addEventListener(MouseEvent.CLICK, onClickHandler);
}
private function onClickHandler(event : MouseEvent) : void
{
switch(event.target)
{
case _menu:
_menu.alpha = 0.5;
break;
case stage:
_menu.alpha = 1;
break;
}
}
}
}
You can add a listener to the click event of the root element:
MovieClip(root).addEventListener(MouseEvent.CLICK, clickObject);
then in the function clickObject, you can check to see what you are clicking.
function clickObject(e:Event):void
{
var hoverArray:Array = MovieClip(root).getObjectsUnderPoint(new Point(stage.mouseX, stage.mouseY));
var hoverOverObject:* = hoverArray[hoverArray.length - 1];
}
hoverOverObject references the element that you are clicking on. Often this will be the shape within the movie clip, so you'll need to look at it's parent then compare it to your movie clip. If the click wasn't on the drop down movie clip, trigger the close.
var container:MovieClip = new MovieClip();
var mc:MovieClip = new MovieClip();
with(mc.graphics){
beginFill(0xff0000,1);
drawCircle(0,0,30);
endFill();
}
mc.name = "my_mc";
container.addChild(mc);
addChild(container);
stage.addEventListener(MouseEvent.CLICK, action);
function action (e:MouseEvent):void
{
if(e.target.name != "my_mc"){
if(container.numChildren != 0)
{
container.removeChild(container.getChildByName("my_mc"));
}
}
}
Use capture phase:
button.addEventListener(MouseEvent.CLICK, button_mouseClickHandler);
button.stage.addEventListener(MouseEvent.CLICK, stage_mouseClickHandler, true);
//...
private function button_mouseClickHandler(event:MouseEvent):void
{
trace("Button CLICK");
}
private function stage_mouseClickHandler(event:MouseEvent):void
{
if (event.target == button)
return;
trace("Button CLICK_OUTSIDE");
}
Note that using stopPropagation() is good for one object, but failed for several. This approach works good for me.
Use a stage and a sprite (menu) click listener with the sprite listener executing first and apply the stopPropagation() method to the click handler of the sprite. Like this:
menu.addEventListener(MouseEvent.CLICK, handleMenuClick);
stage.addEventListener(MouseEvent.CLICK, handleStageClick);
private function handleMenuClick(e:MouseEvent):void{
// stop the event from propagating up to the stage
// so handleStageClick is never executed.
e.stopPropagation();
// note that stopPropagation() still allows the event
// to propagate to all children so if there are children
// within the menu overlay that need to respond to click
// events that still works.
}
private function handleStageClick(e:MouseEvent):void{
// put hide or destroy code here
}
The idea is that a mouse click anywhere creates a single MouseEvent.CLICK event that bubbles from the stage, down through all children to the target, then back up through the parents of the target to the stage. Here we interrupt this cycle when the target is the menu overlay by not allowing the event to propagate back up to the parent stage, ensuring that the handleStageClick() method is never invoked. The nice thing about this approach is that it is completely general. The stage can have many children underneath the overlay and the overlay can have its own children that can respond to clicks and it all works.
I'm developing a drag'n'clone feature for my flash app (AS3 and Flash CS5). The clones are created perfectly, but when I try to drag the clone recently created, the app creates a new clone (and allow me to drag it).
I want to remove this behaviour: clone only should be drag and dropped, not cloned. My code is:
public class Car extends MovieClip
{
// imports...
public function Car()
{
addListeners();
}
private function addListeners():void
{
this.addEventListener(MouseEvent.MOUSE_DOWN,clone);
}
private function clone(e:MouseEvent):void
{
// Clone the object
var newcar = new e.target.constructor;
newcar.graphics.copyFrom(this.graphics);
newcar.x = this.x;
newcar.y = this.y;
this.parent.addChild(newcar);
newcar.addEventListener(MouseEvent.MOUSE_MOVE,dragCar);
newcar.addEventListener(MouseEvent.MOUSE_UP,dropCar);
}
private function dragCar(e:MouseEvent):void
{
e.target.startDrag();
}
private function dropCar(e:MouseEvent):void
{
e.target.stopDrag();
// This line doesn't remove the event, and I don't know why
e.currentTarget.removeEventListener(MouseEvent.MOUSE_DOWN,clone);
e.target.removeEventListener(MouseEvent.MOUSE_MOVE, dragCar);
e.target.removeEventListener(MouseEvent.MOUSE_UP,dropCar);
}
}
I hope someone can help me. Thanks!
Here as you are creating a new instance of Car in the clone function, the constructor of that new Car object is called which in turn calls addListeners() and the clone has a MOUSE_DOWN event listener that again clones the clone. That is why you have the problem you describe.
To avoid this you need to add the following line in you clone function (this does not work see below edit)
newcar.removeEventListener(MouseEvent.MOUSE_DOWN,clone);
this removes the clone event listener from the cloned (new) Car instance and avoids cloning again.
Also to start drag you should do it in MOUSE_DOWN instead of MOUSE_MOVE.
Update
OK I see, MOUSE_DOWN is not called Again for the clone, so you need to use MOUSE_MOVE. Thus in this case I would remove the MOUSE_MOVE listener in the listener's body so that it is only called once.
Update
This seems to remove the event listener.
newcar.removeEventListener(MouseEvent.MOUSE_DOWN, newcar.clone);
You have to refer to the newcar instance's clone method newcar.clone to remove the event listener.
Working Code
The following code is working just fine
package{
import flash.display.MovieClip;
import flash.events.*;
public class Car extends MovieClip
{
public function Car()
{
addListeners();
}
private function addListeners():void
{
this.addEventListener(MouseEvent.MOUSE_DOWN,clone);
}
private function clone(e:MouseEvent):void
{
// Clone the object
var newcar = new e.target.constructor;
newcar.graphics.copyFrom(this.graphics);
newcar.x = this.x;
newcar.y = this.y;
this.parent.addChild(newcar);
// remove the clone listener
newcar.removeEventListener(MouseEvent.MOUSE_DOWN, newcar.clone);
// add dragging listeners
newcar.addEventListener(MouseEvent.MOUSE_MOVE, dragCar);
newcar.addEventListener(MouseEvent.MOUSE_UP, dropCar);
// this is used for dragging the clone
newcar.addEventListener(MouseEvent.MOUSE_DOWN, dragCar);
}
private function dragCar(e:MouseEvent):void
{
// if a MOUSE_MOVE event listener is registered remove it
if(e.target.hasEventListener(MouseEvent.MOUSE_MOVE))
e.target.removeEventListener(MouseEvent.MOUSE_MOVE, dragCar);
e.target.startDrag();
}
private function dropCar(e:MouseEvent):void
{
e.target.stopDrag();
// do not remove the listener if you want the clone to be dragable
// e.target.removeEventListener(MouseEvent.MOUSE_UP,dropCar);
}
}
}
Keep clone dragable
For this in the above code I have added a MOUSE_DOWN event listener for dragging the clone
newcar.addEventListener(MouseEvent.MOUSE_DOWN, dragCar);
And commented out the e.target.removeEventListener(MouseEvent.MOUSE_UP,dropCar); in dropCar
Also added a Check before removing MOUSE_MOVE listener as to whether it is there or not, as this function will be called later by MOUSE_DOWN also.