Two KeyboardEvent.KEY_DOWN events fired with wmode="transparent" - apache-flex

When I listen for key up and key down events with wmode="transparent", I receive 2 key down events followed by a single key up event for the following keys: F-keys, arrow keys, ins, del, home, end, page up, page down, pause, print screen, application key, windows key, and the equivalent numeric keypad keys. The other keys work normally. This is occurring with FF 3.5, but not with IE 6.
Below is a simple Flex application that illustrates the problem if you run it with wmode="transparent".
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" applicationComplete="init()">
<mx:Script>
<![CDATA[
import mx.controls.Label;
private function init():void {
stage.addEventListener(KeyboardEvent.KEY_DOWN, onKey);
stage.addEventListener(KeyboardEvent.KEY_UP, onKey);
}
private function onKey(event:KeyboardEvent):void {
var msg:Label = new Label();
msg.text = event.toString();
eventLog.addChildAt(msg, 0);
}
]]>
</mx:Script>
<mx:VBox width="100%" height="100%">
<mx:TextInput width="100%" text="Hello"/>
<mx:VBox id="eventLog" width="100%" height="100%"/>
</mx:VBox>
</mx:Application>
Our application requires wmode="transparent" and needs to handle both key up and key down events for the problematic keys, so I'm looking for the best way to solve this. What would be the best workaround for this problem? Is there some Flash player parameter that I can use to get this working? (FF configuration changes aren't viable for our application, but might be interesting for understanding the cause of this.)

By the way, the workaround I'm currently favoring is to discard additional key down events for a key during a brief dead-band (maybe 50-100 ms) following its initial key down event. This solution has the merits of being relatively simple to implement while still supporting key repeats, though with an additional delay before repeat begins.
Here's basically what I've added to the test code:
private var keyDownTs:Array = new Array();
private function onKey(event:KeyboardEvent):void {
var msg:Label = new Label();
msg.text = getTimer() + "-" + (isDead(event) ? "**DEAD**" : "") + event.toString();
eventLog.addChildAt(msg, 0);
}
private function isDead(event:KeyboardEvent):Boolean {
var dead:Boolean = false;
switch (event.type) {
case KeyboardEvent.KEY_DOWN:
var ts:int = keyDownTs[event.keyCode];
if (ts == 0) {
// save timestamp for the initial key down event
keyDownTs[event.keyCode] = getTimer();
} else if (getTimer() - ts < 50) {
// this key down is within the dead-band of the initial key down
dead = true;
}
break;
case KeyboardEvent.KEY_UP:
// clear previous key down timestamp
keyDownTs[event.keyCode] = 0;
break;
}
return dead;
}
On my system, the spurious key down events are happening within a few ms of the initial key down (2-6 ms), so 50 ms is looking like a pretty good value. For my production implementation, I think I'm going to put this logic into an EventDispatcher that KeyboardEvent listeners will use instead of listening to the stage directly.

I don't have an answer for you, but can confirm that wmode="transparent" has caused this exact same behaviour with an item on our website. Using transparent and opaque wmodes was causing this unidentified (until now) double keypress behaviour. Due to the numerous issues with opaque and transparent modes, we instead worked around all the other issues that surround using standard mode, because it's the only mode in which keyboard IO works correctly.
Other issues include incorrect key mappings, issues with screen-reading/accessibility software, flash movie does not start executing until it is scrolled into the viewport and others.
If there's any way you can survive without wmode transparent, then consider it, because otherwise, as far as I could see, everything is borked.
The reason we initially chose transparent mode was so we could overlay other HTML elements over the Flash movie. Now instead, if we're going to overlay, we "hide" the flash:
Don't use css display:none because this resets the movie (but not using AX version in IE).
The movie can be hidden by either setting its height to 0.001px via css or to visibility:hidden in css, or both.

Related

Playing two consecutive videos using VideoPlayer without 'black frame in between'

I am playing multiple short video clips behind each other and I want it to look like it is one continuous video. Is there a way to stich them together so that it's unnoticable when you set a new video source.
At the moment I am having a problem where a black frame is inserted when I set a new video source. The order of events seems to be something like this
--> Video Finishes
--> Complete event dispatched
--> Set source to new video
--> Screen turns blank (this should not happen, instead it should keep the last frame of the video until the new video is loaded)
--> a few miliseconds pass
--> the new video starts playing.
Is it possible to avoid the screen turning blank or to keep the last frame showing until the next video is loaded.
Thanks,
Dennis
Technically, it is doable, however I doubt you'll be able to get this right 100% of the time. Here is how you'll need to do this.
Just before the first (or previous) video finishes, you'll have to load the next video in a new player instance. Of course depending a number of factors, how soon before the first finishes that you have to load the next depends on a number of factors
1. The bit rate of the next video
2. The Internet bandwidth of the client machine.
3. If the previous video has not loaded completely before you need to load the next, you'll have to account for that as well a not hamper the playing of the currently playing video.
4. Some client computers may not be able to do this without stuttering and glitches.
The best solution to the problem is to really stitch the videos together before hand. of course if you don't know the sequence before hand you can't do this, so you're left with doing what I mentioned earlier.
Here is the double video player that Shiv describes. This works for the most part, except for that there is a short pause when the next video loads... still not ideal.
<?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="400" height="300">
<fx:Metadata>
[Event(name="complete", type="org.osmf.events.TimeEvent")]
</fx:Metadata>
<fx:Script>
<![CDATA[
import org.osmf.events.MediaPlayerStateChangeEvent;
import org.osmf.events.TimeEvent;
import org.osmf.media.MediaPlayer;
import org.osmf.media.MediaPlayerState;
import spark.components.VideoPlayer;
var _source:String = "";
var activePlayer:VideoDisplay = null;
var otherPlayer:VideoDisplay = null;
var playerIndex:int = 0;
public function set source (value:String) {
trace('set source: ' + value)
if(value == '' || value == null || value == _source) {
trace('rejected source');
return;
}
if(activePlayer != null) {
activePlayer.depth = 3;
}
activePlayer = getElementAt(playerIndex) as VideoDisplay;
playerIndex = playerIndex == 0?1:0;
otherPlayer = getElementAt(playerIndex) as VideoDisplay;
trace('active player: ' + activePlayer.id);
activePlayer.addEventListener(MediaPlayerStateChangeEvent.MEDIA_PLAYER_STATE_CHANGE, player1_mediaPlayerStateChangeHandler);
activePlayer.source = value;
_source = value;
}
public function get source ():String {
return _source;
}
protected function player1_mediaPlayerStateChangeHandler(event:MediaPlayerStateChangeEvent):void
{
trace('state change: ' + event.state + ', player: ' + (event.currentTarget.id));
if(event.state == MediaPlayerState.READY) {
player1.removeEventListener(MediaPlayerStateChangeEvent.MEDIA_PLAYER_STATE_CHANGE,player1_mediaPlayerStateChangeHandler);
player2.removeEventListener(MediaPlayerStateChangeEvent.MEDIA_PLAYER_STATE_CHANGE,player1_mediaPlayerStateChangeHandler);
activePlayer.play();
activePlayer.depth = 2;
otherPlayer.depth = 1;
}
}
protected function completeHandler(event:TimeEvent):void
{
if(event.currentTarget == activePlayer) {
trace('video complete');
dispatchEvent(event);
}
}
]]>
</fx:Script>
<s:VideoDisplay id="player1" autoRewind="false" complete="completeHandler(event)" autoPlay="true" opaqueBackground="false"/>
<s:VideoDisplay id="player2" complete="completeHandler(event)" autoRewind="false" autoPlay="true" opaqueBackground="false" />
</s:Group>

Flex textarea control not updating properly

I am writing a flex application that involves modifying a textarea very frequently. I have encountered issues with the textarea sometimes not displaying my modifications.
The following actionscript code illustrates my problem:
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" minWidth="955" minHeight="600">
<mx:TextArea x="82" y="36" width="354" height="291" id="textArea" creationComplete="initApp()"/>
<mx:Script>
<![CDATA[
private var testSentence:String = "The big brown fox jumps over the lazy dog.";
private var testCounter:int = 0;
private function initApp():void {
var timer:Timer = new Timer(10);
timer.addEventListener(TimerEvent.TIMER, playSentence);
timer.start();
}
private function playSentence(event:TimerEvent):void {
textArea.editable = false;
if (testCounter == testSentence.length) {
testCounter = 0;
textArea.text += "\n";
}
else {
textArea.text += testSentence.charAt(testCounter++);
}
textArea.editable = true;
}
]]>
</mx:Script>
</mx:Application>
When you run the above code in a flex project, it should repeatedly print, character by character, the sentence "The big brown fox jumps over the lazy dog.". But, if you are typing into the textarea at the same time, you will notice the text the timer prints is distorted.
I am really curious as to why this happens. The single-threaded nature of flex and disabling user input for the textarea when I make modifications should prevent this from happening, but for some reason this doesn't seem to be working.
I must note too that, when running the timer at larger intervals (around 100ms) it seems to work perfectly, so I am tempted to think it's some kind of synchronization issue in the internals of the flex framework.
Any ideas on what could be causing the problem?
My son is having a rough go at this teething thing, hense my being awake. AND, I'm not really sure how I happened upon this random question/answer since I'm so rarely on stackoverflow.... but...
You SHOULD NOT have to increase your framerate by 4x because of a textArea! We're talking about system resources, not to mention a very elegant flex framework that you could instead leverage:
Here's a very quick, and flex-sdk compliant and happy fix:
create a component that extends TextArea and add the following property and override (if this in fact what you're trying to do):
private var _tackOnText : String = "";
private var _tackOnTextChanged : Boolean = false;
public function set tackOnText(value:String):void
{
_tackOnText = value;
_tackOnTextChanged = true;
invalidateProperties();
}
public function get tackOnText():String
{
return _tackOnText;
}
override protected function commitProperties():void
{
super.commitProperties();
if(_tackOnTextChanged) {
this.textField.text += _tackOnText;
_tackOnText = "";
_tackOnTextChanged = false;
}
}
Then change your playSentence to do this:
if (testCounter == testSentence.length) {
testCounter = 0;
textArea.tackOnText += "\n";
}
else {
textArea.tackOnText += testSentence.charAt(testCounter++);
}
This is one fix to a semi-common problem that should allow you to not increase your frameRate by 4x to get your app to function correctly AND you'll be better working with the flex sdk.
My 2p.
Have a good one,
Jeremy
the problem you are facing seems to be when the addition ticker function is called at the same time that you are entering text, by entering text manually it seems that it is then missing the addition of the text. though im not sure why.
the solution i found seems to be to make sure that the framescript and timer dont meet (for the faster text input, increase the framerate) for 10ms timer 100fps seems to work without any problems (add frameRate="100" to mx:Application line).

Flex: image switching place in tilelist

I'm running into an odd issue with itemRenderers inside a TileList.
Here is a working example without an itemRenderer: 152.org/flex/
Here is the broken version with an itemRenderer: 152.org/brokenExample/
(I don't have the rep to make both of these a link)
Both examples have "View Source" enabled.
To see the problem use the broken example, select an album and scroll down one row. Scroll back up and the images will be switched. If you try this on the working example it's fine.
This seems to be a widely known bug, but I can't find a solution for it.
UPDATE
I started playing with this example again and found out something else. Turns out you don't have to override the data setter. You can create a new method in the itemRenderer that is set whenever the tile wants to refresh. So the trick is to not rely on the initialize or creationComplete methods.
This is what I have for the itemRenderer in the Application.
<itemRenderers:ImageTile img="{data}"/>
This is the code I have in the itemRenderer.
public function set img(value:String) : void {
trace("setting source: " + value);
this.source = value;
this.name = value.toString().split("/").pop().split(".").shift();
}
I updated my example to reflex this change.
I don't have your app handy, so I can't test end-to-end, but I've looked at your source. You probably need to override the data setter in your itemRenderer:
<?xml version="1.0" encoding="utf-8"?>
<mx:Image xmlns:mx="http://www.adobe.com/2006/mxml" initialize="init()">
<mx:Script>
<![CDATA[
override public function set data(value:Object):void
{
super.data = value;
this.source = data;
this.name = data.toString().split("/").pop().split(".").shift();
}
private function init() : void {
// Removed from your source and transplanted above
}
]]>
</mx:Script>
</mx:Image>
Flex will attempt to re-use item renderers in lists (which means the lifecycle events you might be expecting -- initialize, creationComplete, etc. -- won't always fire), so if you want to be sure your renderer gets updated when the data item changes (as it will when scroll events happen), the best practice is to override the renderer's data property. That'll most likely fix the problem.
Maybe try to invalidate on creationComplete?
From what I recall with DataGrids (which work somewhat similarly to a tilelist), when an item comes into focus its recreated.
<mx:itemRenderer>
<mx:Image id="myImage" creationComplete="myImage.invalidate()" />
</mx:itemRenderer>
Haven't tried this code but I think this is where you want to start looking. I took a look at your itemRenderer component. Try creationComplete instead of initialize to call your function

Flex - Problem with auto resizing datagrid

I'm trying to create a datagrid which will resize vertically to ensure all the renderers are displayed in full. Additionally,
Renderers are of variable height
Renderers can resize themselves
Generally speaking, the flow of events is as follows :
One of the item renderers resizes itself (normally in response to a user click etc)
It dispatches a bubbling event which the parent datagrid picks up
The DataGrid attempts to resize to ensure that all renderers remain visible in full.
I'm currently using this code within the datagrid to calculate the height:
height = measureHeightOfItems(0, dataProvider.length ) + headerHeight;
This appears to get an incorrect height. I've tried a number of variations including callLater ( to ensure the resize has completed so measure can work correctly), and overriding meausre() and calling invalidateSize() / validateSize(), but neither works.
Below are 3 classes which will illustrate the problem. Clicking the button in the item renderers resizes the renderer. The grid should also expand so that all of the 3 renderers are shown in their entirety.
Any suggestions would be greatly appreciated.
Regards
Marty
DataGridProblem.mxml (Application file)
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical"
xmlns:view="view.*">
<mx:ArrayCollection id="dataProvider">
<mx:String>Item A</mx:String>
<mx:String>Item B</mx:String>
<mx:String>Item C</mx:String>
</mx:ArrayCollection>
<view:TestDataGrid
id="dg"
dataProvider="{ dataProvider }"
width="400">
<view:columns>
<mx:DataGridColumn dataField="text" />
<mx:DataGridColumn itemRenderer="view.RendererButton" />
</view:columns>
</view:TestDataGrid>
</mx:Application>
view.TestDataGrid.as
package view
{
import flash.events.Event;
import mx.controls.DataGrid;
import mx.core.ScrollPolicy;
public class TestDataGrid extends DataGrid
{
public function TestDataGrid()
{
this.verticalScrollPolicy = ScrollPolicy.OFF;
this.variableRowHeight = true;
this.addEventListener( RendererButton.RENDERER_RESIZE , onRendererResize );
}
private function onRendererResize( event : Event ) : void
{
resizeDatagrid();
}
private function resizeDatagrid():void
{
height = measureHeightOfItems(0, dataProvider.length ) + headerHeight;
}
}
}
view.RendererButton.mxml
<?xml version="1.0" encoding="utf-8"?>
<mx:HBox xmlns:mx="http://www.adobe.com/2006/mxml">
<mx:Button width="50" height="50"
click="onClick()" />
<mx:Script>
<![CDATA[
public static const RENDERER_RESIZE : String = "resizeRenderer";
private function onClick() : void
{
this.height += 20;
dispatchEvent( new Event( RENDERER_RESIZE , true ) );
}
]]>
</mx:Script>
</mx:HBox>
You can achieve this goal with the AdvancedDataGrid using the following code. If you remove the this.headerHeight, it works for List as well, which makes me believe it should work for the regular old DataGrid.
override protected function measure():void
{
super.measure();
if ( this.dataProvider )
{
var newHeight : int = measureHeightOfItems( 0, this.dataProvider.length ) + this.headerHeight;
this.minHeight = newHeight;
this.height = newHeight;
}
}
To re size the Data-grid at runtime....use rowcount property and bind it with the dataprovider length. As the dataprovider is updated so will be the rowcount.
You can see the example how to use rowcount in flex here
To do things such as variable width and height cells/rows/column, as well as other "advanced" features - try extending the AdvancedDataGrid rather than the older, more boring DataGrid
For what it's worth, I never managed to resolve this issue. Code quickly became obsessed with dealing with edge cases.
I ended up throwing out the dataGrid approach, and wrote a solution using VBox & HBox to facilitate resizing.
for what its worth; you were supposed to use the variableRowHeight="true" property of the datagrid / advanced datagrid.
if it makes you feel better i created the custom VBox,HBox solution then after that discovered that its already done!! (snap!!)
good luck!
There's an underlying problem to your approach. ItemRenderers are intended to render the data item in a consistent manner, and if you throw out the current renderer and create a new one and set the data property of the new renderer, it should look identical to the previous one. You're not supposed to make transient changes to the renderer outside of the data member, and the renderer is going to behave in odd ways when you do that.
I suspect that if you changed your data model objects to contain a count property and data bound your renderer's height to the expression "{50 + data.count * 20}", and then made the button's click handler increment data.count by 1, that this would work properly. (My experience is that the DataGrid will redraw itself with the proper size for each row as long as the changes get done as a result of the data property, before it calls makeRowsAndColumns(). ) I haven't tried that approach for your example, so I can't say for sure that it actually works, but certainly that's the proper mindset for working with item renderers.
I have a little bit Dirty solution for such problem Try this one
private function ResizeGrid():void{
if ( this.dg.maxVerticalScrollPosition > 0 ){
this.dg.height +=5;
setTimeout(this.ResizeGrid,100);
}
}
And Call your function with some delay like follows
setTimeout(this.ResizeGrid,100);
This is dirty approach but it works for me :)
The following code in gridItemRenderer helped me:
protected function resize():void
{
column.grid.validateSize();
column.grid.validateDisplayList();
}
I have a solution for this problem, renderer changes need to sync with your dataprovider.
Then only measureHeightOfItems method will give accurate results.

In Flex, How can I prevent my variable from losing state after being set?

I've hit a very strange issue in adobe flex and I'm not sure how to solve it. I have a popup box that is called on creationComplete before startup of my main application. The popup simply asks for an email address and then the application is enabled showing the email address in a label component.
However, when I try to access the email address from the label component called UserID.text in the application, it is always null even though it is visually present in the label box...It seems like it loses state somehow...so HOW in earth can I keep it from losing state? I need to access the label or some variable throughout the use of the application and everything I try always ends up in a null variable???
The main part of the application that sets the label is here:
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" xmlns:controls="com.iwobanas.controls.*" xmlns:local="*" creationComplete="showWindow(true)">
private function showWindow(modal:Boolean):void
{
var logonWindow:LogonWindow = new LogonWindow();
logonWindow.addEventListener("logon", logonHandler);
PopUpManager.addPopUp(logonWindow, this, modal);
PopUpManager.centerPopUp(logonWindow);
}
public function logonHandler(event:LogonEvent):void
{
UserID.text=event.userId;
}
Any help would be greatly appreciated.
Store the id as a Bindable variable in your application instead of setting the text input directly.
[Bindable]
private var logonId:String;
public function logonHandler(event:LogonEvent):void
{
logonId=event.userId;
}
<mx:TextInput text="{logonId}" />

Resources