Flex List ItemRenderer with image loses BitmapData when scrolling - apache-flex

Hi i have a mx:List with a DataProvider. This data Provider is a ArrayCollection if FotoItems
public class FotoItem extends EventDispatcher
{
[Bindable]
public var data:Bitmap;
[Bindable]
public var id:int;
[Bindable]
public var duration:Number;
public function FotoItem(data:Bitmap, id:int, duration:Number, target:IEventDispatcher=null)
{
super(target);
this.data = data;
this.id = id;
this.duration = duration;
}
}
my itemRenderer looks like this:
<?xml version="1.0" encoding="utf-8"?>
<mx:VBox 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.collections.ArrayCollection;
]]>
</fx:Script>
<s:Label text="index"/>
<mx:Image source="{data.data}" maxHeight="100" maxWidth="100"/>
<s:Label text="Duration: {data.duration}ms"/>
<s:Label text="ID: {data.id}"/>
</mx:VBox>
Now when i am scrolling then all images that leave the screen disappear :(
When i take a look at the arrayCollection every item's BitmapData is null.
Why is this the case?

I changed Datatype of data in Class FotoItem from Bitmap to BitmapData
in the ItemRenderer i do the following:
override public function set data( value:Object ) : void {
super.data = value;
pic.source = new Bitmap(value.image);
}
this works now. No idea why it is not working with bitmaps

I think it might be something with your use of data.data - I believe data is a reserved keyword in Actionscript, and it might be best to name your image property something else, such as data.imageData.
I'm also not sure why you're importing ArrayCollection into your item renderer as you don't appear to be using it in your itemRenderer.
You may also be running into problems with itemRenderer recyling. You may want to override public function set data() and handle setting the individual item properties there instead of relying on binding.
Where are you looking at the arrayCollection to see that the bitmapData is null?

Related

How can you programmatically make the "mouseover" item into the selected item in a tree/list?

I would like to programmatically change a selected item, in a tree or list, to the item currently "marked/focused" under the mouse pointer .
I'm working with an Flex Air standalone application.
I was thinking in the lines of:
myTree.selectedItem = EVENT.TARGET (where EVENT could be a mouseover/rightclick/rollOver event, and TARGET should be the node/item currently under the mouse pointer).
Is there a way of doing this (or in any other way)?
Ahh, and i want to do it without left clicking ;-)
Thank you in advance,
Sebastian
I found this interesting enough so I am asking if this is the easiest way to achieve this. First off, instead of the list, you need to add the rollOver-listener to the ItemRenderer, not to the list itself (as the event.target and event.currentTarget will just show your list).
So lets create a custom ItemRenderer and add a rollOver listener
<xml version="1.0" encoding="utf-8"?>
<s:ItemRenderer xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx"
autoDrawBackground="true" height="20" rollOver="itemrenderer1_rollOverHandler(event)">
<fx:Script>
<![CDATA[
protected function itemrenderer1_rollOverHandler(event:MouseEvent):void
{
this.dispatchEvent(new CustomEvent(CustomEvent.SELECT_ITEM, data, true));
}
]]>
<s:Label id="label1" text="{data.label}"/>
</s:ItemRenderer>
You need to somehow get the value of the selected item (which is the data on the itemRenderer) so I created a CustomEvent-class just to do so.
package
{
import flash.events.Event;
public class CustomEvent extends Event
{
public var selectedItem:Object;
public static const SELECT_ITEM:String = "selectItem";
public function CustomEvent(type:String, selectedItem:Object, bubbles:Boolean=false, cancelable:Boolean=false)
{
super(type, bubbles, cancelable);
this.selectedItem = selectedItem;
}
}
}
then I added a eventListener to the main class and set the list.selectedItem property accordingly:
//for the main MXML initializer:
this.addEventListener(CustomEvent.SELECT_ITEM, rollOverChangeSelected);
//and the function:
protected function rollOverChangeSelected(ce:CustomEvent):void{
list.selectedItem = ce.selectedItem;
}
Another way: bindable variable
The list:
s:List id="list" allowMultipleSelection="true" selectionColor="red" rollOverColor="red" itemRenderer="customItemRenderer" selectedItem="{_rollOverSelectedItem}">
The variable and set / get methods:
[Bindable] public var _rollOverSelectedItem:Object;
public function get rollOverSelectedItem():Object
{
return _rollOverSelectedItem;
}
public function set rollOverSelectedItem(value:Object):void
{
_rollOverSelectedItem = value;
}
and the ItemRenderer's rollOver-method:
protected function itemrenderer1_rollOverHandler(event:MouseEvent):void
{
this.parentApplication.rollOverSelectedItem = data;
}
What is the best/proper way?

Bind to object property

AS
[Bindable]
var object:Object = {
property: "Property"
};
MXML
<s:Label text="{object.property}"/>
The labels text will be "Property", but if object.property is changed, the label isn't updated. Is there any way around this?
Properties of an object or collection will not dispatch a property change event unless implemented.
Likewise to your example, a change to an Array element will not be bound.
Collections such as ArrayCollection wrap objects within a proxy to dispatch events for binding.
Use an ObjectProxy to dispatch changes to your object.
Instantiate an ObjectProxy and listen for PropertyChangeEvent:
objectProxy = new ObjectProxy(object);
objectProxy.addEventListener(PropertyChangeEvent.PROPERTY_CHANGE, propertyChangeHandler);
Access your object via the proxy, such as setting a property named 'property':
objectProxy.property = "Hello, world";
This example creates an ObjectProxy with a timer to change the 'property' member every second:
<?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="creationCompleteHandler(event)">
<fx:Script>
<![CDATA[
import mx.events.FlexEvent;
import mx.events.PropertyChangeEvent;
import mx.utils.ObjectProxy;
private var object:Object = {};
private var objectProxy:ObjectProxy;
private var timer:Timer;
protected function creationCompleteHandler(event:FlexEvent):void
{
objectProxy = new ObjectProxy(object);
objectProxy.addEventListener(PropertyChangeEvent.PROPERTY_CHANGE, propertyChangeHandler);
timer = new Timer(1000);
timer.addEventListener(TimerEvent.TIMER, timerHandler);
timer.start();
}
protected function propertyChangeHandler(event:PropertyChangeEvent):void
{
label.text = (event.source).property;
}
protected function timerHandler(event:TimerEvent):void
{
objectProxy.property = Math.random() * 1000;
}
]]>
</fx:Script>
<s:Label id="label" />
</s:Application>
You're binding to object itself. That means, that the text of the label would change only if reference to object will be changed.
object={property:"Hello World"}
In this case the binding will do the job.
If you want the text to update after some property change, you should make this property Bindable.
public class MyObject {
[Bindable]
public var property:String;
}
Then declare the variable of class MyObject and bind to its property.

Flex 4: Is there a simple way to extend Spark button?

I created a somewhat custom Spark button by doing the File > New > MXML skin and basing it on spark.components.button. The problem is that I need to add an extra text field to the button component and dynamically change that text...but of course, the property isn't recognized on a Spark Button.
Is there a simple way to add this field to my custom button skin & its property so it can be addressed? If not, is there a simple way to take what I've done and just extend the Spark Button? I can't seem to find any examples that show how to do it without writing it all up in ActionScript.
I'm glad you asked! This is much easier than you'd think so don't be discouraged! ActionScript is pretty easy once you get the hang of it.
First of all, let's define what we want. After reading your question I believe you would like to use your button something like this:
<local:MyCustomButton label="Hello" label2="World!"/>
So let's go over how to make that a reality.
Now, I would highly suggest extending Button with ActionScript, but it is also possible to do in mxml:
//MyCustomButton.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">
</s:Button>
Then you can add the SkinParts you need in a <fx:Script>:
<?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">
<fx:Script>
<![CDATA[
[SkinPart]
public var secondLabelDisplay:spark.components.Label;
]]>
</fx:Script>
</s:Button>
So now when you make a skin you should include something like the original label, just with a different ID to reflect your new SkinPart:
But wait! What text should our second label show?? Well, we will need to add another property that you can set for each individual instance of the button:
<?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">
<fx:Script>
<![CDATA[
[SkinPart]
public var secondLabelDisplay:spark.components.Label;
private var _label2:String;
public function get label2():String
{
return _label2;
}
public function set label2(value:String):void
{
_label2 = value;
}
]]>
</fx:Script>
</s:Button>
Cool, so now we can set label2 when we use our button, but at this point it won't change the label's actual text property. We need to hook up our label2 to our secondLabelDisplay. We do this by calling invalidateProperties when the label2 changes and then change the label in commitProperties (which will be called because of the invalidateProperties() call):
<?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">
<fx:Script>
<![CDATA[
[SkinPart]
public var secondLabelDisplay:spark.components.Label;
private var _label2:String;
private var label2Changed:Boolean;
public function get label2():String
{
return _label2;
}
public function set label2(value:String):void
{
_label2 = value;
label2Changed = true;
invalidateProperties();
}
override protected function commitProperties():void
{
super.commitProperties();
if(label2Changed)
{
label2Changed = false;
secondLabelDisplay.text = label2;
}
}
]]>
</fx:Script>
</s:Button>
Lastly, you'll notice that if you change label2 again afte runtime, the label will show the change. But it won't show the change if you set an initial label2 like in our target usage. The Flex team made a special method just for this case, partAdded(). I won't go over too many details about it because there is already a good amount of literature on the subject.
Finally, here's our finished, custom button awaiting a skin to put it to use:
<fx:Script>
<![CDATA[
[SkinPart]
public var secondLabelDisplay:spark.components.Label;
private var _label2:String;
private var label2Changed:Boolean;
public function get label2():String
{
return _label2;
}
public function set label2(value:String):void
{
_label2 = value;
label2Changed = true;
invalidateProperties();
}
override protected function commitProperties():void
{
super.commitProperties();
if(label2Changed)
{
label2Changed = false;
secondLabelDisplay.text = label2;
}
}
override protected function partAdded(partName:String, instance:Object):void
{
if(instance == secondLabelDisplay)
{
secondLabelDisplay.text = _label2;
}
}
]]>
</fx:Script>
Best of luck!

Unable to bind to property of custom component

Can't seem to bind to data from within a custom component. I've tried BindUtilis and {} but can't seem to fathom it out. Here's what I've got:
I have a class DataModel which has been made bindable
Within Mainn.mxml I have two components: DataGrid (used for testing) & CustomComponent (which extends Canvas)
When the data within DataModel.somelist is updated the DataGrid reflects the changes but the CustomComponent doesn't appear to.
I was expecting to see the trace (CustomComponent.dataProvider) fired whenever this._dataModel.itemList is changed. What am I doing wrong?
Main.mxml looks something like this:
<mx:Script>
<![CDATA[
import model.DataModel;
[Bindable]
private var _dataModel:DataModel = DataModel.getInstance();
]]>
</mx:Script>
<mx:VBox width="100%" height="100%">
<views:ItemDisplayList width="100%" height="300" id="idl" >
<views:dataProvider>
{this._dataModel.itemList}
</views:dataProvider>
</views:ItemDisplayList>
<mx:DataGrid id="dg" width="100%" height="300" >
<mx:dataProvider>
{this._dataModel.itemList}
</mx:dataProvider>
</mx:DataGrid>
</mx:VBox>
The CustomComponent has this AS class:
package code{
import model.DataModel;
import mx.containers.Canvas;
public class CustomComponent extends Canvas{
[Bindable]
private var _dataModel:DataModel = DataModel.getInstance();
private var _dataProvider:ArrayCollection ;
public function CustomComponent(){
super();
_dataProvider = new ArrayCollection();
trace("CustomComponent start");
}
public function get dataProvider() : ArrayCollection {
trace("get dataProvider");
return _dataProvider;
}
public function set dataProvider(value: ArrayCollection) : void {
trace("set dataProvider");
this._dataProvider = value;
invalidateProperties();
invalidateSize();
invalidateDisplayList();
dispatchEvent(new FlexEvent(FlexEvent.DATA_CHANGE));
}
...
override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number ) : void{
trace("updateDisplayList");
super.updateDisplayList(unscaledWidth, unscaledHeight);
}
}
}
Are you resetting the dataProvider in your custom component, or by updating you mean adding/removing an item from dataModel.itemList?
If you're adding an item to dataModel.itemList, then your custom component wont update but the DataGrid will. This is because in the DataGrid (maybe it's in the core ListBase component in Flex?), when you set dataProvider, it adds an event listener for CollectionEvent.COLLECTION_CHANGE. When it hears that (when something in your ArrayCollection/IList changes, from remove/add/refresh/etc), it runs a few methods to update the DataGrid's itemRenderers. You'll have to code that manually if you create your own dataProvider property.
If you set the dataProvider explicitly on your custom component (custom.dataProvider = myArrayCollection), it will update. But that's not the most efficient thing to do. I recommend extending one of the List classes that has already implemented a dataProvider property, it's a lot to tackle.
Check out the source for the mx ListBase's dataProvider method to see what they did.
Hope that helps,
Lance

How do I implement data binding in an ActionScript Class?

I am having a problem with binding values in my ActionScript components. I basically want to set the value of a a variable in my component to a value in the model, and have the component variable automatically update when the model value is updated. I think that I just don't fully understand how data binding works in Flex - this is not a problem when using MXML components, but, when using ActionScript classes, the binding does not work.
This is the code I'm using, where the values are not binding:
package
{
public class Type1Lists extends TwoLists
{
public function Type1Lists()
{
super();
super.availableEntities = super.composite.availableType1Entities;
super.selectedEntities = super.composite.selectedType1Entities;
}
}
}
package
{
public class Type2Lists extends TwoLists
{
public function Type2Lists()
{
super();
super.availableEntities = super.composite.availableType2Entities;
super.selectedEntities = super.composite.selectedType2Entities;
}
}
}
/* TwoLists.mxml */
<?xml version="1.0" encoding="utf-8"?>
<mx:HBox xmlns:mx="http://www.adobe.com/2006/mxml">
<mx:Script>
<![CDATA[
public var __model:ModelLocator = ModelLocator.getInstance();
public var composite:Composite =
__model.selectedComposite;
[Bindable]
public var availableEntities:ArrayCollection;
[Bindable]
public var selectedEntities:ArrayCollection;
]]>
</mx:Script>
<mx:List id="availableEntitiesList" dataProvider="{availableEntities}" />
<mx:List id="selectedEntitiesList" dataProvider="{selectedEntities}" />
</mx:HBox>
To use binding by code you should use mx.binding.utils.*
Take a look and the BindingUtils.bindProperty and bindSetter methods.
Also, be careful with manual databinding, it could lead you to memory leaks.
To avoid them, save the ChangeWatcher returned by bindProperty and bindSetter and call watcher's unwatch method when is no more used (i.e, in the dipose or destructor method)
You need to add the [Bindable] tag either to the class itself (making all properties bindable) or the properties you want to be [Bindable]. Marking properties or objects as [Bindable] in your MXML is not sufficient.
To fix this, I simply converted the classes to MXML components, and added a private variable for my ModelLocator.
/* Type1Lists.mxml */
<?xml version="1.0" encoding="utf-8"?>
<TwoLists xmlns:mx="http://www.adobe.com/2006/mxml"
xmlns="*"
availableEntities="{__model.selectedComposite.availableType1Entities}"
selectedEntities="{__model.selectedComposite.selectedType1Entities}">
<mx:Script>
<![CDATA[
import model.ModelLocator;
[Bindable]
private var __model:ModelLocator = ModelLocator.getInstance();
</mx:Script>
</TwoLists>
/* Type2Lists.mxml */
<?xml version="1.0" encoding="utf-8"?>
<TwoLists xmlns:mx="http://www.adobe.com/2006/mxml"
xmlns="*"
availableEntities="{__model.selectedComposite.availableType2Entities}"
selectedEntities="{__model.selectedComposite.selectedType2Entities}">
<mx:Script>
<![CDATA[
import model.ModelLocator;
[Bindable]
private var __model:ModelLocator = ModelLocator.getInstance();
</mx:Script>
</TwoLists>

Resources