Flex Catch Keydown - apache-flex

I want to handle the keydown event globally in my application and as such have added the event handler to the top most DisplayObject in the display list. However, the event doesn't seem to fire unless the cursor focus is on a Textbox or Datagrid or the like.
How can I have key presses handled globaly by the application?

In response the OP's comment above, here's how to hook it up correctly, namely in the application's creationComplete event:
<?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" minWidth="955" minHeight="600"
creationComplete="initApp();">
<fx:Script>
<![CDATA[
import mx.controls.Alert;
import mx.managers.SystemManager;
public function initApp():void {
systemManager.stage.addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown)
}
public function onKeyDown(event:KeyboardEvent):void {
pressedKey.text = event.charCode.toString();
}
]]>
</fx:Script>
<mx:Label x="486" y="63" text="You pressed: "/>
<mx:Label id="pressedKey" x="577" y="58" width="122" text=""/>
</s:Application>

stage.addEventListener(...) ?

First, you won't detect any keystroke until your flex application get the focus.
You can do it automatically by calling a js script at startup that will set the focus on the application
Once done, I would recommand you to add your key listener on systemManager. Doing that will allow you to handle keydown event globally.

ExternalInterface.call('function browserFocus(){document.getElementById(\'APPLICATION_NAME_HERE\').focus();}');
This works only with IE and Opera.

Related

How to get the value of a ComboBox in ActionScript

I want to show the value of a ComboBox in it's Change event handler. The labelField is assigned dynamically, so that I don't know what it is when I run the event handler
cmbArea.labelField = qry.outFields[0];
I was hoping that I can have access to the value of selectedItem like:
protected function cmbArea_changeHandler(event:IndexChangeEvent):void{
// TODO Auto-generated method stub
Alert.show(event.target.selectedItem.value);
}
But it didn't work.
Any help would be much appreciated.
It's difficult to debug this without seeing your data provider code or the error message. However, it doesn't matter that the labelField is assigned dynamically. What you have will work if elements in your data provider have a property named "value". If the only property in your data provider is the one you assign as the labelField, you should be able to access it in your change handler with the following:
Alert.show(ComboBox(event.target).selectedItem);
<?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">
<fx:Script>
<![CDATA[
import mx.controls.Alert;
import spark.events.IndexChangeEvent;
protected function comboBox_changeHandler(event:IndexChangeEvent):void
{
Alert.show(comboBox.selectedItem);
}
]]>
</fx:Script>
<s:ComboBox id="comboBox"
change="comboBox_changeHandler(event)">
<s:dataProvider>
<s:ArrayList>
<fx:String>Item 1</fx:String>
<fx:String>Item 2</fx:String>
<fx:String>Item 3</fx:String>
</s:ArrayList>
</s:dataProvider>
</s:ComboBox>
</s:Application>
I tested the following line and seems it works:
Alert.show(event.target.selectedItem[event.target.labelField]);

Yet another Flex custom events in components question

I can't seem to get one of the most basic uses of events working in Flex 4. I've followed multiple tutorials and looked everywhere. From what I can tell, I'm doing everything right, so there must be some stupid mistake somewhere.
I have a main application file that contains a button. On clicking the button, it fires a custom event which I want my listener in my child component to catch. The event IS firing. The child component event listener is NOT catching that event. No clue why. The custom event is copy-pasted from Adobe's tutorial (minus the custom namespace and comments).
Main Application
<?xml version="1.0" encoding="utf-8"?>
<s:WindowedApplication xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx"
applicationComplete="init()" xmlns:local="*">
<fx:Metadata>
[Event(name="enableChanged", type="EnableChangeEvent")]
</fx:Metadata>
<fx:Script>
<![CDATA[
import EnableChangeEvent;
import TestComponent;
private function doDispatchEvent(event:MouseEvent):void {
if(dispatchEvent(new EnableChangeEvent(EnableChangeEvent.ENABLE_CHANGED, true)))
{
statusLabel.text = "Event was dispatched";
}
}
public function init():void {
myButton.addEventListener(MouseEvent.CLICK, doDispatchEvent);
}
]]>
</fx:Script>
<s:Button x="95" y="83" label="Button" id="myButton" />
<s:Label x="230" y="83" text="" id="statusLabel" />
<local:TestComponent x="95" y="150" width="300" height="400" />
</s:WindowedApplication>
Child Component (Test)
<?xml version="1.0" encoding="utf-8"?>
<s:Group 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="100%" height="100%"
initialize="init();">
<fx:Script>
<![CDATA[
import EnableChangeEvent;
private function doFinalAction(event:EnableChangeEvent):void {
myLabel.text = "Custom Event Recieved";
}
private function init():void {
addEventListener(EnableChangeEvent.ENABLE_CHANGED, doFinalAction);
}
]]>
</fx:Script>
<s:Label width="300" height="50" text="Should change on click" id="myLabel" />
</s:Group>
Custom Event (from Adobe)
package {
import flash.events.Event;
public class EnableChangeEvent extends Event
{
public function EnableChangeEvent(type:String, isEnabled:Boolean=false) {
super(type);
this.isEnabled = isEnabled;
}
public static const ENABLE_CHANGED:String = "enableChanged";
public var isEnabled:Boolean;
override public function clone():Event {
return new EnableChangeEvent(type, isEnabled);
}
}
}
Timofei Davydik's answer it correct. Since the Application object dispatches the event, if you want your TestComponent object to catch it, you have to add a listener to a reference of the Application object from within your TestComponent. You can use the TestComponent object's inherited property "parentApplication" to get a reference to the Application object`.
Change the following code in TestComponent.mxml:
private function init():void
{
this.parentApplication.addEventListener(EnableChangeEvent.ENABLE_CHANGED, doFinalAction);
}// end function
[UPDATE]
Personally I feel like your going about using events the wrong way, so I made a similar flex application to demonstrate how to use events in flex.
Main.mxml
<?xml version="1.0" encoding="utf-8"?>
<s:WindowedApplication xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx"
xmlns:components="components.*"
xmlns:events="events.*"
applicationComplete="init()">
<fx:Script>
<![CDATA[
import events.EnableChangeEvent;
public function init():void
{
label1.text = "Application Complete!";
}// end function
private function onTestButtonEnableChanged(e:EnableChangeEvent):void
{
label2.text = "Enabled = " + e.isEnabled;
}// end function
]]>
</fx:Script>
<s:layout>
<s:VerticalLayout horizontalAlign="center" paddingTop="50" />
</s:layout>
<components:TestButton id="testButton" label="CLICK!" enableChanged="onTestButtonEnableChanged(event)" />
<s:Label id="label1"/>
<s:Label id="label2"/>
</s:WindowedApplication>
TestButton.mxml
<?xml version="1.0" encoding="utf-8"?>
<s:Button xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx"
click="onClick()">
<fx:Metadata>
[Event(name="enableChanged", type="events.EnableChangeEvent")]
</fx:Metadata>
<fx:Script>
<![CDATA[
import events.EnableChangeEvent
private var isEnabled:Boolean;
private function onClick():void
{
isEnabled = !isEnabled;
dispatchEvent(new EnableChangeEvent(EnableChangeEvent.ENABLE_CHANGED, isEnabled));
}// end function
]]>
</fx:Script>
</s:Button>
The main difference between the applications is that the Button object, in this case the TestButton object, dispatches the EnableChangeEvent event when clicked. While dispatching the event we can parse the boolean value for the _isEnabled property of TestButton to the event. Before, the value is inverted using the line isEnabled = !isEnabled. Now we can make use of handling the event using the mxml declaration of the TestButton object with the xml attribute enableChanged.
Also instead of following tutorials you might want to try a book like the one i'm reading. It's called "Adobe Flash Builder 4 and Flex 4 Bible" by David Gassner which you can get as a paperback book or a book for your kindle.
#Taurayi is correct, but to get you current code working with just one change, edit the init of Test Component to look like the following:
private function init():void
{
FlexGlobals.topLevelApplication.addEventListener(EnableChangeEvent.ENABLE_CHANGED, doFinalAction);
}
Your application object dispatches the custom event. Why do you expect your group to "catch" this event? It won't. Custom events have neither capturing nor bubbling phase. So, only listeners added to your application object will listen to this event.

Flex 4: DropDownList doesn't work in new window

In this code I'm creating a new window when I click the button. In the new window are TextInput and DropDownList components. When the new window opens, clicking the DropDownList does nothing - you have to click it a second time round to get it to open. However, click into the TextInput field first and then try opening the DropDownList works no problem.
Any reason why this is happening? Is this a bug or something I'm doing wrong? The issue occurs with Flex 4.1 and Flex Hero (Sept 2010 release).
Below is the code, or download the FXP file here.
// DropDownTest.mxml (application)
<?xml version="1.0" encoding="utf-8"?>
<s:WindowedApplication 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="400" height="300">
<fx:Script>
<![CDATA[
private function newWindow():void
{
this.close();
var w:MyWindow = new MyWindow();
w.open();
}
]]>
</fx:Script>
<s:Button label="New Window" click="newWindow()"/>
</s:WindowedApplication>
// MyWindow.mxml (component)
<?xml version="1.0" encoding="utf-8"?>
<s:Window 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="400" height="300">
<s:TextInput/>
<s:DropDownList y="30"/>
</s:Window>
Turns out this is a bug. Adobe suggested calling "setFocus()" after "open()" and it worked. See here for further details: http://forums.adobe.com/message/3241460#3241460

Component doesn't get garbage collected

I just noticed a strange behaviour while looking at my application in the Flash Profiler. When I click a button in my TitleWindow then the TitleWindow doesn't get garbage collected after it is removed. I have no idea why that is happening.
I've created a small example application, so you can try it out yourself:
Main.mxml
<?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" pageTitle="Memory Leak (Spark)">
<fx:Script>
<![CDATA[
protected function openWindowBtn_clickHandler():void
{
removeAllElements();
addElement(new ExampleView());
}
]]>
</fx:Script>
<s:controlBarContent>
<s:Button label="Open Window" id="openWindowBtn" click="openWindowBtn_clickHandler()"/>
</s:controlBarContent>
</s:Application>
ExampleView.mxml
<?xml version="1.0" encoding="utf-8"?>
<s:TitleWindow 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="100%" height="100%" title="Example View" close="closeHandler()">
<fx:Script>
<![CDATA[
import mx.core.IVisualElementContainer;
protected function closeHandler():void
{
var visualElementParent:IVisualElementContainer = parent as IVisualElementContainer;
if (visualElementParent)
visualElementParent.removeElement(this);
else
parent.removeChild(this);
}
]]>
</fx:Script>
<s:layout>
<s:VerticalLayout verticalAlign="middle" horizontalAlign="center"/>
</s:layout>
<s:Button id="doSomethingBtn" label="Click me!"/>
</s:TitleWindow>
When you click "Open Window" and close the ExampleView without clicking the "Click me!" button in it then the GC kicks in and removes the ExampleView. However, when you click on "Click me!" and close the ExampleView afterwards, the ExampleView stays in memory forever.
I wasn't able to find the references in the Profiler which cause this behaviour. I hope someone knows a solution to this, otherwise Flex is creating a lot of memory leaks.
I may be wrong, but iirc EventListeners added in MXML are always created with a strong reference, which would prevent the Button from being GC'ed.
Have you tried adding the EventListener manually with setting it to being a weak reference? If you look at the list of EventListeners in the Debugger you should see something like a WeakMethodClosure if it was added with a weak reference.
One thing you're probably forgetting is that garbage collection isn't collecting unreferenced objects at the moment they loose the last reference. Usually the GC will collect the loose instances only after you create some object, but even than it's not obvious if it will do it in that moment. You can read more about it here:
About garbage collection
Or take a look at this presentation: Garbage Collection - Alex Harui
<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" pageTitle="Memory Leak (Spark)">
<fx:Script>
<![CDATA[
protected function openWindowBtn_clickHandler():void
{
removeAllElements();
addElement(new ExampleView());
}
protected function button1_clickHandler(event:MouseEvent):void
{
var o:Object = new Object();
System.gc();
}
]]>
</fx:Script>
<s:controlBarContent>
<s:Button label="Open Window" id="openWindowBtn" click="openWindowBtn_clickHandler()"/>
<s:Button label="Force GC" click="button1_clickHandler(event)"/>
</s:controlBarContent>
</s:Application>
Take a look at this. If you press the "Force GC" button a couple of times, it will collect the ExampleWindow. In a real world application that does something this happens without the need to call the System.gc() (in fact, it's not a good practice to call it), but after a while, so the things don't just disapear when you're done with them, they disapear when you're done, and Flash Player decides it needs to clean up.
Looks like the ExampleView is not getting Garbage collected because somehow some EventListener is added when "Click Me" is clicked.
The best way to avoid this is to
1. add Event Listener manually in createComplete event
2. Remove the EventListener in closeHandler
3. Remove the button from the container and set it to null
Now the ExampleView will be Garbage collcected

Flash Builder 4: Error #1009 in when Button is wrapped in BorderContainer

This is really bugging me, but I have a component where a Button is wrapped in BorderContainer. I'm passing a custom property to the component at run-time to change the label of the button but Flex is reporting the following error:
Cannot access a property or method of a null object reference
When the error occurs, Flex highlights the following code:
myButton.label = value;
Here's the app:
// MyApp.mxml
<?xml version="1.0" encoding="utf-8"?>
<s:WindowedApplication xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx"
xmlns:local="*">
<local:MyComp id="myButton" label="My Button"/>
</s:WindowedApplication>
// MyComp.mxml
<?xml version="1.0" encoding="utf-8"?>
<s:BorderContainer 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="400" height="300">
<fx:Script>
<![CDATA[
private var _label:String;
public function get label():String
{
return _label;
}
public function set label(value:String):void
{
_label = value;
myButton.label = value;
}
]]>
</fx:Script>
<s:Button id="myButton" label="Test"/>
</s:BorderContainer>
Any help would be greatly appreciated. Thanks in advance.
The myButton Object is not already created if the setter function for the label property is called the first time. Assign the new label value to myButton.label in commitProperties().
You should read About creating advanced components (most notably "About the component instantiation life cycle") to understand why.

Resources