GridColumn labelFunction not bindable - actionscript-3

Recently i noticed one problem when using labelFunction for gridColumn:
It does not refresh, when nested (displayed) property is changed.
// SomeClass and SomeSubClass has [Bindable] metadata
[Bindable]
private var someSubClass:SomeSubClass;
[Bindable]
private var someClass:SomeClass;
private function inits():void{
someSubClass = new SomeSubClass();
someSubClass.value = "SomeSubValue";
someClass = new SomeClass();
someClass.subclass = someSubClass;
var ac:ArrayCollection = new ArrayCollection();
ac.addItem(someClass);
dg.dataProvider = ac;
}
private function myLabelFunction(item:Object, column:GridColumn):String
{
return (item.subclass)? item.subclass.value : "";
}
mxml
<s:Button click="someSubClass.value = 'Hello World'"/>
<s:DataGrid dataProvider="{dataProvider}" width="100%" height="200">
<s:columns>
<s:ArrayList>
<s:GridColumn labelFunction="{myLabelFunction}" headerText="NoRenderer"/>
<s:GridColumn headerText="WithRenderer">
<s:itemRenderer>
<fx:Component>
<s:GridItemRenderer>
<s:Label text="{data.subclass.value}"/>
</s:GridItemRenderer>
</fx:Component>
</s:itemRenderer>
</s:gridColumn>
</s:ArrayList>
</s:columns>
</s:DataGrid>
When button clicked first column which use labelFunction still displaing old value, but second column (with custom renderer) works fine. I am trying to use as less as possible custom item renderers for performance issue. Or something wrong with my label function?

I've never tried this, but I'm not surprised that it won't update. In the second example, you've explicitly set a binding chain on data.subclass.value. In the first case with myLabelFunction, there's no binding set up on item.subclass, so Flex won't have any "magic" way of knowing you'd like to watch that value for changes.
I would go with the custom renderer, I'm not sure what your particular performance concerns are but it should be manageable as long as you leave virtual layout / renderer recyling on.

Related

Data Value lose color when Scrolling on Datagrid in Flex 4

I am having problem when I am scrolling my datagrid. My item renderer column is losing its value when I scroll.
In my code, I change the colour of the itemrenderer (a label) on MouseOver and MouseOut. When I load the datagrid, this works fine but when I scroll down my grid some values have their colours already changed as if the MouseOver event has been executed on them.
Any one can tell me what is the issue with this?
Please have a look at the code for my datagrid and the itemrenderer. Please note that I am using a flexicious datagrid in my case.
Thanks for your precious help.
<flxs:columnLevel>
<flxs:FlexDataGridColumnLevel>
<flxs:columns>
<flxs:FlexDataGridColumn dataField="testcol" width="118" id="coltest">
<flxs:itemRenderer>
<fx:Component>
<mx:VBox horizontalAlign="left" paddingLeft="10" verticalAlign="middle">
<fx:Script>
<![CDATA[
import mx.controls.Alert;
protected function lbl1_clickHandler(event:MouseEvent):void
{
//Do Something
}
protected function lbl1_mouseOverHandler(event:MouseEvent):void
{
var dataColor:uint;
var dataUnderline:String = 'none';
if (data.payer == 'D'){
dataColor = 0x999999;
}
else{
dataColor = 0x0DACE0; //color
}
}
protected function lbl_mouseOutHandler(event:MouseEvent):void
{
var dataColor:uint;
if (data.payer == 'D'){
dataColor = 0x999999;
}
else{
dataColor = 0x000000;
}
}
]]>
</fx:Script>
<mx:Label id="lbl" paddingLeft="10" left="10" fontWeight="normal" mouseOut="lbl_mouseOutHandler(event)" mouseOver="lbl_mouseOverHandler(event)" text="{data.testcol}" click="lbl1_clickHandler(event)"/>
</mx:VBox>
</fx:Component>
</flxs:itemRenderer>
</flxs:FlexDataGridColumn>
</flxs:columns>
</flxs:FlexDataGridColumnLevel>
</flxs:columnLevel>
</flxs:FlexDataGrid>*
Short answer: You haven't overridden the "set data" on your itemrenderer, so it will keep the previous properties.
Itemrenderers in the Flex world are recycled, meaning that even though you may have 100 objects in your list, you only have 10-12 actual instances of your inline itemrenderer. The caveat is that the when the itemrenderer is populated with new data, the data-dependent properties aren't reset. That lack of resetting is why you have renderers that appear to have been mousedOver when in fact they havent'.
The standard solution is to override the "set data" function which does exactly as it sounds, it performs operations when the data is set. If I had a similar problem, my code would look like (best I can do with a 9:00AM memory).
override public function set data(value:Object):void
{
if( value != null )
{
super.data = value'
dataColor = 0xwhatever-color-non-moused-over-objects-should-have;
}
}
Here is a link with more information.

Item Renderer not Working correctly in Spark List

I used flex sdk 4.5 for creating Adobe Air application. My application download files one by one. I show the currently downloading, pending and completed files in spark list. I used item Renderer that show the progress of the file and other states.
It works fine if selected files fit in current view. But as number of downloads increase, the Spark list shows vertical scroll. here I have the problem. Here file downloading functionality works fine, but if I try to scroll the list, File titles mix up or displayed out of order.
item renderer code:
<?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"
creationComplete="onInit()"
>
<fx:Script><![CDATA[
import com.streamingnetworks.hdvrwin.vo.DownloadInfo;
import mx.events.CloseEvent;
[Bindable]private var downloadInfo:DownloadInfo;
private var url:String = "";
private var fileName:String = "";
private function onInit():void
{
downloadInfo = data as DownloadInfo;
downloadInfo.addEventListener("Progress", onProgress);
downloadInfo.addEventListener("DownloadComplete", onComplete);
prog.label = "Downloading %3%%";
lblPath.text = " - " + downloadInfo.getPathIp();
trace("Downloading: " + downloadInfo.name);
}
private function onProgress(e:Event):void
{
prog.setProgress(downloadInfo.downloadedBytes, downloadInfo.totalBytes);
lblState.text = downloadInfo.getDownState();
}
private function onComplete(e:Event):void
{
prog.label = "Download Complete";
}
protected function onClose(event:CloseEvent):void
{
downloadInfo.state = "deleted"
}
]]></fx:Script>
<s:TitleWindow top="0" left="0" right="0" bottom="0" dropShadowVisible="false"
title="{'Downloading ' + downloadInfo.name}" close="onClose(event)">
<mx:ProgressBar id="prog" label="" mode="manual"
trackHeight="20" labelPlacement="center" width="100%"/>
<s:HGroup top="25">
<s:Label id="lblState" text="Downloading 0 Bytes of 0 Bytes."/>
<s:Label id="lblPath"/>
</s:HGroup>
</s:TitleWindow>
</s:ItemRenderer>
here the list control where I am using that renderer
<s:List id="lstControls" dataProvider="{urlArr}"
itemRenderer="ItemRenderer"
left="5" right="5" bottom="5" top="40"/>
urlArr is an arrayList that contains only url in string form.
How can I solve this problem? Any Help will be appreciated. Thanks
Your renderers are not updating correctly because you are setting your data when the renderer is created - but you should be overriding the set data method, eg:
override public function set data(value:Object):void
{
super.data = value;
downloadInfo = data as DownloadInfo;
downloadInfo.addEventListener("Progress", onProgress);
downloadInfo.addEventListener("DownloadComplete", onComplete);
prog.label = "Downloading %3%%";
lblPath.text = " - " + downloadInfo.getPathIp();
trace("Downloading: " + downloadInfo.name);
}
And remove the creationComplete listener.
As a better practice. Have all the data your progress bars will need all ready in the data provider and have them react ONLY to changes in the data. Adding and removing listeners and binding internally will only cause you grief. Renderers do not live forever and are re-used inside of list components. Manage everything through overriding set data().
In this example I would create a model object with a name like FileDownloadInfo and properties such as .downloadProgress .fileName and .ipAddress then have the renderer update on property change events of .downloadProgress

Flash Builder Long Press on List - Mobile

I want to display a list (in a tile layout, but I don't think that changes anything) and open up the editing window for the tile that I long press on (i.e. press for at least 1 second). The timer starts fine and the longPressHandler event is correctly called after 1 second, however, the object that is selected is the previous object that I had touched.
For example, say I tap on Object A, then I long press on Object B: the longPressHandler will "open" up Object A in the editing window. I have debugged and I see that the SelectedItem property of my List only updates after I end my long press (as in, after I pick up my finger or release the mouse button). Is there any way to open up the currently selected item?
Relevant Actionscript:
private var longPressTimer:Timer = new Timer(1000,1);
private function startLongPressMouse(event:MouseEvent):void {
startLongPressTimer();
list.addEventListener(MouseEvent.MOUSE_MOVE, endLongPressMouse);
}
private function endLongPressMouse(event:MouseEvent):void {
stopLongPressTimer();
enableClick();
list.removeEventListener(MouseEvent.MOUSE_MOVE, endLongPressMouse);
}
private function startLongPress(event:TouchEvent):void {
startLongPressTimer();
list.addEventListener(TouchEvent.TOUCH_MOVE, endLongPress);
}
private function endLongPress(event:TouchEvent):void {
stopLongPressTimer();
enableClick();
list.removeEventListener(TouchEvent.TOUCH_MOVE, endLongPress);
}
private function startLongPressTimer():void {
longPressTimer.start();
longPressTimer.addEventListener(TimerEvent.TIMER_COMPLETE, longPressHandler);
}
protected function disableClick():void {
trace("disable click");
list.removeEventListener(MouseEvent.CLICK, regularClickHander);
}
public function enableClick():void {
list.callLater(list.addEventListener, [MouseEvent.CLICK, regularClickHander]);
}
public function resetListSelection():void {
list.selectedIndex = -1;
list.validateDisplayList();
}
private function stopLongPressTimer():void{
longPressTimer.stop();
longPressTimer.reset()
}
public function longPressHandler(event:TimerEvent):void{
disableClick();
stopLongPressTimer();
lblD.text = "Long Press Detected on: " + list.selectedItem.className;
}
Relevant MXML:
<s:List id="list" dataProvider="{grades}" touchBegin="startLongPress(event)" touchEnd="endLongPress(event)"
mouseDown="startLongPressMouse(event)" mouseUp="endLongPressMouse(event)"
labelField="name"
left.landscape="10" right.landscape="20" top.landscape="350" bottom.landscape="20"
left.portrait="20" right.portrait="20" top.portrait="350" bottom.portrait="20">
<s:itemRenderer>
<fx:Component>
<s:ItemRenderer width="100%" height="200">
<s:Label text="{data.className}" top="30" horizontalCenter="0" color="#646464"/>
<s:Label text="{data.credits}" top="50" horizontalCenter="0" color="#646464" fontSize="14"/>
<s:Label text="{data.grade}" top="100" horizontalCenter="0" color="#646464" fontSize="14"/>
</s:ItemRenderer>
</fx:Component>
</s:itemRenderer>
<s:layout>
<s:TileLayout requestedColumnCount="3" requestedColumnCount.landscape="4" columnAlign="justifyUsingWidth"/>
</s:layout>
</s:List>
I changed the longPressHandler to just display the name of the selected item rather than open up the editing window.
Let me know if you need any more information. Thank you in advance for your help!
One way is to create a custom event (with custom property) so that you can pass whatever data you want when you dispatch the event.
package events;
{
import flash.events.Event;
public class YourCustomEvent extends Event
{
public var yourData:Object;
public function YourCustomEvent(type:String, yourData:Object)
{
super(type);
this.yourData = yourData;
}
override public function clone():Event{
return new YourCustomEvent(type, yourData);
}
}
}
Hope it helps!
PS: I wanted to put this in the comment but it got bigger as a typed :)

Strange Behaviour on DataGrid with ArrayCollection as DataProvider

I have a Datagrid with an ArrayCollection as DataProvider, the arrayCollection is partially generated by a remoteObject call, the dataprovider seems to works at least until I try to edit the field...
By the RemoteObject I only receive an ArrayCollection with the field ip, but the datagrid looks for the fields ip, check and save...
If I add/edit this new field it works, but only under particular condition
The DataGrid:
<s:DataGrid id="datagrid" left="10" right="10" top="136"
dataProvider="{listaIPCheck}" bottom="10" requestedRowCount="4">
<s:columns>
<s:ArrayList>
<s:GridColumn dataField="ip" headerText="Asset"/>
<s:GridColumn dataField="check" headerText="Inventory"/>
<s:GridColumn dataField="save" headerText="Salvataggio"/>
</s:ArrayList>
</s:columns>
</s:DataGrid>
The Script:
[Bindable]private var listaIPCheck:ArrayCollection;
private function ro_resultHandler(event:Event=null):void
{
listaIPCheck = new ArrayCollection();
listaIPCheck = ro.getListUpdate.lastResult;
heap = 0;
// Read Below {POINT #1}
init3();
}
private function init3():void
{
// Read Below {POINT #2}
if (heap<listaIPCheck.length)
{
// omitted the initialization of the process p
p.addEventListener(NativeProcessExitEvent.EXIT, onExit);
try{
p.start(startupInfo);
}catch(e:Error){}
}
}
private function onExit(e:NativeProcessExitEvent):void {
// Read below {POINT #3}
}
Here is my code, now as you can see there are 3 line where I wrote to read below...
Let's assume to put this simple for instead of the commented line (once at a time)
for (var k:Number=0;k<listaIPCheck.length;k++)
{
listaIPCheck.getItemAt(k).check = "checkVal";
listaIPCheck.getItemAt(k).save = "saveVal";
}
This code always work in the 3 points, so at the end of the call the ArrayCollection is always filled with the new values, but the datagrid refresh the items only in point #1 and #2
Why not in Point #3???
The reason the DataGrid doesn't refresh when you change properties on an item in an ArrayCollection is that because changing the properties does not trigger the collectionChange event. The DataGrid has no way to know that properties within the object changed. It has to do with pointers and memory spaces and what exactly the DataGrid is looking at for binding purposes.
In most cases, the invalidateList() method to force a refresh of the display. You can call the refresh() method or the itemUpdated() method on the collection or replace the dataProvider completely to force a refresh.

Question on bindable variables in AS3

I'm probably misusing bindable variables here, so please bear with me here while I try and explain what I'm trying to to.
I have a simple spark list where I allow people to select a background by clicking on one of the items. When a background is selected, I then save it to the SharedObject in order to use it when the user loads the application again later.
This list is populated by an ArrayCollection (binded variable) created as follows:
[Bindable] private var arrBG:ArrayCollection = new ArrayCollection();
This then gets populated the following way:
var objImage:Object;
var strSharedObjImage:String = sharedObj.sharedBackground.data.backgroundIndex;
// Background
objImage = new Object();
objImage.icon = ICONS_PATH + objImage.label;
objImage.label = "Titanium";
objImage.selected = (strSharedObjImage == objImage.fileName) ? true : false;
arrBG.addItem(objImage);
objImage = new Object();
objImage.icon = ICONS_PATH + objImage.fileName;
objImage.label = "Iron";
objImage.selected = (strSharedObjImage == objImage.label) ? true : false;
arrBG.addItem(objImage);
I then use it as the dataProvider on my spark list.
If you notice above, on my object I have a property called selected, which will get set to true, if the value of my shared object is the same as the value on the "label" property.
On the item renderer for my spark list, I have the following:
<s:ItemRenderer name="HorizontalListSkin"
xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
autoDrawBackground="false"
creationComplete="initMenuSkin(event)"
>
<fx:Script>
<![CDATA[
protected function initMenuSkin(event:Event):void
{
iconImage.source = data.icon;
iconText.text = data.label;
// Check to see if the item we're displying is selected. If it is make it stand out
if(data.selected){
iconText.setStyle("color", "Green")
}
}
]]>
</fx:Script>
<s:VGroup x="10" y="10" width="50" height="50" buttonMode="true" horizontalAlign="center">
<s:Image id="iconImage" horizontalCenter="0"/>
<s:Label id="iconText" fontFamily="Verdana" fontSize="11" fontWeight="bold" horizontalCenter="0" showTruncationTip="false"/>
</s:VGroup>
</s:ItemRenderer>
So as you can see, I'm simply changing the colour of the font on my selected item.
When I load it up, I can see that the item I have previously selected is marked in green, and if I select a new item, I would like it to now be marked as green instead.
Obviously there's a big gap in here, since nowhere in my explanation above I mention updating my bindable variable so in theory it wold propagate to my spark list (being it a bindable variable I would of thought it would simultaneously update the item on my list(?)).
Well, I have tried doing it in a few different ways, and the debugger does "say" my array has been updated, however, my list isn't being updated at all, and will only bring another item marked in green if I close the screen and open again (when it all gets reloaded)
The whole logic described above to create a new background is contained within a function, so whenever I select an item from my list of backgrounds I was triggering my "loadBackgrounds" method again, which would apply all the logic to know which is the selected background, and because the variable is binded with my spark list I'd have hoped would update the list. Thing is, it doesn't.
What am I doing wrong here? Am I going totally bonkers and there's a much easier way of doing this but only I can't see it?
Any help here would be appreciated.
Thanks in advance
After you set the data ion the collection you need to refresh it.
arrBG.refresh();
[EDIT]
Ok I re-read your question.
I think I misunderstood what you were asking.
You want to know how to update the list so the item renderer will re-render the new list after you made changes to the data provider?
function newSelection( val:String ):void{
for each( var item:Object in arrBG ){
if( item.label == val ){
item.selected = true;
}else{
item.selected = false;
}
}
arrBG.refresh();
}
// use the commit properties on your renderer not the init
// commit properties will trigger whenever there is a dataprovider update/change
<s:ItemRenderer name="HorizontalListSkin"
xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
autoDrawBackground="false"
>
<fx:Script>
<![CDATA[
override protected function commitProperties():void{
super.commitProperties();
iconImage.source = data.icon;
iconText.text = data.label;
// Check to see if the item we're displying is selected. If it is make it stand out
if(data.selected){
iconText.setStyle("color", "Green")
}
}
]]>
</fx:Script>
<s:VGroup x="10" y="10" width="50" height="50" buttonMode="true" horizontalAlign="center">
<s:Image id="iconImage" horizontalCenter="0"/>
<s:Label id="iconText" fontFamily="Verdana" fontSize="11" fontWeight="bold" horizontalCenter="0" showTruncationTip="false"/>
</s:VGroup>
</s:ItemRenderer>