Handle MouseEvent.CLICK, MOUSE_DOWN in subclasses - actionscript-3

I'm building a panel of buttons, that are enclosed in Canvas container. For this purpose I created a MyButton class, which is a subclass of UIComponent. MyButton class has 2 other subclasses: MyRadioButton and MyMenuButton, which have another behavior on MouseEvent.MOUSE_DOWN. MyMenuButtoncreates and shows a menu, that I build from XML and it builds ok.
I add listeners in the superclass like this:
this.addEventListener(MouseEvent.CLICK, handleClick);
this.addEventListener(MouseEvent.MOUSE_DOWN, handleMouse);
this.addEventListener(MouseEvent.MOUSE_UP, handleMouse);
It's done at the creation stage.
In my subclasses I override handleMouse handler.
override protected handleMouse(event:MouseEvent) : void
{
// subclass specific handling
}
These button objects are added to canvas container as following:
in class MyButtonsContainer.as:
this.rowChildren.addChild(button);
These buttons draw perfectly and in place. The problem is the behavior:
The event doesn't come to handleClick handler of superclass. And that's the question actually - why can it be?
Thanks in advance
EDIT: It appears that MOUSE_DOWN & MOUSE_UP interfere with CLICK event. When I remove listeners to them I get to click handler.How to cause them live together?

I managed to catch CLICK event as timing difference between MOUSE_DOWN & MOUSE_UP.
EDIT: A short/long explanation: For some reason my listeners for MOUSE_DOWN/MOUSE_UP events were interfering with MouseEvent.CLICK. I was suggested by somebody to give up on listening to MouseEvent.CLICK and instead start a timer in MouseEvent.MOUSE_DOWN and check again the timer in MouseEvent.MOUSE_UP. If the difference less than 0.1 (you can put there another threshold) then it was a click actually.
Now some sample code:
public class MyButton extends UIComponent
{
...
private var _clickTime : Number = 0.;
public function void MyButton()
{
super();
addEventListener(MouseEvent.MOUSE_DOWN, handleMouse);
addEventListener(MouseEvent.MOUSE_UP, handleMouse);
}
protected function checkForClick(event:MouseEvent) : Boolean
{
var down : Boolean = event.type == MouseEvent.MOUSE_DOWN;
if (down)
_clickTime = getTimer();
else {
var diff : Number = getTimer() - _clickTime;
if (diff/1000. < 1.) {
handleClick();
return true;
}
}
return false;
}
protected function handleClick(event:MouseEvent) : void
{
// handle the click here
}
protected function handleMouse(event:MouseEvent) : void
{
checkForClick(event);
...
// take care of your button graphic state
}
DISCLAIMER: This code works for me and good for me and my application needs. If you disagree with this way or have a better way to suggest, please do it in comments. Since this answer answers my own question and this EDIT was added only because I was asked to do so, please don't downvote it if you don't agree with it.

call the superclass method like so
override protected handleMouse(event:MouseEvent) : void
{
super.handleMouse(event);
// subclass specific handling
}

If I'm understanding you correctly, you just need to make sure that in your overriden handlers you end each one with a call to super -
override protected handleMouse(event:MouseEvent):void {
// my logic here for the subclass
trace("blah blah blah");
//now redispatch up to the superclass.
super.handleMouse(event);
}

I'm not 100% sure what you're asking, but I'd override click actions like this..
Base class:
public class MyButton extends UIComponent
{
/**
* Constructor
*/
public function MyButton()
{
// listeners
addEventListener(MouseEvent.CLICK, _click);
// ...other listeners
}
/**
* Called on dispatch of MouseEvent.CLICK
*/
private function _click(e:MouseEvent):void
{
doClick();
}
/**
* Override this method and fill with actions to
* do when this is clicked
*/
protected function doClick():void
{
trace("base class was clicked");
}
}
And the extending class:
public class MyOtherButton extends MyButton
{
/**
* Override doClick
*/
override protected function doClick():void
{
super.doClick();
trace("extending class was clicked");
}
}
After closely inspecting your code:
You've added two different listeners to one function for one, shouldn't these have seperate functions as they are called by contrasting events?
this.addEventListener(MouseEvent.MOUSE_DOWN, handleMouse);
this.addEventListener(MouseEvent.MOUSE_UP, handleMouse);
Also, it seems like you're expecting handleMouse to be fired by a click event judging by the below code, which isn't the case.
override protected handleMouse(event:MouseEvent) : void
{
// subclass specific handling
}
Either that, or you're forgetting to override handleClick as well - which is the method that you're running on a click event. You possibly need to add this code:
override protected handleClick(event:MouseEvent) : void
{
// subclass specific handling for a click
}

Related

Dispatchevent not firing

I have a fla file and 2 class files. On my fla I have:
addEventListener(SubtitleLoadEvent.PASS_PARAMS, onProcessedEvent);
function onProcessedEvent(e:Event):void {
trace(e.currentTarget);
}
SubtitleLoadEvent.as :
package
{
import flash.events.Event;
public class SubtitleLoadEvent extends Event
{
public static const PASS_PARAMS:String = new String("passparams");
public var resultArr:Array = new Array();
public function SubtitleLoadEvent(type:String, arr:*, bubbles:Boolean = false,
cancelable:Boolean = false):void
{
this.resultArr = arr;
super(type, bubbles, cancelable);
}
override public function clone():Event
{
return(new SubtitleLoadEvent(type, resultArr, bubbles, cancelable));
}
}
}
And I have a class file which extends sprite :
dispatchEvent(new SubtitleLoadEvent(SubtitleLoadEvent.PASS_PARAMS, cleanArr));
But the movie doesn't output anything. How can I fix this?
Since your event doesn't bubble, the only way your timeline code will hear the event is if it was dispatched on the same scope (which is unlikely to be the case here).
If your dispatching sprite is on the same scope (timeline) or a descendant/child of it, then making the event bubble (third parameter when creating the event) should make it work. (or you could listen on the capture phase)
Otherwise, you will need to listen for the event on some common parent of both objects.
The easiest way to resolve this, is to dispatch and listen on the stage:
stage.addEventListener(SubtitleLoadEvent.PASS_PARAMS, onProcessedEvent);
stage.dispatchEvent(new SubtitleLoadEvent(SubtitleLoadEvent.PASS_PARAMS, cleanArr));
This assumes that your dispatching sprite has been added to the display list. If not, then the stage property will be null. If it's not on the display list, then it will not work.
TO learn more about how events work and their lifecycle, you could read this article.

public function access fail

I have this situation where I declare inside my main class a function that looks like this:
public class Main extends MovieClip
{
public static var instance:Main;
public function Main()
{
// constructor code
welcomeScreen();
instance = this;
}
public final function welcomeScreen():void
{
//some code in here
}
public final function startLevelOne():void
{
//some other code here
}
}
In some other class I use this statement to fire a reset:
restart.addEventListener('click', function() {
Main.instance.welcomeScreen();
});
Somehow in another class I try to use the same statement for 'startLevelOne' but it seems it is not working and gives the fallowing error:
1195: Attempted access of inaccessible method startLevelOne through a reference with static type Main.
Any ideas?
UPDATE #1
The class where I try to access the function is in full this one:
public class LevelBrief extends MovieClip
{
public function LevelBrief()
{
// constructor code
startBut.addEventListener('click', function() {
Main.instance.startLevelOne();
});
}
}
UPDATE #2
I have pasted the full code of the main definition here http://pastebin.com/s6hGv7sT
Also the other class could be found here http://pastebin.com/s6h3Pwbp
UPDATE #3
Even though the problem was solved with a workaround, I still cannot understand where was the problem.
I would recommend to leave the static instance (singleton), and work event-based. Now you make all functions public, which is not desirable. It's not that hard to use custom events. See this is how your Main class could look:
public class Main extends MovieClip
{
public function Main()
{
this.addEventListener(Event.ADDED_TO_STAGE, handleAddedToStage);
}
public function handleAddedToStage(event:Event)
{
this.removeEventListener(Event.ADDED_TO_STAGE, handleAddedToStage);
this.showWelcomeScreen();
stage.addEventListener(ScreenEvent.SHOW_WELCOME_SCREEN, handleScreenEvent);
stage.addEventListener(ScreenEvent.SHOW_LEVEL, handleScreenEvent);
}
private function handleScreenEvent(event:ScreenEvent):void
{
switch (event.type)
{
case ScreenEvent.SHOW_WELCOME_SCREEN:
{
this.showWelcomeScreen()
break;
}
case ScreenEvent.SHOW_LEVEL:
{
// event.data contains level number
this.startLevel(event.data);
break;
}
default:
{
trace("Main.handleScreenEvent :: Cannot find event.type '" + event.type + "'.");
break;
}
}
}
private function showWelcomeScreen():void
{
trace("show WelcomeScreen")
//some private code in here
}
private function startLevel(level:int):void
{
trace("start level: " + level)
//some other private code here
}
}
This is how the custom event class should look (ScreenEvent.as). Note it has an optional parameter called data. You can pass any value (objects, numbers, strings etc) into this. To the example as clear as possible, I used one event-class for both actions, you can also choose to make more specific custom events for other actions with more detailed parameters, you would have names like ScreenEvent, LevelEvent, PlayerEvent, GameEvent etc etc..
At the top of the class the (static constant) types are defined. An event should only have getters.
package
{
import flash.events.Event;
public class ScreenEvent extends Event
{
public static const SHOW_WELCOME_SCREEN:String = "ScreenEvent.showWelcomeScreen";
// event.data contains level number
public static const SHOW_LEVEL:String = "ScreenEvent.showLevel";
private var _data:String;
public function ScreenEvent(type:String, data:String):void
{
super(type);
this._data = data;
}
public function get data():String
{
return this._data;
}
override public function clone():Event
{
return new ScreenEvent(this.type, this._data);
}
}
}
.. Anywhere in your code you can dispatch the event to the stage.
// dispatch event to Main (stage). Should show welcome screen in our case
stage.dispatchEvent(new ScreenEvent(ScreenEvent.SHOW_WELCOME_SCREEN));
// show level 2
stage.dispatchEvent(new ScreenEvent(ScreenEvent.SHOW_LEVEL, 2));
I know, its a bit more code, it looks more difficult at first but if the project grows, it will help a lot. The difference with events is 'this could happen, and when it happens, do this' instead of 'do this here, do that over there'
The advantage is that if you remove the event listener in the Main class, nothing will break (loosely coupled). This makes it easier to maintain, it saves a singleton, and you have the ability to extend the Main class if you want to.
I think you wrote
Main.startLevelOne();
instead of
Main.instance.startLevelOne();
Hmm. Given your code, there is only one serious question - what is PerwollGame? You have there public static var instance:PerwollGame; and you assign it an object of type Main. Perhaps that PerwollGame has a startLevelOne() function with a different signature, that obscures your function in the Main class. Also, the other people who answered you are right as well, you should never use nested functions in your code, really put that listener of yours out from inline declaration.
Judging from your coding style and the error reported, I would assume you did this.
public static function startLevelOne():void
There is a fine line between static methods and instantiated objects.
Also never use nested functions
public class LevelBrief extends MovieClip
{
public function LevelBrief()
{
// constructor code
startBut.addEventListener('click', onMyClick )
}
public functiononMyClick (e:Event) {
Main.instance.startLevelOne();
});
}
}
I assume that when you register the listener Main.instance is not already assigned.
Did you try to trace Main instance here?
public function LevelBrief()
{
// constructor code
startBut.addEventListener('click', function() {
Main.instance.startLevelOne();
});
trace(Main.instance); // I assume Main.instance is null
}
what about if you add the listener in another method in LevelBrief like :
public function registerListeners():void{
trace("Main.instance == null? -> " + (Main.instance == null)); //not null here if called later.
startBut.addEventListener('click', function() {
Main.instance.startLevelOne();
});
}

Multiple dispatchHandler Actionscript 3

I want to dispatch an Event 2 times:
I have a MainClass, a SecondClass and a ThirdClass.
In the ThirdClass, there's a clickEvent. I dispatch it to the SecondClass:
this.addEventListener(MouseEvent.CLICK, clickHandler);
public static const CLICKED_HANDLER:String = "clickedHandler";
public function clickHandler(e:MouseEvent):void {
dispatchEvent(new Event(CLICKED_HANDLER));
}
I catch and throw it in the SecondClass:
object.addEventListener(ThirdClass.CLICKED_Handler, clickedEventListener);
public static const CLICKED_HANDLER:String = "clickedHandler";
public function clickedEventListener(e:Event):void {
dispatchEvent(new Event(CLICKED_HANDLER));
}
And this I catch in the MainClass:
object.addEventListener(SecondClass.CLICKED_HANDLER, clickedEventListener);
public function clickedEventListener(e:Event):void {
trace("click");
}
But it wouldn't work... What am I doing wrong?
And how could I get information about the object of the ThirdClass that's clicked?
(Normaly with 1 dispatchEvent, it's with:
var thirdClassObject:ThirdClass = e.currentTarget as ThirdClass;
in the clickHandler method, but how to do this with 2 dispatchEvents?)
So: I want to know in my MainClass which ThirdClass-object is clicked.
Thanks a lot!
When you dispatch an event, the target property references the dispatcher.
What you apparently need is passing a reference to third class along with the event to use in the event handler of main class.
You have several options to achieve this.
If a dispatcher instance is on the display list, use event bubbling to handle events in parent display objects. Refer to the second argument of Event constructor and Event.bubbles property. This way you can subscribe to any events of child objects in a parent, and check Event.target property in your event handler. Most of mouse events like MouseEvent.CLICK are bubbling by default. So you could just listen to them and check targets in main class.// inside MainClass
// notice: we are subscribing to MainClass instance
this.addEventListener(MouseEvent.CLICK, clickedEventListener);
public function clickedEventListener(e:MouseEvent):void {
trace(e.target); // the target is what was actually clicked
// you may also notice the difference between e.target and e.currentTarget
}
If you still want to use CLICKED_HANDLER event, you may do so as follows:// inside ThirdClass
dispatchEvent(new Event(CLICKED_HANDLER, true));
// ---------------------------------------^
Remember: this will only work for display list members.
See the Event flow article on adobe.com.
Create a custom event class and place reference there. (And you can also use event bubbling with custom event classes.) This way you will dispatch your event as// inside SecondClass
public function clickedEventListener(e:Event):void {
var customEvent:CustomEventClass = new CustomEventClass(CLICKED_HANDLER);
customEvent.customTarget = e.target;
dispatchEvent(customEvent);
}
// inside MainClass
public function clickedEventListener(e:CustomEventClass):void {
trace(e.customTarget );
}
First, define what do you mean by "it wouldn't work..."?
Does the SecondClass receive the event at all? If so, you may try this:
//SecondClass
public function clickedEventListener(e:Event):void {
dispatchEvent(e);
}
//MainClass
object.addEventListener(ThirdClass.CLICKED_HANDLER, clickedEventListener);

How do I use mouse event listeners outside of the document class?

Sorry if this is a newbie question but: how do I use mouse event listeners outside of my document class. I have the following simplified document class:
public class Main extends Sprite
{
public var realMain:State1 = new State1();
public function Main():void
{
addChild(realMain);
//stage.addEventListener(MouseEvent.CLICK, handleClick);
}
/*
*private function handleClick(e:MouseEvent):void
*{
* trace("Main");
*}
*/
}
and I want to be able to use the same stage.addEventListener functionality commented above in this class:
public class State1 extends Sprite
{
public function State1()
{
addEventListener(MouseEvent.CLICK, handleClick);
}
private function handleClick(e:MouseEvent):void
{
trace("State1");
}
}
However, this doesn't work in its current state; "State1" isn't outputted. I have seen references to the use of the Event.ADDED_TO_STAGE event, but am not sure if that is what I need to do, or how to use it. Again, I'm sorry if it has been answered before, I may have seen the question and the answer before but I just am not very good at understanding what is an answer to my questions in as3 yet.
Your State1 is blank so it can't take any mouse events. Try drawing something there and see how it works.
public function State1()
{
var g:Graphics = graphics;
g.beginFill(0);
g.drawRect(0, 0, 200, 200);
g.endFill();
addEventListener(MouseEvent.CLICK, handleClick);
}
If you just want a blank object, then just draw a transparent rectangle there (pass 0 as second argument to beginFill()).

Flash: Listen to all events of a type with one eventlistener

It's not a matter of life or death but I wonder if this could be possible:
I got a couple of events from one type of custom event (FormEvent) now I got a FormListener that listens to all those events and handles them according to the event type. Instead of adding one eventListener at the time I wish to add all events at once.
so now it looks like this:
private function addListeners():void {
addEventListener(FormEvent.SHOW_FORM, formListener);
addEventListener(FormEvent.SEND_FORM, formListener);
addEventListener(FormEvent.CANCEL_FORM, formListener);
}
private function formListener(event:formEvent):void {
switch(event.type){
case "show.form":
// handle show form stuff
break;
case "send.form":
// handle send form stuff
break;
case "cancel.form":
// handle cancel form stuff
break;
}
}
but instead of adding every event one at the time I would rather be doing something like
private function addListeners():void {
addEventListener(FormEvent.*, formListener);
}
I wonder if something like this is possible, i would love it. I work with loads of events :)
You only really need one event listener in this case anyhow. That listener will be listening for any change with the form and a parameter equal to what the change was becomes available to the event listener function. I will show you, but please remember that this is a pseudo situation and normally I wouldn't dispatch an event off of something as simple as a method call because the dispatch is implied so there is no real need to listen for it.
First the Custom Event
package com.yourDomain.events
{
import flash.events.Event;
public class FormEvent extends Event
{
//Public Properties
public static const CANCEL_FORM:int = "0";
public static const SHOW_FORM:int = "1";
public static const SEND_FORM:int = "2";
public static const STATE_CHANGED:String = "stateChanged";
//Private Properties
private var formState:int;
public function FormEvent(formState:int):void
{
super(STATE_CHANGED);
formState = formState;
}
}
}
So we have just created our custom event class and we have set it up so that we can catch the state through the listener function as I will demonstrate once done with the pseudo form class that will dispatch the for said custom event.
Remember that this is all hypothetical as I have no idea what your code looks like or how your implementing things. What is important is to notice that when I dispatch the event I need to send a parameter with it that reflects what the new state is.
package com.yourDomain.ui
{
import flash.events.Event;
import flash.events.EventDispatcher;
import com.yourDomain.events.FormEvent;
public class Form extends EventDispatcher
{
public function Form():void
{
//Anything you want form to do upon instantiation goes here.
}
public function cancelForm():void
{
dispatchEvent(new Event(FormEvent.CANCEL_FORM);
}
public function showForm():void
{
dispatchEvent(new Event(FormEvent.SHOW_FORM);
}
public function sendForm():void
{
dispatchEvent(new Event(FormEvent.SEND_FORM);
}
}
}
And finally we create the document class that will listen for it. Please know that I realize it isn't logical to create a listener that fires when you call a method of a class because you obviously know you called the method, but for this example it will due.
package com.yourDomain.ui
{
import com.yourDomain.ui.Form;
import com.yourDomain.events.FormEvent;
//Form is in the same package so we need not import it.
public class MainDocumentClass
{
private var _theForm:Form;
public function MainDocumentClass():void
{
_theForm = new Form();
_theForm.addEventListener(FormEvent.STATE_CHANGED, onFormStateChange, false, 0, true);
/*
The following three method calls each cause the
FormEvent.STATE_CHANGE event to be dispatched.
onFormStateChange is notified and checks what
the last change actually was.
*/
_theForm.cancelForm();
_theForm.showForm();
_theForm.sendForm();
}
private function onFormStateChange(e:FormEvent):void
{
switch(e.formState)
{
case CANCEL_FORM:
trace('The form was canceled');
break;
case SHOW_FORM:
trace('The form was revealed');
break;
case SEND_FORM:
trace('The form was sent');
break;
}
}
}
}
I hope that this was helpful, its late and I may have to revise some things later, but this should help get an understanding of how to make your own events and to customize how things work.
I don't know of any routines that let you do that directly, but you could write your own. The syntax here won't be perfect, but here's a first pass:
private function addMultipleEventListeners( evts:Array, callback:function ):void
{
for each( var evt:Event in evts )
{
addEventListener( evt, callback );
}
}
You could then call that routine like so:
var evts:Array = [ FormEvent.SHOW_FORM, FormEvent.SEND_FORM, FormEvent.CANCEL_FORM ];
addMultipleEventListeners( evts, formListener );