Key Events not registering in Flex AS3 - actionscript-3

I'm currently working on a project using ActionScript 3 in Flex 4.6. I was having trouble making any key events register in my script (though mouse events were fine), and debugging lead to some very bizarre results.
First of all, this is my test code:
Contents of KET.mxml:
<?xml version="1.0"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
name="Key Events Test"
backgroundColor="#000000"
horizontalAlign="center"
creationComplete="setup();"
enterFrame="updateFrame();"
paddingLeft="0"
paddingTop="0"
paddingBottom="0"
paddingRight="0"
frameRate="60"
width="960" height="720">
<mx:Script>
<![CDATA[
include "KET.as";
]]>
</mx:Script>
<mx:Canvas id="gamePanel" x="0" y="0" width="100%" height="100%" click="mouseClicked(event)" keyDown="keyDown(event)"/>
</mx:Application>
Contents of KET.as:
import flash.display.*;
import flash.events.*;
import mx.events.*;
import mx.controls.*;
public static const SCREEN_WIDTH:int = 960;
public static const SCREEN_HEIGHT:int = 720;
private var initializationCompleted:Boolean = false;
public var screenBuffer:BitmapData;
private var trigger:int = 0;
public function setup():void {
screenBuffer = new BitmapData(SCREEN_WIDTH, SCREEN_HEIGHT, false, 0x00000000);
initializationCompleted = true;
}
private function updateFrame():void {
if (!initializationCompleted) {
return;
}
gamePanel.graphics.clear();
gamePanel.graphics.beginBitmapFill(screenBuffer, null, false, false);
gamePanel.graphics.drawRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
gamePanel.graphics.endFill();
if (trigger == 0) {
screenBuffer.fillRect(new Rectangle(480, 360, 200, 200), 0xFF000000);
} else if (trigger == 1) {
screenBuffer.fillRect(new Rectangle(480, 360, 200, 200), 0xFFFF0000);
} else if (trigger == 2) {
screenBuffer.fillRect(new Rectangle(480, 360, 200, 200), 0xFF0000FF);
}
}
private function mouseClicked(event:MouseEvent):void {
trigger = 1;
}
private function keyDown(event:KeyboardEvent):void {
trigger = 2;
}
The code above, when ran, is supposed to give a black screen at the start, on which a red rectangle appears once the user clicks on it and a blue one once the user presses a key. However, the key press is completely ignored.
While debugging this, I started getting some very strange results. A tutorial I have been loosely following found here shows how to check for keyboard events. Now upon following the same instructions, or even downloading and compiling the code given in the tutorial I get a fully working project, but with no keyboard controls. Now this happens regardless of how I run the resulting SWF, be it in a flash interpreter, through a browser locally or from a server. At first I thought it was due to the SWF given in the tutorial being compiled by some legacy version of Flex, but after downloading the SWF from the website directly I get the same results. The only place where it works is on the original website.
So what am I doing terribly wrong here? Is there a bug in my current solution or am I just taking an incredibly wrong approach?
Thanks in advance!!

I have not tested your project, but if memory serves, a component needs to receive focus to handle keyboard events.
By simply adding gamePanel.setFocus(); at the end of the updateFrame() function, I think it will work.
Like this:
private function updateFrame():void {
if (!initializationCompleted) {
return;
}
gamePanel.graphics.clear();
gamePanel.graphics.beginBitmapFill(screenBuffer, null, false, false);
gamePanel.graphics.drawRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
gamePanel.graphics.endFill();
if (trigger == 0) {
screenBuffer.fillRect(new Rectangle(480, 360, 200, 200), 0xFF000000);
} else if (trigger == 1) {
screenBuffer.fillRect(new Rectangle(480, 360, 200, 200), 0xFFFF0000);
} else if (trigger == 2) {
screenBuffer.fillRect(new Rectangle(480, 360, 200, 200), 0xFF0000FF);
}
gamePanel.setFocus();
}
Another option could be to add an EventListener for the whole application (be careful, if you use text fields, this EventListener will be triggered during text input).
public function setup():void {
screenBuffer = new BitmapData(SCREEN_WIDTH, SCREEN_HEIGHT, false, 0x00000000);
systemManager.stage.addEventListener(KeyboardEvent.KEY_DOWN, keyDown);
initializationCompleted = true;
}
I hope this will help you.

Related

Actionscript 3 and Flex 4 Zoom Gesture on SWFLoader

I seem to be having issue with Zoom Gestures on a SWFLoader. I have an swf file which is a floor plan of a home, I want the user to be able to zoom in and out with two finger touch, the following code below is what I tried and does not work. When I test on a touchscreen, it does not zoom when I place two fingers inside the SWF and try to zoom in.
<s:SWFLoader id="floorplanImage" source="#Embed('assets/test2.swf')" width="100%" height="100%" smoothBitmapContent="true" horizontalAlign="center" />
here is my actionscript 3 code
import flash.ui.Multitouch;
import flash.ui.MultitouchInputMode;
Multitouch.inputMode = MultitouchInputMode.GESTURE;
import flash.events.Event;
public var selectedItem:Object;
public function init(): void
{
floorplanImage.addEventListener(TransformGestureEvent.GESTURE_ZOOM , onZoom);
}
public function onZoom (e:TransformGestureEvent):void{
floorplanImage.scaleX *= e.scaleX;
floorplanImage.scaleY *= e.scaleY;
}
Please Help!
UPDATE
I am going the route of gestouch, however with this code, I CANNOT zoom in or out on an SWF. With a regular image it works, but not with SWF unless I am missing something. Here is my code:
<mx:Script>
<![CDATA[
import org.gestouch.events.GestureEvent;
import org.gestouch.gestures.TransformGesture;
private var _zoom:TransformGesture;
[Embed(source="assets/test2.swf")]
private var myClass:Class;
private var myMovieClip:MovieClip;
private function initModel():void
{
myMovieClip = MovieClip(new myClass());
swfcontainer.addChild(myMovieClip);
_zoom = new TransformGesture(swfcontainer);
_zoom.addEventListener(org.gestouch.events.GestureEvent.GESTURE_BEGAN, onGesture);
_zoom.addEventListener(org.gestouch.events.GestureEvent.GESTURE_CHANGED, onGesture);
}
private function onGesture(event:org.gestouch.events.GestureEvent):void
{
const gesture:TransformGesture = event.target as TransformGesture;
var matrix:Matrix = swfcontainer.transform.matrix;
// Panning
matrix.translate(gesture.offsetX, gesture.offsetY);
swfcontainer.transform.matrix = matrix;
if (gesture.scale != 1)
{
// Scale and rotation.
var transformPoint:Point = matrix.transformPoint(swfcontainer.globalToLocal(gesture.location));
matrix.translate(-transformPoint.x, -transformPoint.y);
matrix.scale(gesture.scale, gesture.scale);
matrix.translate(transformPoint.x, transformPoint.y);
swfcontainer.transform.matrix = matrix;
}
}
]]>
</mx:Script>
<mx:Image id="swfcontainer" horizontalAlign="center" width="100%" height="100%" />
When I use this with a regular image, it still does not work properly...it doesn't keep the image center when zooming, it does not let me zoom in, only out and when I first use it, it moves the image to the right. HOW COME THIS IS SOOOOOO HARD?
Please keep in mind I am very very new to Adobe Flex and Actionscript, so please make your answers as clear as possible.
Your code has no problem,I'll explain what I did for my project. Maybe it helped, i have Window design app and i load SWF into starling and have issue
with zoom and pan;
FYI: I Use starling and load SWF file with native overlay in starling and zoom pan work
_mLoader = new Loader();
var mRequest:URLRequest = new URLRequest("drawer.swf" + "?nf=" + getTimer());
_mLoader.contentLoaderInfo.addEventListener(flash.events.Event.COMPLETE, onCompleteHandler);
_mLoader.load(mRequest);
private function onCompleteHandler(loadEvent:flash.events.Event):void
{
_mLoader.contentLoaderInfo.removeEventListener(flash.events.Event.COMPLETE,
Starling.current.nativeOverlay.addChild(LoaderInfo(loadEvent.currentTarget).content);
}
And because the library touching the flash was weak i use above lib,and this is to Strong
first of all test above lib maybe that's work Without Starling
better GESTURE lib than flash lib
my zoom code with this lib
var _zoom:
_zoom = new TransformGesture(this);
_zoom.addEventListener(GestureEvent.GESTURE_BEGAN, onGesture);
_zoom.addEventListener(GestureEvent.GESTURE_CHANGED, onGesture);
private function onGesture(event:org.gestouch.events.GestureEvent):void
{
const gesture:TransformGesture = event.target as TransformGesture;
var matrix:Matrix = container_movieclip.transform.matrix;
if (container_movieclip.scaleX!=1 || container_movieclip.scaleY!=1)
{
matrix.translate(gesture.offsetX, gesture.offsetY);
container_movieclip.transform.matrix = matrix;
}
if (gesture.scale != 1)
{
var transformPoint:Point = matrix.transformPoint(container_movieclip.globalToLocal(gesture.location));
matrix.translate(-transformPoint.x, -transformPoint.y);
matrix.scale(gesture.scale, gesture.scale);
matrix.translate(transformPoint.x, transformPoint.y);
container_movieclip.transform.matrix = matrix;
}
}
I hope I could help
simply your issue solved if you changed multiinputMethod to touchpoint
Multitouch.inputMode=MultitouchInputMode.TOUCH_POINT;

as3 How to dispatch a specific keyboard event

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!

How to correctly load und unload swf galleries for PORTFOLIO web site?

My problem looks like: I have five buttons and four of them are loading four different swf galleries into the main swf, the last one unloads all galleries. Each of them has exactly the same code, the only differeces are the locations of jpg files and swf files. Also each o them has the listeners ADDTOSTAGE and REMOVEFROMSTAGE, because i'm using the stage inside the gallery/swfs.
So when I enter the menu and load the first swf i don't get any output errors, when I click the btn_back he unloads the swf properly with no errors. And so on but only when i load only one gallery.
But when I, after loading the first gallery, want to click on another button and load second gallery then i got this error in output, so many times as many pictures are loaded inside the loaded swf/gallery:
TypeError: Error #1009: Nie można uzyskać dostępu do właściwości lub metody dla odniesienia do obiektu null.
at DocObject/resizeMyDoc()
at DocObject/onResizeDoc()
at flash.events::EventDispatcher/dispatchEventFunction()
at flash.events::EventDispatcher/dispatchEvent()
at flash.display::Stage/dispatchEvent()
at Miniaturka/onAddedToStage()
at flash.display::DisplayObjectContainer/addChild()
at Miniaturki/callback()
at Function/http://adobe.com/AS3/2006/builtin::apply()
at Miniaturka/onLoadComplete()
So my conclusion is that the problem is not inside the loaded swf but in the code of loading and unloading code of the mine swf. The only solution that I can find is: that when I click the second button the code should first unload the current swf, and when the unload is complete then immidietly load the another one. But I don't know how to write it. Can anyone help? So here is the code.
import flash.events.MouseEvent;
import fl.transitions.Tween;
import fl.transitions.easing.*;
import fl.transitions.TweenEvent;
import flash.display.Loader;
var _mainLoader:Loader;
var _currentContent:DisplayObject;
var url:URLRequest;
var loaded:Boolean = false;
var a:Array = new Array(s1_1_arch, s1_2_arch, s1_3_arch, s1_4_arch);
var swfList:Array = ["A0101.swf", "A0102.swf", "A0103.swf"];
//Initiate Loader, do it only once
function initiateLoader():void {
_mainLoader = new Loader();
_mainLoader.contentLoaderInfo.addEventListener(Event.COMPLETE, onComplete);
}
function loadGallery(path:String):void {
//Check if content previously loaded, unload it
if (_currentContent != null) {
removeChild(_currentContent);
_currentContent = null;
_mainLoader.unloadAndStop();
}
//Now load another one
_mainLoader.load(new URLRequest[1]);
}
function onComplete(e:Event):void {
_currentContent = _mainLoader.content;
//Add root DisplayObject of the loaded content to the display list
stage.addChild(_currentContent);
}
function samClick(e:MouseEvent):void
{
switch (e.target)
{
case s1_1_arch :
break;
case s1_2_arch :
break;
case s1_3_arch :
break;
case s1_4_arch :
break;
}
}
function samOver(e:MouseEvent):void{
switch (e.target)
{
case s1_1_arch : var sam1Ou:Tween = new Tween(s1_1_arch, "alpha", Strong.easeOut, 1, 0.2, 0.5, true);
break;
case s1_2_arch : var sam2Ou:Tween = new Tween(s1_2_arch, "alpha", Strong.easeOut, 1, 0.2, 0.5, true);
break;
case s1_3_arch : var sam3Ou:Tween = new Tween(s1_3_arch, "alpha", Strong.easeOut, 1, 0.2, 0.5, true);
break;
case s1_4_arch : var sam4Ou:Tween = new Tween(s1_4_arch, "alpha", Strong.easeOut, 1, 0.2, 0.5, true);
break;
}
}
function samOut(e:MouseEvent):void{
switch (e.target)
{
case s1_1_arch : var sam1Ou:Tween = new Tween(s1_1_arch, "alpha", Strong.easeOut, 0.2, 1, 0.5, true);
break;
case s1_2_arch : var sam2Ou:Tween = new Tween(s1_2_arch, "alpha", Strong.easeOut, 0.2, 1, 0.5, true);
break;
case s1_3_arch : var sam3Ou:Tween = new Tween(s1_3_arch, "alpha", Strong.easeOut, 0.2, 1, 0.5, true);
break;
case s1_4_arch : var sam4Ou:Tween = new Tween(s1_4_arch, "alpha", Strong.easeOut, 0.2, 1, 0.5, true);
break;
}
}
for (var i:Number = 0; i < 4; i++)
{
a[i].addEventListener(MouseEvent.CLICK, samClick);
a[i].addEventListener(MouseEvent.MOUSE_OVER, samOver);
a[i].addEventListener(MouseEvent.MOUSE_OUT, samOut);
}
Just to add...
For something like this you are better of having all code in one place. A class file will save you other frustrations later on if you're still new. Just control all elements from one place. Also consider what Nicolas Siver said in his answer..
Just a quick guide on how to setup class file. If you save as Main.as and attach to your FLA then you can paste in your timeline code here.
package
{
//IMPORTS go here
//Declare your Class (name of .as file)
public class Main extends MovieClip
{
//VARS go here
//Declare main function of your Class (must have same name as Class (.as file)
public function Main()
{
// Your main program code go here
//Something(); //example: run a function called Something
}
public function Something();
{
// Do something code
}
} //End of Class
} //End of Package
You are on the right way. But you have many problems in your code, most of your switches are redundant. By default, all galleries, that you are loading, will be loaded in child ApplicationDomains, so they are suitable for garbage collection.
If you will use single Loader it will be easier for you to manage it, and load one section in time.
//Pseudo code
Create main loader (with onComplete event handler);
Create a function for loading content, and call it.
Check if previous load operation was successful (use your flag `loaded`), if Yes - unload previous content, remove previous content from the display list;
Load new content;
After loading is completed, add content to the main display list holder, designed to show gallery content.
Also I would recommend to use TweenLite, It's also more developer friendly.
Here for you several utility functions that will help you accomplish your task:
private var _mainLoader:Loader;
private var _currentContent:DisplayObject;
//Initiate Loader, do it only once
private function initiateLoader():void {
_mainLoader = new Loader();
_mainLoader.contentLoaderInfo.addEventListener(Event.COMPLETE, onComplete);
}
private function loadGallery(path:String):void {
//Check if content previously loaded, unload it
if (_currentContent != null) {
removeChild(_currentContent);
_currentContent = null;
_mainLoader.unloadAndStop();
}
//Now load another one
_mainLoader.load(new URLRequest(path));
}
private function onComplete(e:Event):void {
_currentContent = _mainLoader.content;
//Add root DisplayObject of the loaded content to the display list
addChild(_currentContent);
}
More concrete realisation, you could use it in your frame, but don't forget to do all imports, and create all necessary display object on the scene:
//Self explanatory
var currentContent:DisplayObject;
//Initiate Loader
var mainLoader:Loader = new Loader();
mainLoader.contentLoaderInfo.addEventListener(Event.COMPLETE, onComplete);
//Create some MovieClip and give it a name, you will place loaded content there, ex: contentHolder
//Add buttons in one container (MovieClip) and give it name, ex: buttonHolder
//Give buttons some names, ex: s1Arch, s2Arch, s3Arch
//Map our buttons to external links and prepare for mouse events
var links:Object = {"s1Arch": "A0101.swf", "s2Arch": "A0102.swf", "s3Arch": "A0103.swf"};
//Register listeners, you could do it in cycle
s1Arch.addEventListener(MouseEvent.CLICK, onClickButton);
s2Arch.addEventListener(MouseEvent.CLICK, onClickButton);
s3Arch.addEventListener(MouseEvent.CLICK, onClickButton);
//Mouse events, using events bubbling
buttonHolder.addEventListener(MouseEvent.CLICK, onClickButton);
function loadGallery(path:String):void {
//Check if content previously loaded, unload it
if (currentContent != null) {
contentHolder.removeChild(currentContent);
currentContent = null;
mainLoader.unloadAndStop();
}
//Now load another one
mainLoader.load(new URLRequest(path));
}
function onClickButton(e:MouseEvent):void {
//Explanation step-by-step
//Clicked button
var button: DisplayObject = DisplayObject(e.currentTarget);
//Path to the external file
var path: String = links[button.name];
//Utility function will handle another stuff
loadGallery(path);
}
function onComplete(e:Event):void {
currentContent = mainLoader.content;
//Add root DisplayObject of the loaded content to the display list
contentHolder.addChild(currentContent);
}

Displaying a progress dialog before calling URLLoader.load in flex?

I'm developing a flex application (run in web browser), that lets the user load a number of files and then sends them all to a server in one single multipart/form-data request.
My problem is this:
For security reasons, flash requires that the user initiates the URLLoader.load action by clicking on a button. That's ok, the user clicks the save button in the application and off we go.
Unfortunately, the uploading part of the request doesn't seem to be asynchronous, but only the downloading part. The data can be any size since the user can add an arbitrary number of files, so the upload can take a while to complete.
In an ideal world I would do something like this in the click handler of the save button:
PopupManager.createPopup(this, ProgressDialog, true);
doUpload();
Unfortunately this will result in the progressdialog being shown after the upload finishes.
So, can I defer the upload start until the popup has been shown, like this?
PopupManager.createPopup(this, ProgressDialog, true);
setTimeout(doUpload, 100);
Turns out I can't do this either, because of the security reasons mentioned above. This will throw a SecurityError: Error #2176.
Basically the problem is
A popup won't be visible until the code in the current frame has completed
URLLoader.load blocks until the upload part of the request is completed
The popup arrives too late, since the time consuming part is already over.
I can't wait for the popup, since the load action has to be initiated by the user click.
Is there any way whatsoever to work around this limitation?
Here is a small application that illustrates the problem. Button two and three will throw the SecurityError.
<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx"
width="419" height="250">
<fx:Script>
<![CDATA[
import mx.core.IFlexDisplayObject;
import mx.managers.PopUpManager;
private var urlLoader:URLLoader;
private var popup:IFlexDisplayObject;
protected function sendNowButton_clickHandler(event:MouseEvent):void {
trace(">> URLLoaderSendTest.sendNowButton_clickHandler(event)");
popup = PopUpManager.createPopUp(this, ProgressDialog, true);
doLoad();
trace("<< URLLoaderSendTest.sendNowButton_clickHandler(event)");
}
protected function sendWithSetTimeoutButton_clickHandler(event:MouseEvent):void {
trace(">> URLLoaderSendTest.sendLaterButton_clickHandler(event)");
popup = PopUpManager.createPopUp(this, ProgressDialog, true);
setTimeout(doLoad, 200);
trace("<< URLLoaderSendTest.sendLaterButton_clickHandler(event)");
}
protected function sendWithCallLaterButton_clickHandler(event:MouseEvent):void {
trace(">> URLLoaderSendTest.sendWithCallLaterButton_clickHandler(event)");
popup = PopUpManager.createPopUp(this, ProgressDialog, true);
callLater(doLoad);
trace("<< URLLoaderSendTest.sendWithCallLaterButton_clickHandler(event)");
}
private function doLoad():void {
trace(">> URLLoaderSendTest.doLoad()");
var bytes:ByteArray = new ByteArray();
bytes.writeInt(1);
var request:URLRequest = new URLRequest("http://localhost/test/");
request.method = URLRequestMethod.POST;
request.contentType = "multipart/form-data";
request.data = bytes;
urlLoader = new URLLoader();
urlLoader.addEventListener(Event.COMPLETE, loaderComplete);
urlLoader.load(request);
trace("<< URLLoaderSendTest.doLoad()");
}
protected function loaderComplete(event:Event):void {
trace(">> URLLoaderSendTest.loaderComplete(event)");
PopUpManager.removePopUp(popup);
}
]]>
</fx:Script>
<s:Button id="sendNowButton" x="10" y="10"
label="Send now"
click="sendNowButton_clickHandler(event)"/>
<s:Button id="sendWithsetTimeoutButton" x="10" y="40"
label="Send with setTimeout"
click="sendWithSetTimeoutButton_clickHandler(event)"/>
<s:Button id="sendWithCallLaterButton" x="10" y="70"
label="Send with callLater"
click="sendWithCallLaterButton_clickHandler(event)"/>
</s:Application>

Problems adding new rows to mx DataGrid

I'm using mx:DataGrid and I cannot change to spark (it's a legacy project). The DataGrid is editable and when the users get in the last column I want to add a new column and start the edit mode in the first column of the new row. I can do this.
My problem is that sometimes the last column cannot be edited, so I added an itemEditBeginning listener to stop the edit and add the new row. That's my problem. When the user get in the last field, the new row is added but I cannot see it. I have to click in the column header to sort datagrid data, then the new rows appears. It's kind of a delay.
My code is bigger, but this simple code can reproduce the same problem:
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx"
creationComplete="application1_creationCompleteHandler(event)"
minWidth="955" minHeight="600">
<fx:Script>
<![CDATA[
import mx.collections.ArrayCollection;
import mx.events.CollectionEvent;
import mx.events.CollectionEventKind;
import mx.events.DataGridEvent;
import mx.events.FlexEvent;
[Bindable] public var itens:ArrayCollection;
protected function application1_creationCompleteHandler(event:FlexEvent):void {
itens = new ArrayCollection();
itens.addEventListener(CollectionEvent.COLLECTION_CHANGE, onCollectionChange);
itens.addItem({a: '1', b: '2'});
}
protected function onCollectionChange(event:CollectionEvent):void {
if (event.kind == CollectionEventKind.ADD) {
var row:int = itens.length - 1;
var column:int = 0;
var args:Array = [row, column];
callLater(moveTo, args);
}
}
protected function moveTo(row:int, col:int):void {
itensDg.editedItemPosition = { rowIndex:row, columnIndex:col };
}
protected function addInfo():void {
itens.addItem({a: '10', b: '20'});
}
protected function itensDg_itemEditBeginningHandler(event:DataGridEvent):void {
if (event.columnIndex == 1) {
event.preventDefault();
addInfo();
}
}
]]>
</fx:Script>
<mx:DataGrid id="itensDg" dataProvider="{itens}" editable="true"
itemEditBeginning="itensDg_itemEditBeginningHandler(event)" />
</s:Application>
Any tips about how to solve this?
Thanks.
Try updating addInfo() as follows:
protected function addInfo():void {
itens.addItem({a: '10', b: '20'});
itens.refresh();
}
Trish answer led me to a solution that works. DataGrid has its own itemEditBeginning listener that modifies the editedItemPosition. Firstly I tried to stop the propagation of the itemEditBeginning event, but I realized some focus and mouse events modifies the editItemPosition too. So, the item was added in the array, my onCollectionChange listener was called, but then other events modified the editedItemPosition later.
So, instead of changing the editedItemPosition in the CollectionEvent listener, I simply changed it after adding the element:
protected function addInfo():void {
itens.addItem({a: '10', b: '20'});
var row:int = itens.length - 1;
var column:int = 0;
var args:Array = [row, column];
callLater(moveTo, args);
}