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();
}
}
Related
I'm using the tooltipManager as described HERE.
I want to display a custom tooltip for the buttons of a buttonbar.
The buttonbar's declaration is as follow:
<mx:ButtonBar id="topToolbar" height="30" dataProvider="{topToolbarProvider}"
iconField="icon" itemClick="topToolbarHandler(event)"
buttonStyleName="topButtonBarButtonStyle"
toolTipField="tooltip"/>
Until here, everything works fine. I do see the proper text displaying in a tooltip.
Then, I created a custom tooltip manager using the tutorial quoted previously:
public class TooltipsManager
{
private var _customToolTip:ToolTip;
public function TooltipsManager()
{
}
public function showToolTipRight(evt:MouseEvent, text:String):void
{
var pt:Point = new Point(evt.currentTarget.x, evt.currentTarget.y);
// Convert the targets 'local' coordinates to 'global' -- this fixes the
// tooltips positioning within containers.
pt = evt.currentTarget.parent.contentToGlobal(pt);
customToolTip = ToolTipManager.createToolTip(text, pt.x, pt.y, "errorTipRight") as ToolTip;
customToolTip.setStyle("borderColor", "0xababab");
// Move the tooltip to the right of the target
var xOffset:int = evt.currentTarget.width + 5;//(customToolTip.width - evt.currentTarget.width) / 2;
customToolTip.x += xOffset;
}
public function showToolTipAbove(evt:MouseEvent, text:String):void
{
var pt:Point = new Point(evt.currentTarget.x, evt.currentTarget.y);
// Convert the targets 'local' coordinates to 'global' -- this fixes the
// tooltips positioning within containers.
pt = evt.currentTarget.parent.contentToGlobal(pt);
customToolTip = ToolTipManager.createToolTip(text, pt.x, pt.y, "errorTipAbove") as ToolTip;
customToolTip.setStyle("borderColor", "#ababab");
// Move tooltip below target and add some padding
var yOffset:int = customToolTip.height + 5;
customToolTip.y -= yOffset;
}
public function showToolTipBelow(evt:MouseEvent, text:String):void
{
var pt:Point = new Point(evt.currentTarget.x, evt.currentTarget.y);
// Convert the targets 'local' coordinates to 'global' -- this fixes the
// tooltips positioning within containers.
pt = evt.currentTarget.parent.contentToGlobal(pt);
customToolTip = ToolTipManager.createToolTip(text, pt.x, pt.y, "errorTipBelow") as ToolTip;
customToolTip.setStyle("borderColor", "ababab");
// Move tooltip below the target
var yOffset:int = evt.currentTarget.height + 5;
customToolTip.y += yOffset;
}
// Remove the tooltip
public function killToolTip():void
{
ToolTipManager.destroyToolTip(customToolTip);
}
[Bindable]
public function get customTooltip():ToolTip { return _customToolTip; }
public function set customTooltip(t:ToolTip):void { _customToolTip = t; }
}
Now, this is where I start having issues...
I'm trying to get to use this custom tooltip, but I don't know how to get the buttonbar to take it into account.
I created a function to see when could I call the functions in my TooltipsManager:
public function showTopToolbarTooltip(e:ToolTipEvent):void{
trace('blabla');
}
But it would seem that it's never taken into account. I've put this function in different buttonbar's events: tooltipcreate, tooltipstart, tooltipend but nothing happens. Not a single trace...
Could anyone tell me where to call the function of my tooltipManager?
Thanks a lot for your help.
Kind regards,
BS_C3
Actually, skipped a part of the tutorial =_= Attaching the functions to the mouseover/out events... Sorry for that.
I have a object that is extended from arraycollection. This object has to access and manipulate the arraycollections source object. When this happens, the local sorted/filter copy of data goes out of sync with the source data. To line things up correctly, the sort/filter needs to be re-applied.
To do this normally, you would call refresh() on the arraycollection, but this also broadcasts a refresh event. What I want is to update the sort/filter without dispatching an event.
Having looked into the ArrayCollection class, I can see it is extended from ListCollectionView. The refresh function
public function refresh():Boolean
{
return internalRefresh(true);
}
is in ListCollectionView and it calls this function
private function internalRefresh(dispatch:Boolean):Boolean
{
if (sort || filterFunction != null)
{
try
{
populateLocalIndex();
}
catch(pending:ItemPendingError)
{
pending.addResponder(new ItemResponder(
function(data:Object, token:Object = null):void
{
internalRefresh(dispatch);
},
function(info:Object, token:Object = null):void
{
//no-op
}));
return false;
}
if (filterFunction != null)
{
var tmp:Array = [];
var len:int = localIndex.length;
for (var i:int = 0; i < len; i++)
{
var item:Object = localIndex[i];
if (filterFunction(item))
{
tmp.push(item);
}
}
localIndex = tmp;
}
if (sort)
{
sort.sort(localIndex);
dispatch = true;
}
}
else if (localIndex)
{
localIndex = null;
}
revision++;
pendingUpdates = null;
if (dispatch)
{
var refreshEvent:CollectionEvent =
new CollectionEvent(CollectionEvent.COLLECTION_CHANGE);
refreshEvent.kind = CollectionEventKind.REFRESH;
dispatchEvent(refreshEvent);
}
return true;
}
annoyingly, that function is private and so is unavailable to and class that extends ListCollectionView. Also, a lot of what is in the internalRefresh function is private too.
Does anyone know of a way to call internalRefresh from a class that extends ArrayCollection? Or a way of stopping the refresh event from being dispatched when refresh is called?
My (read:hack) solution to this:
addEventListener(CollectionEventKind.REFRESH, handlerHack, true);
The true adds this listener onCapture, before anyone else gets to act on the event.
Before you call the collection.refresh() to update sort/filter, set a boolean flag to true.
discardRefreshEvent = true;
myCol.refresh();
In the listener...
private function handlerHack(evt:CollectionEvent):void
{
if (discardRefreshEvent)
{
evt.stopImmediatePropagation();
discardRefreshEvent = false;
}
}
Disclaimer: Haven't done this exact use before (have implemented similar functionality with other events), also only guessing on Event types/names.
maybe you could extend ArrayCollection, listen to the refresh event and call stopImmediatePropagation() on it when it is fired ? I would start with this...
Good luck :-)
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;
}
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 :-) ]
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)