How to create custom MouseEvent.CLICK event in AS3

2020-03-03 04:44发布

问题:

This question doesn't relate only to MouseEvent.CLICK event type but to all event types that already exist in AS3. I read a lot about custom events but until now I couldn't figure it out how to do what I want to do. I'm going to try to explain, I hope you understand:

Here is a illustration of my situation:

for(var i:Number; i < 10; i++){
    var someVar = i;
     myClips[i].addEventListener(MouseEvent.CLICK, doSomething);
}

function doSomething(e:MouseEvent){ /* */ }

But I want to be able to pass someVar as a parameter to doSomething. So I tried this:

for(var i:Number; i < 10; i++){

    var someVar = i;
    myClips[i].addEventListener(MouseEvent.CLICK, function(){
        doSomething(someVar);
    });
}

function doSomething(index){ trace(index); }

This kind of works but not as I expect. Due to the function closures, when the MouseEvent.CLICK events are actually fired the for loop is already over and someVar is holding the last value, the number 9 in the example. So every click in each movie clip will call doSomething passing 9 as the parameter. And it's not what I want.

I thought that creating a custom event should work, but then I couldn't find a way to fire a custom event when the MouseEvent.CLICK event is fired and pass the parameter to it. Now I don't know if it is the right answer.

What should I do and how?

回答1:

Without knowing more about your application, it seems more like you should use the target to pass parameters, or extend MouseEvent. The former would be more in line with common practice, though. So for example, if you exposed an integer public property on your "clip" object (whatever it is):

public class MyClip
{
   public var myPublicProperty:int;

   public function MyClip() { //... }
}

for (var i:int = 0; i < 10; i++)
{
   myClips[i].myPublicProperty = i;
   myClips[i].addEventListener(MouseEvent.CLICK, doSomething);
}

... and then, in your event listener, you could retrieve that property using either the target or currentTarget property of the event (probably currentTarget, in your case):

function doSomething(event:MouseEvent):void
{
   trace(event.currentTarget.myPublicProperty.toString());
}

That ought to do it! Good luck.



回答2:

You really need to extend the event class to create your own event with extra parameters. Placing functions inside the addEventListener (anonymous functions) is a recipe for memory leaks, which is not good. Take a look at the following.

import flash.events.Event;

//custom event class to enable the passing of strings along with the actual event
public class TestEvent extends Event
{
    public static const TYPE1 :String = "type1";
    public static const TYPE2 :String = "type2";
    public static const TYPE3 :String = "type3";

    public var parameterText:String;

    public function TestEvent (type:String, searchText:String)
    {
        this.parameterText = searchText;
        super(type);
    }

}

when you create a new event such as

dispatchEvent(new TestEvent(TestEvent.TYPE1, 'thisIsTheParameterText'))" ;

you can then listen for that event like this

someComponent.addEventListener(TestEvent.TYPE1, listenerFunction, true , 0, true);

and inside the function 'listenerFunction' event.parameterText will contain your parameter.

so inside your myClips component you would fire off the custom event and listen for that event and not the Click event.



回答3:

    private function myCallbackFunction(e:Event, parameter:String):void
    {
         //voila, here's your parameter
    }

    private function addArguments(method:Function, additionalArguments:Array):Function 
    {
         return function(event:Event):void {method.apply(null, [event].concat(additionalArguments));}
    }

    var parameter:String = "A sentence I want to pass along";
    movieClip.addEventListener(Event.COMPLETE, addArguments(myCallbackFunction, [parameter] ) );

Take advantage of the dynamic function construction in AS3.



回答4:

You can accomplish this by getting your handler out of a function that gives the variable closure, like this:

for (var i=0; i<5; i++) {
    myClips[i].addEventListener( MouseEvent.CLICK, getHandler(i) );
}

function getHandler(i) {
    return function( e:MouseEvent ) {
        test(i);
    }
}

function test( j ) {
    trace("foo "+j);
}

Also, as for why this creates a new closure, you might want to check the explanation in the accepted answer to this similar question.



回答5:

Thanks so much for this usefull tips, this technique is better to understand than classes explanation.

for me I just started new code algorithm using this technique to solve link relation between timers array and viewports array, and update status by change text inside them frequently, by passing ID's with timers events.

like this:

    var view:Object=[];

    for(var i:uint=0;i<Camera.names.length;i++){

    view[i]=getChildByName("Cam"+i);

    //_________ Add Text _________
    var tf:TextField = new TextField();
    tf.autoSize = TextFieldAutoSize.LEFT;
    tf.textColor=0xffffff;
    view[i].tf=view[i].addChild(tf);

    //_________ Add Timer _________
    var t:Timer = new Timer(1000);
    view[i].timer=t;
    view[i].timer.start();
    view[i].timer.addEventListener(TimerEvent.TIMER, addArg(i)); 
}

function addArg(adi:uint):Function {
    return function(event:TimerEvent):void {
        updatecamstatus(adi);
    }
}
function updatecamstatus(vH:uint):void {
   with (view[vH]){
    tf.text="Cam" + vH + "\n";
    tf.appendText("activityLevel: " + videosource.activityLevel + "\n"); 
    tf.appendText("motionLevel: " + videosource.motionLevel + "\n"); 
   }
}


回答6:

I see your main goal isn't actually to create a custom MouseEvent.CLICK, but to pass a parameter to the function. You don't need to complicatedly create or extend anything. There's a simple and closure-trouble-free way to do it.

Just make your function like this:

function doSomething(index:Number):Function {
  return function(e:MouseEvent):void {
    // Use "e" and "index" here. They'll be unique for each addEventListener()
    trace(index);
  }
}

This technique can relate to any AS3 event type you can use addEventListener on.

And now you can add it like this:

var functionsDoSomething:Object;

for (var i:Number = 0; i < 10; i++) {
  var someVar:Number = i;
  functionsDoSomething[i] = doSomething(someVar);
  myClips[i].addEventListener(MouseEvent.CLICK, functionsDoSomething[i]);
}

The doSomething(someVar) can be used directly on addEventListener(), but it's better to keep it in a variable because you'll be able to remove it later the same fashion you added it:

for (i = 0; i < 10; i++) {
  myClips[i].removeEventListener(MouseEvent.CLICK, functionsDoSomething[i]);
}

The commonly used e.currentTarget.someCustomProperty works for dynamic objects (i.e. MovieClip), but will let you down at anything else (i.e. Sprite), forcing you to build a whole custom extended object/event for every type.

This solution deals with every "listenable" object and event. And this answer has more details and examples on it.