AIR: component behaves wrong after switching window - apache-flex

Ok so I have a component, that has a function to remove itself as a popUp in its current Window, and add itself to a newly created Window.
It works, however, if the component has a child like a ComboBox, the drop down still pops up in the old window where it used to be, also scrollbars, and focus seems to behave incorrectly in the new window also.
It seems to me like Flex still thinks the component is a child of the original window, not the new window. I have no idea how to resolve this though.
Here is my code:
private var ownWindow:Window;
private var _inOwnWindow:Boolean;
private var _removedEffect:Move;
private var _openX:Number;
private var _openY:Number;
public function launchInNewWindow(e:Event):void
{
_openX = Application.application.nativeWindow.x + this.x + 5; //keep in same spot add 5 for systemChrom border
_openY = Application.application.nativeWindow.y + this.y + 30;//keep in same spot add 30 for systemChrom title
this.parent.removeChild(this);
ownWindow = new Window();
ownWindow.systemChrome = 'none';
ownWindow.type = NativeWindowType.LIGHTWEIGHT;
ownWindow.transparent = true;
ownWindow.setStyle('showFlexChrome', false);
ownWindow.width = this.width > 750 ? 750 : this.width;
ownWindow.height = this.height > 550 ? 550 : this.height;
edit.enabled = false;
_removedEffect = this.getStyle('removedEffect') as Move;
if(_removedEffect == null)
{
openNewWindow();
}
else
{
// Wait for removed effect to play before adding to new window
_removedEffect.addEventListener(EffectEvent.EFFECT_END,delayOpenInNewWindow);
}
}
private function delayOpenInNewWindow(e:Event = null):void
{
var t:Timer = new Timer(100,1);
t.addEventListener(TimerEvent.TIMER,openNewWindow);
t.start();
}
private function openNewWindow(e:Event = null):void
{
ownWindow.addChild(this);
ownWindow.width += 5; //add to show dropshadow
ownWindow.height += 10; //add to show dropshadow
ownWindow.open();
_inOwnWindow = true;
ownWindow.nativeWindow.x = _openX;
ownWindow.nativeWindow.y = _openY;
}
Any ideas?
Thanks!!

Before I give this a run, have you tried a callLater on the openNewWindow() line?
[ lame fix attempt, i know -- but given that there doesn't seem to be an event that you can listen for in the case that the removedEffect isn't null and it seems like a timer is your only option there, I think it's o.k. to give lame fix attempts :-) ]

Related

Flex 4.5 mobile Resize textarea with animation on soft keyboard activate event?

I'm using Flex 4.5.1 with AIR 2.7 (and Flash Builder 4.5.1) to build an app for the Blackberry Playbook.
The app has a large textarea that needs to be resized when the soft keyboard shows up. I am able to hook into the soft keyboard events and do things there.
Now, I want to resize the textarea to fit the remaining part of the screen when the keyboard shows up. I know the current and destination size and want to use a smooth animation to show the textarea resizing. (I can't already set the height and can see the textarea being correctly resized in the keyboard_activating event - but I want to do it through an animation). I've tried using the Animate class, the spark.effects.Move class and none of them seem to work in this case. They seem to run the animation but the screen does not get refreshed!
I don't want to use the built-in resizeAppForKeyboard property on the ViewNavigatorApplication. I have set to 'none' in the app descriptor so that part of it is fine.
Any ideas / thoughts? Is my approach correct at all? How would one go about resizing the textarea using an animation in the keyboard activating event? My code looks like:
In the main view (onCreationComplete event): (txNote is the textarea in question)
txNote.addEventListener(SoftKeyboardEvent.SOFT_KEYBOARD_ACTIVATE, function(e:SoftKeyboardEvent):void {
toggleEditMode(true);
});
txNote.addEventListener(SoftKeyboardEvent.SOFT_KEYBOARD_DEACTIVATE, function(e:SoftKeyboardEvent):void {
toggleEditMode(false);
});
private function toggleEditMode(keyboardActivated:Boolean):void {
trace("toggle edit: " + editMode + ", kb activating: " + keyboardActivated + ", txNote height = " + txNote.height);
editMode = keyboardActivated;
//we handle resize manually, because we want a nice animation happening and we want to resize only the text area - nothing else.
var y:Number = editMode ? -38 : 0;
var height:Number = editMode ? 218 : 455;
txNote.moveAndSize(y, height);
}
The code in txNote.moveAndSize:
public function moveAndSize(newY:Number, newHeight:Number):void {
//parent group uses BasicLayout
//(this.parent as Group).autoLayout = false;
//resizePath.valueFrom = this.height;
//resizePath.valueTo = newHeight;
//movePath.valueFrom = this.y;
//movePath.valueTo = newY;
//resizeEffect.heightFrom = this.height;
//resizeEffect.heightTo = height;
this.top = newY;
moveEffect.xFrom = this.x;
moveEffect.xTo = this.x;
moveEffect.yFrom = this.y;
moveEffect.yTo = newY;
moveEffect.end();
moveEffect.play([ this ]);
//this.move(x, newY);
//animate.play([ this ]);
//this.height = height;
//this.y = y;
//setLayoutBoundsSize(width, height);
//setLayoutBoundsPosition(x, y);
}
The moveEffect / movePath / animate various things I tried are set up in the txNote constructor as follows:
public class NotesArea extends TextArea {
// private var animate:Animate;
// private var movePath:SimpleMotionPath;
// private var resizePath:SimpleMotionPath;
private var moveEffect:Move;
// private var resizeEffect:Resize;
public function NotesArea() {
super();
// animate = new Animate();
// var paths:Vector.<MotionPath> = new Vector.<MotionPath>();
// movePath = new SimpleMotionPath("top");
// resizePath = new SimpleMotionPath("height");
// paths.push(movePath);
//paths.push(resizePath);
// animate.duration = 300;
// animate.motionPaths = paths;
// animate.addEventListener(EffectEvent.EFFECT_UPDATE, function(e:EffectEvent):void {
// trace("y = " + y);
// invalidateDisplayList();
// });
// animate.addEventListener(EffectEvent.EFFECT_END, function(e:EffectEvent):void {
// trace("EFFECT ended: y = " + y);
// });
moveEffect = new Move();
moveEffect.duration = 250;
moveEffect.repeatCount = 1;
moveEffect.addEventListener(EffectEvent.EFFECT_END, function (e:EffectEvent):void {
//(this.parent as Group).autoLayout = true;
trace("move effect ran. y = " + y + ", top = " + top);
});
//this.setStyle("moveEffect", moveEffect);
//
// resizeEffect = new Resize();
// resizeEffect.duration = 250;
// this.setStyle("resizeEffect", resizeEffect);
}
}
yourTextField.addEventListener(SoftKeyboardEvent.SOFT_KEYBOARD_DEACTIVATE, onActivating);
yourTextField.addEventListener(SoftKeyboardEvent.SOFT_KEYBOARD_ACTIVATING, onActivating);
yourTextField.addEventListener(SoftKeyboardEvent.SOFT_KEYBOARD_ACTIVATE, onActivating);
// in the event listener to the textField IE :
private function onActivating(event:SoftKeyboardEvent):void
{
//listen to the event; make your move here.
}

as3 duplicating a text field without removing it from stage

I am trying to duplicate a text field. First I get the text with a mc.getChildAt(0) and then copy all the contents into a new textfield. The problem I am having is that getChildAt removes the textfield from the movieclip it is in. How to I get the properties of the textfield without moving it? Or maybe it is something else and what I am doing is fine. Any insight would be a huge help...
var dupeTField:MovieClip = duplicateTextField($value.sourceImg.getChildAt(0));
private function duplicateTextField($textField):MovieClip
{
var currTextField:TextField = $textField;
var dupeTextHolder:MovieClip = new MovieClip();
var dupeTextField:TextField = new TextField();
dupeTextField.text = currTextField.text;
dupeTextField.textColor = currTextField.textColor;
dupeTextField.width = $textField.width;
dupeTextField.height = $textField.height;
dupeTextHolder.addChild(dupeTextField);
return dupeTextHolder;
}
Use something like this:
package com.ad.common {
import flash.text.TextField;
import flash.utils.describeType;
public function cloneTextField(textField:TextField, replace:Boolean = false):TextField {
var clone:TextField = new TextField();
var description:XML = describeType(textField);
for each (var item:XML in description.accessor) {
if (item.#access != 'readonly') {
try {
clone[item.#name] = textField[item.#name];
} catch(error:Error) {
// N/A yet.
}
}
}
clone.defaultTextFormat = textField.getTextFormat();
if (textField.parent && replace) {
textField.parent.addChild(clone);
textField.parent.removeChild(textField);
}
return clone;
}
}
I think you'll find your problem is somewhere else. getChildAt does not remove its target from its parent, and the function you posted works as advertised for me, creating a duplicate clip without affecting the original.
private var dupeTField:MovieClip;
private function init():void
{
//getChildAt will return a DisplayObject so you
//should cast the return DisplayObject as a TextField
var tf:TextField = $value.sourceImg.getChildAt(0) as TextField;
dupeTField = duplicateTextField(tf);
//don't forget to add your duplicate to the Display List
//& make sure to change the x, y properties so that
//it doesn't sit on top of the original
addChild(dupeTField );
}
private function duplicateTextField(textField:TextField):MovieClip
{
var dupeTextHolder:MovieClip = new MovieClip();
var dupeTextField:TextField = new TextField();
//if you pass a TextField as a parameter, you don't need to
//replicate the instance inside the function, simply access the
//parameter directly.
//You may consider copying the TextFormat as well
dupeTextField.defaultTextFormat = textfield.defaultTextFormat;
dupeTextField.text = textField.text;
dupeTextField.textColor = textField.textColor;
dupeTextField.width = textField.width;
dupeTextField.height = textField.height;
dupeTextHolder.addChild(dupeTextField);
return dupeTextHolder;
}

Flex ArgumentError: Error #2025 in ItemRenderer

I've got a problem in an ItemRenderer in Flex 3.5. I've looked at the other posts regarding this error but still can't figure it out. The ItemRenderer is part of an AdvancedDataGrid who's data provider is HierarchicalData. I'm getting the ArgumentError but the trace doesn't go to any of my code. I've gone through in debug mode tons of times but it looks like it doesn't happen until after my code runs. Quite strange.
The item renderer has a couple different parts. It figures out what row it should be drawing for based on the xml data and then adds labels and sprites appropriately. If anyone can help, that would be a great help! Thanks!
Here is one of the methods that gets called if the itemrenderer is on a certain row.
private function addLabels(planList:ArrayCollection):void {
height = 0;
var sprite:Sprite = new Sprite();
var curX:Number = (width / planList.length);
height = 110;
for each (var plan:Plan in planList) {
var label:Label = new Label();
label.text = plan.planner.label;
label.rotationZ = 270;
label.visible = true;
label.x = curX - 7;
//Draw line divider
curX += (width / planList.length);
addChild(label);
label.move(label.x, height - 30);
//Draw divider line
sprite.graphics.lineStyle(.5, 0x000000);
sprite.graphics.moveTo(label.x - ((width / planList.length) / 2) + 10.5, 0);
sprite.graphics.lineTo(label.x - ((width / planList.length) / 2) + 10.5, height - 28);
//Draw meatball
sprite.graphics.beginFill(0x00FF33);
sprite.graphics.drawCircle(label.x + 10, height - 15, 10);
sprite.graphics.endFill();
}
rawChildren.addChild(sprite);
}
There's another function that gets called on a different row, but if I comment out the code above everything works fine, so my guess is that the problem definitely lies there. Thanks for the help!
Here is where addLabels gets called:
override protected function createChildren():void {
removeAllChildren();
var count:int = rawChildren.numChildren;
for (var i:int = 0; i < count; i++) {
if (rawChildren.getChildAt(0).parent) {
rawChildren.removeChildAt(0);
}
}
var allPlans:ArrayCollection = new ArrayCollection();
if (_plan) {
getAllPlans(_plan, allPlans);
}
if (_name == "capability") {
}
else if (_name == "components") {
height = 130;
width = 335;
addLabels(allPlans); // <-- RIGHT HERE!
var sprite:Sprite = new Sprite();
sprite.graphics.lineStyle(.5, 0x000000);
sprite.graphics.moveTo(0, 0);
sprite.graphics.lineTo(width, 0);
sprite.graphics.moveTo(0, height - 28);
sprite.graphics.lineTo(width, height - 28);
rawChildren.addChild(sprite);
}
}
I've seen this kind of thing before. This error could be happening in the AdvancedDataGrid itself, not the itemRenderer.
See http://www.judahfrangipane.com/blog/?p=196 for more information.
If you simply want to draw something on specific rows, you might try extending the AdvancedDataGrid to override two functions:
override protected function drawHorizontalLine(s:Sprite, rowIndex:int, color:uint, y:Number):void {
// do some drawing
}
override protected function drawRowBackground(s:Sprite, rowIndex:int, y:Number, height:Number, color:uint, dataIndex:int):void {
// do some drawing
}
Here's the answer if anyone was looking at this..
I'm still not exactly sure what the problem was, but the AdvancedDataGrid certainly did not like me adding a Label as a child to the renderer. Here's how I got around it.. add a TextField as a child to the sprite, as shown:
var newLabel:TextField = new TextField();
newLabel.text = plan.planner.label;
newLabel.rotationZ = 270;
newLabel.visible = true;
newLabel.x = curX - (dividerWidth / 2) - ((newLabel.textHeight) / 1.5);
newLabel.y = height - (RADIUS * 2.5);
newLabel.antiAliasType = AntiAliasType.ADVANCED;
sprite.addChild(newLabel);
Hope this helps someone!

Flash "visible" issue

I'm writing a tool in Flex that lets me design composite sprites using layered bitmaps and then "bake" them into a low overhead single bitmapData. I've discovered a strange behavior I can't explain: toggling the "visible" property of my layers works twice for each layer (i.e., I can turn it off, then on again) and then never again for that layer-- the layer stays visible from that point on.
If I override "set visible" on the layer as such:
override public function set visible(value:Boolean):void
{
if(value == false) this.alpha = 0;
else {this.alpha = 1;}
}
The problem goes away and I can toggle "visibility" as much as I want. Any ideas what might be causing this?
Edit:
Here is the code that makes the call:
private function onVisibleChange():void
{
_layer.visible = layerVisible.selected;
changed();
}
The changed() method "bakes" the bitmap:
public function getBaked():BitmapData
{
var w:int = _composite.width + (_atmosphereOuterBlur * 2);
var h:int = _composite.height + (_atmosphereOuterBlur * 2);
var bmpData:BitmapData = new BitmapData(w,h,true,0x00000000);
var matrix:Matrix = new Matrix();
var bounds:Rectangle = this.getBounds(this);
matrix.translate(w/2,h/2);
bmpData.draw(this,matrix,null,null,new Rectangle(0,0,w,h),true);
return bmpData;
}
Incidentally, while the layer is still visible, using the Flex debugger I can verify that the layer's visible value is "false".
Wait a frame between setting visible and calling changed(). Use the invalidateProperties()/commitProperties() model.
private bool _isChanged = false;
private function onVisibleChange():void
{
_layer.visible = layerVisible.selected;
_isChanged = true;
invalidateProperties();
}
protected override function commitProperties():void {
super.commitProperties();
if (_isChanged) {
_isChanged = false;
changed();
}
}

How to data bind DataGrid component without scrolling up?

I have a DataGrid component that I would like to update every 5 seconds. As rows are being added to this DataGrid I noticed that every update causes it to reset the scroll bar position to the top. How can I manage to keep the scroll bar at its previous position?
make a variable to store your last scroll position and use that.
roughly something like:
var lastScroll:Number = 0;
private function creationCompleteHandler(event:FlexEvent):void{
stage.addEventListener(MouseEvent.MOUSE_UP, updateLastScroll);
}
private function updateLastScroll(event:MouseEvent):void{
lastScroll = myDataGrid.verticalScrollPosition
}
private function dataGridHandler(event:Event):void{
myDataGrid.verticalScrollPosition = lastScroll;
}
It's not the best code, but it illustrates the point, whenever someone finishes the scroll event, you store last position in a variable and you use that to restore the scroll position right after you've added new data.
I wrote a little extension class to DataGrid based on this article. It seems to work great.
public final class DataGridEx extends DataGrid
{
public var maintainScrollAfterDataBind:Boolean = true;
public function DataGridEx()
{
super();
}
override public function set dataProvider(value:Object):void {
var lastVerticalScrollPosition:int = this.verticalScrollPosition;
var lastHorizontalScrollPosition:int = this.horizontalScrollPosition;
super.dataProvider = value;
if(maintainScrollAfterDataBind) {
this.verticalScrollPosition = lastVerticalScrollPosition;
this.horizontalScrollPosition = lastHorizontalScrollPosition;
}
}
I haven't tested this code, but it should work:
var r:IListItemRenderer;
var len:Number = dataGrid.dataProvider.length;
var i:Number;
//indexToItemRenderer returns null for items that are not visible
for(i = 0; i < len; i++)
{
r = dataGrid.indexToItemRenderer(i);
if(r)
break;
}
//now i contains the first visible item - store this in a variable
lastPos = i;
//update the dataprovider here.
//now scroll to the position
dataGrid.scrollToIndex(lastPos);
These might help:
Maintain Scroll Position after Asynchronous Postback
Maintaining Scroll Position on Postback (Scroll further down to read this)

Resources