mouseUp and mouseMove outside of the application? - apache-flex

I'm trying to implement a very simple way to select a subsection of the screen via mouse. The workflow is the standard one for many apps - click on starting point, move mouse and transparent rectangle updates between first point clicked and current position of the mouse. The basic code looks something like this (minus the graphics, which is simple)
<?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" mouseDown="application1_mouseDownHandler(event)">
<fx:Script>
<![CDATA[
import spark.components.mediaClasses.VolumeBar;
private var _anchorPoint:Point = new Point();
private var _currentPoint:Point = new Point();
protected function application1_mouseDownHandler(event:MouseEvent):void
{
addEventListener(MouseEvent.MOUSE_MOVE, handleMouseMove);
addEventListener(MouseEvent.MOUSE_UP, handleMouseUp);
_anchorPoint = new Point(event.stageX, event.stageY);
}
private function handleMouseMove(e:MouseEvent):void
{
_currentPoint.x = e.stageX;
_currentPoint.y = e.stageY;
trace("rectangle between (",_anchorPoint.x, ",", _anchorPoint.y, ") and (", _currentPoint.x, ",", _currentPoint.y, ").");
}
private function handleMouseUp(e:MouseEvent):void
{
removeEventListener(MouseEvent.MOUSE_MOVE, handleMouseMove);
removeEventListener(MouseEvent.MOUSE_UP, handleMouseUp);
}
]]>
</fx:Script>
</s:Application>
This breaks down when the user moves the mouse outside of the app. Not only _currentPoint stops updating, but also if you let go the mouse button outside of the app you miss the mouseUp event, i.e. when you move the mouse back on the app _currentPoint starts updating again as if you had never let go of the mouse button. Was wondering if there is a way in Flex (for web apps) to get around this by listening to mouseMove and mouseUp events when outside of the app (if that's possible) or whatever other way that may make sense.
thank you for your help!

Here's something most people don't know: MouseEvents are tracked outside of the application window if the MOUSE_DOWN event has been fired, but not MOUSE_UP. You can grab mouse positions outside of the application window (and even outside of the browser window) as long as whatever you're doing makes the user hold their mouse down. To test this, try running the following code:
<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="init()">
<fx:Script>
<![CDATA[
protected function init():void {
addEventListener(Event.ADDED_TO_STAGE, magic)
}
protected function magic(e:Event):void {
stage.addEventListener(MouseEvent.MOUSE_MOVE, moreMagic);
}
protected function moreMagic(e:MouseEvent):void {
magicalButton.label = "Hold me down! " + String(e.stageX) + "x" + String(e.stageY);
}
]]>
</fx:Script>
<s:Button id="magicalButton" label="Hold me down!"/>

It might be possible with some hacking in AIR, but definitely not with a webapp opened in a browser.
Just imagine what would happen if websites could track your mouse outside the browser and be able to take screenshots of your OS!

Though this won't get you to where you want, you can make use of Event.MOUSE_LEAVE and MouseEvent.MOUSE_MOVE events to track whether the cursor is within the boundaries of the application.

Related

Flex Spark RadioButton Deselected Event?

Is there a way to listen for this? I can easily listen for click which selects a RadioButton, but I can't seem to find a way to listen for when the RadioButton has been deselected.
Any ideas?
Thanks!
Back in the Flex 2 days, the "change" event triggered when a radio button was deselected. However, this little convenience disappeared in Flex 3, for some reason, and I don't believe that we were provided with any sort of replacement event.
Handling your events at the RadioButtonGroup level is all fine and well, except that there are times when you really want to handle the events on the radio button level -- particularly if you were hoping to interact with a data provider entry via an itemRenderer that is drawing the radio button.
Conveniently, I have a little bit of boilerplate code you can use as drop-in replacements for RadioButton and RadioButtonGroup that provide a "unselect" event at the radio button level. Here is the SmartRadioButton, first of all:
<?xml version="1.0" encoding="utf-8"?>
<s:RadioButton 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 events.SmartRadioButtonEvent;
public function notifyDeselect():void {
dispatchEvent(new SmartRadioButtonEvent('deselect'));
}
]]>
</fx:Script>
<fx:Metadata>
[Event(name="deselect", type="events.SmartRadioButtonEvent")]
</fx:Metadata>
</s:RadioButton>
And here is the SmartRadioButtonGroup:
<?xml version="1.0" encoding="utf-8"?>
<s:RadioButtonGroup xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx"
change="selectionChanged();"
>
<fx:Script>
<![CDATA[
import spark.components.RadioButton;
import components.SmartRadioButton;
protected var oldSelection:SmartRadioButton = null;
// Notify our old selection that it has been deselected, and update
// the reference to the new selection.
public function selectionChanged():void {
var newSelection:SmartRadioButton = this.selection as SmartRadioButton;
if (oldSelection == newSelection) return;
if (oldSelection != null) {
oldSelection.notifyDeselect();
}
oldSelection = newSelection;
}
// Override some properties to make sure that we update oldSelection correctly,
// in the event of a programmatic selection change.
override public function set selectedValue(value:Object):void {
super.selectedValue = value;
oldSelection = super.selection as SmartRadioButton;
}
override public function set selection(value:RadioButton):void {
super.selection = value;
oldSelection = super.selection as SmartRadioButton;
}
]]>
</fx:Script>
</s:RadioButtonGroup>
The two property overrides are in there to make sure that we correctly update the oldSelection, in the event of a programmatic change to the group's selection.
SmartRadioButtonEvent isn't anything fancy or important. It could probably just be a plain Event, since there isn't a special payload.
I have tested the above code, and it all works, but there are surely edge conditions, and other oddities that should be addressed, if it's used in a larger system.

measure various phases of code execution

Is there a way to measure the average time it takes my code to run and each frame to render for my Flex app? More specifically, I know how to use getTimer() but I'm not sure about which events I should listen to in order to do this. I was reading this post and am not sure how you'd figure out how long the actual rendering took (it would seem like it may be the time between the RENDER event fires and the next ENTER_FRAME event fires, but I'm not sure). Also, not exactly sure where the user code happens, or whether I should care about EXIT_FRAME and FRAME_CONSTRUCTED.
Any help much appreciated!
EDIT ----
here's a snippet of code showing the main events for each repetition of the second frame in a super simple flex app. What I'm trying to figure out is whether there is a clear relationship between the "user code" and "rendering" parts of the classic Flex racetrack and the intervals between the four main signals that I'm tracing from.
The code:
<?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">
<fx:Script>
<![CDATA[
import flash.utils.getTimer;
public var t:Timer;
protected function button1_clickHandler(event:MouseEvent):void
{
t = new Timer(40, 50);
t.addEventListener(TimerEvent.TIMER, handleTimeTick);
t.addEventListener(TimerEvent.TIMER_COMPLETE, timerDone);
addEventListener(Event.RENDER, application1_renderHandler);
addEventListener(Event.ENTER_FRAME, application1_enterFrameHandler);
addEventListener(Event.EXIT_FRAME, application1_exitFrameHandler);
addEventListener(Event.FRAME_CONSTRUCTED, application1_frameConstructedHandler);
t.start();
}
protected function handleTimeTick(e:TimerEvent):void
{
shape.x += 5;
}
protected function timerDone(e:TimerEvent):void
{
t.stop();
t.removeEventListener(TimerEvent.TIMER, handleTimeTick);
t.removeEventListener(TimerEvent.TIMER_COMPLETE, timerDone);
removeEventListener(Event.RENDER, application1_renderHandler);
removeEventListener(Event.ENTER_FRAME, application1_enterFrameHandler);
removeEventListener(Event.EXIT_FRAME, application1_exitFrameHandler);
removeEventListener(Event.FRAME_CONSTRUCTED, application1_frameConstructedHandler);
}
protected function application1_renderHandler(event:Event):void
{
trace("render fire", getTimer());
}
protected function application1_enterFrameHandler(event:Event):void
{
trace("enter frame fire", getTimer());
}
protected function application1_exitFrameHandler(event:Event):void
{
trace("exit frame fire", getTimer());
}
protected function application1_frameConstructedHandler(event:Event):void
{
trace("frame constructed fire", getTimer());
}
]]>
</fx:Script>
<s:Rect id="shape" x="0" y="0" height="20" width="20">
<s:fill>
<s:SolidColor color="0xff0000"/>
</s:fill>
</s:Rect>
<s:Button x="10" y="100" click="button1_clickHandler(event)" label="go"/>
</s:Application>
Easy way to test out the time to takes for a "frame" to run. Here's a quick example. This is untested but you get the idea. You should also look at Grant Skinners talk about performance and framerates.
<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"
xmlns:parsley="http://www.spicefactory.org/parsley"
creationComplete="onCreationComplete()">
<fx:Script>
<![CDATA[
private var _timer:Timer = new Timer(1000);
private var _previousTime:int;
private var _avgTime:int;
private var _times:Array = [];
private function onCreationComplete():void
{
// Add event listener for enter frame
this.addEventListener(Event.ENTER_FRAME, onEnterFrame);
// Listen to timer
this._timer.addEventListener(TimerEvent.TIMER, onTimer);
// start timer
this._timer.start();
}
private function onEnterFrame(e:Event):void
{
var time:int = getTimer() - this._previousTime;
trace("timer frame took "+ time +"ms");
this._previousTime = getTimer();
this._times.push(time);
}
private function onTimer(e:Event):void
{
var total:int = 0;
for(var i:uint = 0, len:uint = this._times.length; i < len ; i++)
{
total += this._times[i];
}
this._avgTime = total/len;
this._times = [];
trace("Average frame time is "+ this._avgTime +"ms");
}
]]>
</fx:Script>
</s:Application>
Any Flex app is only two frames. The first frame is the startup/initialization of the framework and the second frame is your app.
The framerate is set on the Flex Application tag and the default is 24 frames per second. So, therefore it takes Flex 1/24 of a second to render the frames of your application.
However, it is entirely probably that your code takes more than one frame to execute, and I believe that is what you want to measure.
For some background reading, you should investigate the Flex Elastic Racetrack about how Flex divides each frame for different types of processing.
Then read up, if you haven't already, about the Flex Component LifeCycle. ( Flex 3 Version ) .
You already mentioned the getTimer() method. The gist is to use getTimer() at two points and compare the two values. What those two points are depends entirely on what you want to measure.
If you want to measure the time it takes a Flex component to go through the startup process, use getTimer() before you create it (AKA new Component() ) and then in a listener for that component's creationComplete event.
If you want to time the full application setup, you're best bet is to probably get the value in a preinitialize event handler of the main Application tag and on the applicationComplete handler of the same tag.
Does that help?
Ok, after reading plenty around the web I think this presentation gives the best info about how to measure elastic race track times (see for instance this piece of code from the presentation).

Flex Cursor management question

I have a spark borderContainer that contains a spark TextInput.
I have an mouse_down and mouse_up event handlers on the borderContainer in order to drag it around the screen; while dragging I change the cursor.
I would like to make the textInput behave as a "standard" textInput, i.e. when clicking on the textInput the user should not be able to drag the whole component but simply interact with text as he/she would normally. Also, I'd like the textInput cursor to look like a normal textInput cursor.
I'm not sure I'm doing this right. My assumption is that I need to stop the propagation of mouse_down and mouse_up on the textInput to inhibit the drag behavior from its parent, and manage rollOver and rollOut in order for the cursor to look ok. See below an example of my code (to simplify it there is no borderContainer or drag - but the code for that would be very simple - just a bit longer).
So here's the issue: if one clicks on the spark textInput and then rolls out of it the cursor turns into a combination of a textInput cursor + the standard cursor set for for the borderContainer. This doesn't seem to happen on mx textInput components (thus the two boxes), but unfortunately I need to use spark components. My guess is that I'm either not calling the cursorManager correctly or I'm not stopping the propagation of mouse_up properly - it seems like it should hit the textInput but not propagate to the borderContainer. I tried stopPropagation() as well but no luck.
Would love any advice / constructive criticism.
thank you!
f
<?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="application1_creationCompleteHandler(event)"
mouseDown="application1_mouseDownHandler(event)"
mouseUp="application1_mouseUpHandler(event)">
<fx:Script>
<![CDATA[
import mx.events.FlexEvent;
import mx.managers.CursorManager;
[Bindable] [Embed(source="../resources/hand.png")] private var _handIcon:Class;
[Bindable] [Embed(source="../resources/Fist.png")] private var _fistIcon:Class;
private var _cursorID:int;
protected function textinput1_rollOutHandler(e:MouseEvent):void
{
e.stopImmediatePropagation();
CursorManager.removeCursor(_cursorID);
_cursorID = CursorManager.setCursor(_handIcon);
}
protected function textinput1_rollOverHandler(e:MouseEvent):void
{
e.stopImmediatePropagation();
CursorManager.removeCursor(_cursorID);
}
protected function application1_creationCompleteHandler(e:FlexEvent):void
{
_cursorID = CursorManager.setCursor(_handIcon);
}
private function stopPropagation(event:MouseEvent):void
{
event.preventDefault();
event.stopImmediatePropagation();
}
protected function textinput1_mouseDownHandler(event:MouseEvent):void
{
stopPropagation(event);
}
protected function textinput1_mouseUpHandler(event:MouseEvent):void
{
stopPropagation(event);
}
protected function application1_mouseDownHandler(event:MouseEvent):void
{
CursorManager.removeCursor(_cursorID);
_cursorID = CursorManager.setCursor(_fistIcon);
}
protected function application1_mouseUpHandler(event:MouseEvent):void
{
CursorManager.removeCursor(_cursorID);
_cursorID = CursorManager.setCursor(_handIcon);
}
]]>
</fx:Script>
<s:TextInput x="43" y="30"
rollOut="textinput1_rollOutHandler(event)"
rollOver="textinput1_rollOverHandler(event)"
mouseDown="textinput1_mouseDownHandler(event)"
mouseUp="textinput1_mouseUpHandler(event)"/>
<mx:TextInput x="43" y="70"
rollOut="textinput1_rollOutHandler(event)"
rollOver="textinput1_rollOverHandler(event)"
mouseDown="textinput1_mouseDownHandler(event)"
mouseUp="textinput1_mouseUpHandler(event)"/>
You can simply do not start drag and do not change the cursor if user clicks on input:
protected function application1_mouseDownHandler(event:MouseEvent):void
{
var container:DisplayObjectContainer = event.target as DisplayObjectContainer;
if (!container || container == textInput || textInput.contains(container))
return;
// start drag and change the cursor
}
I had a similar problem but I have several TextInput fields in the container. So to avoid checking every single of them I used this version of the idea:
if (event.target is RichEditableText) return;
Works perfectly...
Greetings, J!

A question on capturing the button click event in ANOTHER mxml file

this seems to be an interesting question to be discovered in Flex.
I registered a very simple button event listener in A.mxml:
<mx:Script><![CDATA[
import mx.controls.Alert;
public function Handler():void
{
Alert.show('click event occured');
}
]]></mx:Script>
<mx:Button label="{resourceManager.getString('resources', 'button.startLab')}"
id="nextStepButton" click="Handler()" />
It works fine when clicking the button everytime.
Now I want to have something interesting,that is,I want to capture this buttonClick Event in another mxml file,say B.mxml and do something in B.mxml instead of A.
I am bit stuck on this,hope you could give me some hint and help,thanks a lot.
There are a number of approaches to this problem. The simplest (and least object-oriented) is to have A be aware of B, or vice versa. In that case you can just add a listener. In B you could say a.nextStepButton.addEventListener(MouseEvent.CLICK, myHandler), or in A you could do this.nextStepButton.addEventListener(MouseEvent.CLICK, b.myHandler). (When one component is instantiated, you have to set a reference to it on the other component.)
One step better would be to dispatch a custom event that bubbles, with one of the components still aware of the other. In B: a.addEventListener(CustomNavigationEvent.NEXT_CLICK, myHandler), or in A: b.addEventListener(CustomNavigationEvent.NEXT_CLICK, myHandler).
Taking it further, you could just let the event bubble to the top (the SystemManager) and add your listener to the SystemManager. This way B is not aware of A at all. In B: this.systemManager.addEventListener(CustomNavigationEvent.NEXT_CLICK, myHandler).
Taking it even further, you can implement your own version of an event broadcaster, which is just a third object that is accessible by any component, usually implemented as a singleton, that takes listener registrations and accepts event dispatches, then broadcasts that event to registered listeners.
Hope that helps.
EDIT: Here's some code for doing it the first way:
In A.mxml:
<?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" creationComplete="onCreationComplete(event)">
<fx:Script>
<![CDATA[
import mx.events.FlexEvent;
public var b:B;
private function onCreationComplete(e:FlexEvent):void {
// Note that you have to have a public onClick handler in B
this.myButton.addEventListener(MouseEvent.CLICK, b.onClick);
}
]]>
</fx:Script>
<s:Button id="myButton"/>
</s:Group>
You need to make A aware of B in the container that declares instances of both A and B:
MXML:
<mypackage:A id="aComponent" b="bComponent"/>
<mypackage:B id="bComponent"/>
ActionScript equivalent:
var aComponent:A = new A();
var bComponent:B = new B();
aComponent.b = bComponent;

Why does updateDisplayList stop happening in Flex?

Reward: Has been claimed.
Overview: The code giving me the problem is deployed here: http://www.johnuckele.com/MastersOfTime.html
The problem I am running into is that a certain sequence of actions (I don't fully understand how or why) is causing my calls to invalidateDisplayList to fail to produce a subsequent call to updateDisplayList. What I know is that during this period, some other visual effects will fail to occur (such as changing the width of a component or the addition of a new child).
Example: The program below draws two columns of horizontal lines. The column on the left is drawn during commitProperties, the column on the right is drawn during updateDisplayList. A certain sequence of actions can cause the right column to stop updating.
To trigger this bug: First add a new item. Now hit the start button and a bar starts filling up. If you press the add row button, the right column and the filling bar both stop growing. The left column continues unfettered. The extra component won't appear until the last line of the if statement in TEComputeRow.tick() doesn't execute for a frame. Click on the stop button to halt the execution of the block inside the if statement in TEComputeRow.tick() and everything goes back to normal.
Question: What is going on here?
I can force it to behave by using validate now but it doesn't cure the problem, it merely covers it up for a frame. It also seems like a really sloppy hack. Is there a nicer way to deal with the loss of updateDisplayList than using validateNow? Are there any ways to accurately identify the state of the world?
MastersOfTime.mxml
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
layout="absolute"
backgroundColor="white"
backgroundGradientAlphas="[1,1]"
initialize="init()"
enterFrame="tick()"
creationComplete="addComputeArray()">
<mx:Script>
<![CDATA[
import mx.containers.HBox;
import mx.controls.Button;
import mx.containers.VBox;
import flash.utils.getTimer;
private var global:int = 0;
private function addComputeArray():void
{
var addButton:Button = new Button;
addButton.label = "Add Row Item";
addButton.addEventListener(MouseEvent.CLICK, addComputeBox);
box.addChild(addButton);
}
private function addComputeBox(a:* = null):void
{
box.addChild(new TEComputeRow());
}
private function init():void
{
box.clipContent = false;
box.graphics.lineStyle(1);
}
private function tick():void
{
global++;
this.invalidateDisplayList();
this.invalidateProperties();
//this.validateNow();
}
protected override function commitProperties():void
{
super.commitProperties();
box.graphics.moveTo(100, (global*3)%800);
box.graphics.lineTo(200, (global*3)%800);
}
protected override function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
{
super.updateDisplayList(unscaledWidth, unscaledHeight);
box.graphics.moveTo(200, (global*3)%800);
box.graphics.lineTo(300, (global*3)%800);
}
]]>
</mx:Script>
<mx:VBox id="box"/>
</mx:Application>
TEComputeRow.mxml
<?xml version="1.0" encoding="utf-8"?>
<mx:VBox xmlns:mx="http://www.adobe.com/2006/mxml"
height="60"
width="352"
verticalGap="0"
borderStyle="solid"
enterFrame="tick()">
<mx:Script>
<![CDATA[
public var doStuff:Boolean = false;
private var parameter:Number = 0;
private function tick(e:Event = null):void
{
var value:*;
if(doStuff)
{
parameter = parameter+1;
value = parameter;
fill.width = value;
}
}
]]>
</mx:Script>
<mx:Button label="turn on" click="{doStuff = true;}" height="20"/>
<mx:Container id="fill" x="7" width="0" height="20" backgroundColor="0x8888AA"/>
<mx:Button label="turn off" click="{doStuff = false;}" height="20"/>
</mx:VBox>
Well for starters, you are abusing the Flex life-cycle horribly, and doing things you are not meant to do... Changing the width of the fill in the tick of the row kicks off another invalidation cycle is the one that immediately jumps out. If you drove things via the Timer rather than on enterFrame, you'd immediately be better off.
My guess is that you spend so much time per frame re-invalidating the properties (changing the width will invalidate properties), the player never fits in the updateDisplayList.
Read up on both the elastic race track, and Deepa's presentation on the Flex 3 lifecycle.
The use of .width is what is triggering this problem. If I replace .width with .setActualSize the problem stops. These pieces of code travel through separate paths and .width and .height apparently have the capacity to skip part of the frame cycle (the updateDisplayList part).

Resources