AS3: setSelection Up Arrow Override - actionscript-3

I would like to focus on the end of a TextField when the up arrow is pressed. I'm using:
txt.setSelection(txt.text.length,txt.text.length);
This works great for any key except the up arrow. I believe that the up arrow automatically sets selection to the beginning of a TextField when it is in focus. How can I override this default behaviour?

I wanted to change the behavior of the home key, this is how I did it :
(The following code should essentially disable the HOME key but can be modified to make it do anything)
// Create two variables two remember the TextField's selection
// so that it can be restored later. These varaibles correspong
// to TextField.selectionBeginIndex and TextField.selectionEndIndex
var overrideSelectionBeginIndex:int = -1;
var overrideSelectionEndIndex:int;
// Create a KEY_DOWN listener to intercept the event ->
// (Assuming that you have a TextField named 'input')
input.addEventListener(KeyboardEvent.KEY_DOWN, event_inputKeyDown, false, 0, true);
function event_inputKeyDown(event:KeyboardEvent):void{
if(event.keyCode == Keyboard.HOME){
if(overrideSelectionBeginIndex == -1){
stage.addEventListener(Event.RENDER, event_inputOverrideKeyDown, false, 0, true);
stage.invalidate();
}
// At this point the variables 'overrideSelectionBeginIndex'
// and 'overrideSelectionEndIndex' could be set to whatever
// you want but for this example they just store the
// input's selection before the home key changes it.
overrideSelectionBeginIndex = input.selectionBeginIndex;
overrideSelectionEndIndex = input.selectionEndIndex;
}
}
// Create a function that will be called after the key is
// pressed to override it's behavior
function event_inputOverrideKeyDown(event:Event):void{
// Restore the selection
input.setSelection(overrideSelectionBeginIndex, overrideSelectionEndIndex);
// Clean up
stage.removeEventListener(Event.RENDER, event_inputOverrideKeyDown);
overrideSelectionBeginIndex = -1;
overrideSelectionEndIndex = -1;
}

there is the Prevent Default (livedocs) function you can apply to the action if it is cancelable (which i presume it would be) otherwise you can try and catch it with stopPropagation instead:
This hasnt been tested, but should look something like:
function buttonPress(ev:KeyboardEvent):void{
txt.setSelection(txt.text.length,txt.text.length);
ev.preventDefault();
}

Related

Remember component state after returning from frame change?

I have a frame on the main timeline with many components such as comboboxes, steppers and dynamic/input textboxes.
After going to another frame and then later back to this frame, the components reset to how they were initially.
Is there a way to have it remember the state it is being left in, so it can return to the same state when going back?
You could have a frame extend over every frame and have individual key frames in another layer like so:
and just leave the extended frame empty but hold variables to tell a MovieClip what frame it should go to when it is loaded again.. or any other values
e.g.
var MovieClip1Frame:int = 1; // in the extended frame
then
MovieClip1.gotoAndStop(MovieClip1Frame); // in the frame the MovieClip is held
then just each time you leave the frame update the variable like:
MovieClip1Frame = MovieClip1.currentFrame();
Not sure how efficient that is but it works.
You should read answers more thoroughly. As I already said, when you need to do a quantity of similar operations it is always loop. If the objects you work with are not exactly similar, you are responsible to find a way in which they are similar and make a use of it.
// *** BEGINNING OF INITIALIZATION *** //
var Store:Object;
// First, you need to figure, if you are entering
// the frame for the first time or not.
if (Store)
{
// Object is initialized, it's second time.
processAll(restoreOne);
}
else
{
Store = new Object;
// Store the initial values.
processAll(storeOne);
}
// Subscribe all components to the CHANGE event.
processAll(subscribeOne);
// *** END OF INITIALIZATION *** //
// This function loops through all the display children
// and applies a given method to each one of them.
function processAll(processOne:Function):void
{
// Backward loop is faster than forward because of condition.
// Comparing iterator to fixed number is faster than to variable.
// Not that you would notice any performance boost, but still.
for (var i:int = numChildren - 1; i >= 0; i--)
{
// Get the reference to the child object.
var aChild:DisplayObject = getChildAt(i);
// Apply the given method to the child.
processOne(aChild);
}
}
// Subscribes one target to CHANGE event.
// I cannot guarantee they all dispatch it and
// not some other event, HAVE IT AS A GUIDELINE.
function subscribeOne(target:DisplayObject):void
{
// Figure if it is one of your components.
if (aChild is NumericStepper)
{
aChild.addEventListener(Event.CHANGE, onChange);
}
else if (aChild is ComboBox)
{
aChild.addEventListener(Event.CHANGE, onChange);
}
else if (aChild is TextInput)
{
aChild.addEventListener(Event.CHANGE, onChange);
}
// And so on, for every component type you have.
}
// Every standard component dispatches CHANGE event
// that indicates the component's state/value was changed.
function onChange(e:Event):void
{
// Store the changed value of the component
// that dispatched the CHANGE event.
storeOne(e.target as DisplayObject);
}
// Stores the value/state of a given component.
function storeOne(target:DisplayObject):void
{
// As there are different components, you should
// differ the ways you record their states/values.
if (target is NumericStepper)
{
Store[target.name] = (target as NumericStepper).value;
}
else if (target is ComboBox)
{
Store[target.name] = (target as ComboBox).selectedIndex;
}
else if (target is TextInput)
{
Store[target.name] = (target as TextInput).text;
}
// And so on, for every component type you have.
}
// Restores value/state of a given component.
function restoreOne(target:DisplayObject):void
{
// Get the reference to the child object.
var aChild:DisplayObject = getChildAt(i);
// Figure if it is one of your components.
if (aChild is NumericStepper)
{
(aChild as NumericStepper).value = Store[aChild.name];
}
else if (aChild is ComboBox)
{
(aChild as ComboBox).selectedIndex = Store[aChild.name];
}
else if (aChild is TextInput)
{
(aChild as TextInput).text = Store[aChild.name];
}
// And so on, for every component type you have.
}
One more thing to heed. Some components are not initialized right away and take one extra frame to get ready. In this case they can NOT accept the stored values until the next frame.

Disable a function temporarily as3

On my screen are tiles generated from an array. I have a mouse roll over function called rollover, that adds a movieclip that highlights the edge of the tiles that I am currently on. I want it so that once I click a tile, the roll over function doesn't work until another button is clicked. I tried putting removeEventListener for the roll over function in the click function, doesn't seem to work. How would I go about this if possible?
I will post more information if needed.
function rollover(event:MouseEvent)
{
var tileHover = true;
if (tileHover == true){
(event.currentTarget as Tile).outline.gotoAndStop("hover");
}
if(tileHover == false){
(event.currentTarget as Tile).outline.gotoAndStop("blank");
}
}
Below is the mouseclick function
function mouseclick(event:MouseEvent)
{
tileHover = false;
if (tileHover == false){
tile_MC.removeEventListener(MouseEvent.ROLL_OVER, rollover)
}
}
See below. You set a property and immediately check what the value of that property is. It will always be true because you just set it as true.
var tileHover = true;
if (tileHover == true){
(event.currentTarget as Tile).outline.gotoAndStop("hover");
}
Also, don't forget your data types.
I think you need to have (event.currentTarget as Tile).outline.gotoAndStop("blank"); in mouseclick.
Also, I assume tilehover is some global variable used for tracking the hover state. Explicitly setting it true/false in the handler is just for debugging purposes!!

Simulate Mouse Click in AS3

I am working on an AS3 project and I am struggling with one particularly fragile part which will need a lot of refactoring in the near future. Just unit testing separate classes in isolation does not catch all issues we are running into. For example, we might forget to disable mouse events on a transparent overlay and thereby block all clicks on a button. Therefore, I am trying to write a test that simulates real user input.
I have tried to manually send a MouseEvent to the stage at the correct position:
stage.dispatchEvent(new MouseEvent(MouseEvent.CLICK, true, true, 380, 490, stage));
Since the stage has no click event handler, I expected the event to propagate through the hierarchy to the button that will actually handle it (as it does when I physically click the mouse). However, it doesn't.
I know that I could just dispatch the event on the button, but that will not detect if the object is somehow obstructed. Is there some way to simulate mouse events, such that they will properly propagate through the hierarchy?
Edit:
I managed to do it by re-implementing the propagation behavior of Flash:
Edit 2:
My previous solution didn't work if there was a partly transparent overlay with a click handler, like a Sprite with a few Shapes in it. The problem is that the hitTestPoint method returns true even if the object in question is completely transparent at that point. Therefore, I modified it to check the actual pixel value:
private function clickObject(obj:DisplayObject) : void
{
var relPos:Point = new Point(obj.width / 2, obj.height / 2);
var globalPos:Point = obj.localToGlobal(relPos);
simulateClick(obj.stage, globalPos);
}
private function simulateClick(obj:InteractiveObject, globalPos:Point) : Boolean
{
// first, check if we have any children that would rather handle the event
var container:DisplayObjectContainer = obj as DisplayObjectContainer;
if (container != null)
{
if (container.mouseChildren)
{
for (var i:int = 0; i < container.numChildren; ++i)
{
var child:DisplayObject = container.getChildAt(i);
var interactive:InteractiveObject = child as InteractiveObject;
if (interactive != null)
{
if (simulateClick(interactive, globalPos))
{
// if we have found a handler in the children, we are done
return true;
}
}
}
}
}
if (!obj.mouseEnabled) {
return false;
}
if (obj.hitTestPoint(globalPos.x, globalPos.y))
{
var localPos:Point = obj.globalToLocal(globalPos);
// check if object is visible at the clicked location
var pixel:BitmapData = new BitmapData(1, 1);
pixel.draw(obj, new Matrix(1, 0, 0, 1, -localPos.x, -localPos.y));
var color:uint = pixel.getPixel32(0, 0);
if ((pixel.getPixel32(0, 0) & 0xff000000) != 0)
{
// if yes, dispatch the click event
var e:MouseEvent = new MouseEvent(MouseEvent.CLICK, true, true, localPos.x, localPos.y, obj);
obj.dispatchEvent(e);
return true;
}
}
return false;
}
Unfortunately, there is still at least one case not covered: If the object is a mask for another object. I have no idea how to check for this, since it could be mask anywhere in the display hierarchy. I would have to traverse the whole tree and check every single display object to find this out.
So, my question remains: Isn't there an easier way to do this?
I've had issues with events in AS3 as well. I've found that the best way is to have the eventListeners added to the same object that's dispatching the events. In your case, adding the .addEventListener to the stage and sending the function as a function on a child clip. eg:
stage.addEventListener(MouseEvent.CLICK, object.object.clicked);
I hope this may help. I've used this method with success in the past.
You can use stage.getObjectsUnderPoint(new Point(pointerX , pointerY )); function , that will return You array with objects . Than remove overlay object and last instance in array should be deepest DisplayObject.
note: last instance can be graphic or such thing , so You should loop through parent objects to find nearest InteractiveObject .
Also , dont forget that parent objects can have mouseChildren = false or mouseEnabled = false

ActionScript MouseEvent's CLICK vs. DOUBLE_CLICK

is it not possible to have both CLICK and DOUBLE_CLICK on the same display object? i'm trying to have both for the stage where double clicking the stage adds a new object and clicking once on the stage deselects a selected object.
it appears that DOUBLE_CLICK will execute both itself as well as the first CLICK functions in the path toward DOUBLE CLICK (mouse down, mouse up, click, mouse down, mouse up, double click).
in other languages i've programmed with there was a built-in timers that set the two apart. is this not available in AS3?
UPDATE
here's some code. essentially what i would like is have one or the other, not both with double click
stage.doubleClickEnabled = true;
stage.addEventListener(MouseEvent.DOUBLE_CLICK, twoClicks, false, 0, true);
stage.addEventListener(MouseEvent.CLICK, oneClick, false, 0, true);
function oneClick(evt:MouseEvent):void
{
trace("One CLICK");
}
function twoClicks(evt:MouseEvent):void
{
trace("Two CLICKS");
}
//oneClick trace = "One CLICK"
//twoClicks trace = "One CLICK Two CLICKS" (instead of just Two CLICKS)
Well, you could use setTimeout and clearTimeout.
It'd look something like this:
const var DOUBLE_CLICK_SPEED:int = 10;
var mouseTimeout;
function handleClick(evt:MouseEvent):void {
if (mouseTimeout != undefined) {
twoClicks();
clearTimeout(mouseTimeout);
mouseTimeout = undefined;
} else {
function handleSingleClick():void {
oneClick();
mouseTimeout = undefined;
}
mouseTimeout = setTimeout(handleSingleClick, DOUBLE_CLICK_SPEED);
}
}
function oneClick(evt:MouseEvent):void {
trace("One CLICK");
}
function twoClicks(evt:MouseEvent):void {
trace("Two CLICKS");
}
stage.addEventListener(MouseEvent.CLICK, handleClick, false, 0, true);
Did you set .doubleClickEnabled to true?
You should also take a look here.
Great answer Wallacoloo - thanks for that. I have just implemented your solution and refined a few points, so I'd thought I'd put it here for future reference (and of course the benefit of the overflow community!). Firstly, I couldn't test for undefined on the uint returned by setTimeout, so I replaced the undefined conditional with an == 0 conditional. Secondly, I wanted to commit the logic of a single click instantaneously (just makes for a more pleasant user interface), so I've done a bit of reshuffling:
if (mouseTimeout != 0) {
// clicked within the timeout, handle as double click
// rollback single click logic
rollbackSingleClickHandler(e);
// commit double click logic
dblClickHandler(e);
clearTimeOut(mouseTimeout);
mouseTimeout = 0;
} else {
// first click of a click sequence
// commit single click logic
singleClickHandler(e);
function clearTime():void {
mouseTimeout = 0;
}
// register a timeout for a potential double click
mouseTimeout = setTimeout(clearTime, DOUBLE_CLICK_SPEED);
}

Actionscript3: add and remove EventListeners (with dynamic name and dynamic variables)

I am making a boardgame in flash Action Script 3. Each position on the board is a buttons like this: button_1_1, button_1_2 etc. Whenever a character is selected you want to move it so the script has to add event listeners for positions around the selected unit
// This function adds or deletes an event listener
function listentoButton (isTrue:int, position_x:int, position_y:int):void {
var myFunction:Function = new Function;
myFunction = function ():void {userClickedPosition(position_x, position_y)};
if (isTrue == 1) {
this["button_position_"+(position_x)+"_"+(position_y)].addEventListener(MouseEvent.CLICK, myFunction);
} else {
this["button_position_"+(position_x)+"_"+(position_y)].removeEventListener(MouseEvent.CLICK, myFunction);
}
}
In the rest of the code I have:
function userClickedPosition(position_x:int, position_y:int)
it selects or deselect a unit
function selectUnit(position_x:int, position_y:int):
it uses the listentoButton(1) function to add 8 listeners (the positions around the clicked unit)
function deselectUnit(position_x:int, position_y:int):
it uses the listentoButton(0) function to delete 8 listeners (the positions around the clicked unit)
My question: adding eventlisteners is no problem but removing them dont seem to work? What did I do wrong?
When you go to remove the event, you are using a new instance of myFunction, not the same one you added it with. You either need to declare the function like you would any other function, and use the event args to examine the button's position like. I Think you want the stageX and stageY properties:
http://www.adobe.com/livedocs/flex/3/langref/flash/events/MouseEvent.html
// This function adds or deletes an event listener
function listentoButton (isTrue:int, position_x:int, position_y:int):void {
if (isTrue == 1) {
this["button_position_"+(position_x)+"_"+(position_y)].addEventListener(MouseEvent.CLICK, myFunction);
} else {
this["button_position_"+(position_x)+"_"+(position_y)].removeEventListener(MouseEvent.CLICK, myFunction);
}
}
function myFunction(eventArg:MouseEvent):void {
//use MouseEvent
};
Or you can create a little MyFunctionParameters class to hold the coordinate information and create a new instance of that class, add it to a collection indexed by the x and y coordinates, and later when you go to remove the event, you would lookup the MySpaceParameters instance in the collection, based on x and y coordinates, then use that to remove function.
class MyFunctionParameters
{
public x:int;
public y:int;
function myFunction(eventArg:MouseEvent):void {
userClickedPosition(x,y);
};
}