[RESOLVED] Clicking on a button destroys KeyboardEvent listener?
I have a basic presentation which will have basic interactivity for the user. i.e. The user clicks on a play button, and the presentation plays, the user clicks on a back button, and the playhead steps back through the animation.
I also want to provide keyboard shortcuts to do the same thing, so the user can also hit the spacebar to play the presentation, or press the left arrow key to step back, etc.
I've successfully gotten both the click interactivity and keyboard interactivity to work... but only separately, when I try to add them to the same file, it breaks.
I also think I've figured out why. The problem seems to be that if I click a button that is only on the timeline for SOME of the frames, the KeyboardEvent listener no longer triggers.
I have a rewind button that will jump the playhead to the start of the presentation. However, from a use design standpoint, I really only want the rewind button visible/clickable if the user has advanced a bit into the presentation (i.e. frame 2 or beyond) because having the rewind button active on the first frame of the presentation can be confusing because it makes it appear to the user as though they can still rewind further, even though they've reached the beginning. I want to take a similar approach to a play button... as long as there are frames to play, the play button should be active; however, once you reach the end, the play button should dim down and not be clickable anymore, which serves as a visual cue to the user that they have reached the end of the presentation.
So, previously (in AS 2.0) this was not a problem. On the frame I wanted the rewind button to be "disabled", I would just turn the Button Symbol into a Graphic Symbol which would make it unclickable and remove any script, and then I would dim the Alpha of the Graphic. So, on frame 1, the rewind button would actually just be a transparent Graphic, and then once the playhead goes to Frame 2, it would be a Button symbol that could be clicked.
This technique apparently will break the new event model in AS3. If you look at the FLA I have attached, you can see that when you first "Test Movie"... any keyboard press outputs a trace statement that shows which key was pressed. However, if you hit the play button to play a little of the timeline, and then click the rewind button (instance name of rewind_btn) which jumps the playhead to frame 1 (where rewind_btn doesn't exist, because it's a Graphic now).
Now my Keyboard listener doesn't trigger!
I've even tried testing to see if the KeyboardEvent listener still exists by using the hasEventListener("keyUp"), and it returns as true, so the listener is there, it just doesn't trigger anymore!?
What am I doing wrong? How would I go about solving this problem? Because I can see this being a huge issue in a more complicated project, because I could see scenarios where you only want certain buttons to exist on certain frames! I just don't understand why a click event would mess with a key up event....
Code:
// Add keyboard listener to stage
this.stage.addEventListener(KeyboardEvent.KEY_UP, fOnKeyUp);
// Add keyboard listener to stage
this.stage.addEventListener(MouseEvent.CLICK, fOnClick);
function fOnKeyUp(evt:KeyboardEvent):void {
trace("Key Released: " + evt.keyCode);
}
function fOnClick(evt:MouseEvent):void {
trace("Button Clicked: " + evt.target.name);
switch(evt.target.name){
case "play_btn":
play();
break;
case "rewind_btn":
gotoAndStop(1);
break;
case "trace_btn":
trace(this.stage.hasEventListener("keyUp"));
break;
}
}
Apparently... and I admit this little bit of info IS pretty obvious and self-evident now that I think about it... but an Object (Stage, MovieClip, Button, whatever) MUST have FOCUS to listen for a KeyboardEvent.
So, by clicking on the rewind button, the rewind_btn instance gets focus...
(stage.focus will return as the rewind_btn instance).
So, it seems that since the rewind_btn has focus, when I send the playhead to frame 1, stage.focus still returns the rewind_btn, but it doesn't exist on the stage anymore... so Flash is telling me that a non-existant object has focus... which is... weird. But I get it.
So, I can work around the focus issue by setting stage.focus = stage; when I need to.
HOWEVER, this now brings up another level of confusion for me... because my KeyboardEvent listener (which is registered to the Stage) DOES trigger even though the stage doesn't have focus.
For example, if I click the play_btn, the play_btn now has focus... however, if I hit a key on the keyboard AFTER clicking the play_btn (like in the middle of the animation as the playhead is moving) ... the keyboardevent listener still triggers even though the focus is on the button!
So my previous statement:
"An object must have focus to listen for keyboard events."
Isn't really correct... it's more like:
"The object that has focus must exist for keyboard events to trigger."
This just seems really wrong/weird/illogical ... I must be missing something.
The keyboard even bubbles up from the focussed object through parents to the stage. That is how the listener works even though the stage does not have direct focus. It is also why keyboard listeners are almost always attached to the stage.
Wow, I actually understood what you just said (sort of).
So, is there a way to workaround this "bubbling" thing... because, I'm adverse to manually overriding the stage.focus property, which makes since in this simplified example because there's a small amount of interactivity (things to click on) for the user. But, if there's more complicated things, like drag and drop interface, etc., I doubt I would want to manually reset the focus to the stage... stage.focus = stage;
So, as I understand the bubbles up thing... if myPlay_btn has focus... a keyboard event will bubble up through the parents until it reaches the stage, correct?
However, if myPlay_btn has focus, but no longer exists on the timeline... it has no parents to bubble up through, and therefore the keyboard event never reaches the stage, correct?
How would you recommend dealing with the scenario where an object that doesn't exist has focus?
I'm actually posting from my phone, so I can't verify with the livedocs, but you may be able to put a stage level listener for REMOVED_FROM_STAGE and simply set stage.focus back to stage there if it is currently set to the thing leaving the stage. That will only work if that event bubbles and is fired before the actual removal. Or perhaps you could catch it on the capture phase. Otherwise, you could set a timer to periodically check that the stage.focus is set to something with a non-null stage property. Or you could put the REMOVED_FROM_STAGE listener on each thing you think might go away.
How can I check if an object in the stage.focus property is on the timeline?
Just testing stage.focus or stage.focus.name return as true, even if the object is not currently on the timeline, but testing the button name explicitly gives the desired result:
Code:
stage.focus // returns [object SimpleButton]
stage.focus.name // returns myPlay_btn
if(stage.focus){
trace("focus object exists");
}else{
trace("focus object does not exist");
}
// output: focus object exists
if(stage.focus.name){
trace("focus object exists");
}else{
trace("focus object does not exist");
}
// output: focus object exists
if(myPlay_btn){
trace("focus object exists");
}else{
trace("focus object does not exist");
}
// output: focus object does not exist
The thing is I am having exactly the same problem; my stage.addEventListener(MouseEvent.CLICK,functioncl ick) DESTROYS the stage.addEventListener(KeyboardEvent.KEY_DOWN,keyS election);
But I couldn´t understand quitely how this was resolved. Is there any way to make the keyboard be active on stage with a mouseevent?
Your mouse click listener does not destroy the keyboard listener. It will probably give keyboard focus to whatever you clicked on. In the scenario above, that object with focus is then removed from the displayList. From that point, it will still dispatch keyboard events (because it has focus), but those events will not make it to the stage (because they cannot bubble up through the displayList). The resolution is to re-set the stage.focus to the stage itself. You can set that within the mouseclick listener.