Flex Mobile - TileLayout performance issues - apache-flex

I'm building mobile application for tablet devices.
I have huge performance issue with List component with tile layout.
I'm showing pages with around 20-24 items on the screen and the user can scroll trough the pages. Unfortunately the list component generates the new page extremely slow.
Overall I tested standalone List with tile layout and it's performance in every scenario is extremely slow (I have tested it on iPad1 & iPad2 ).
Please advice if you know some solution to the problem.
Thanks.

Generally the Tile is very slow in flex. This is because it allows variable size for each item renderer and the measure / updateDisplayList takes a lot of time.
I would suggest you use a custom (own made) tile list and drop the flex component in this case. If you use it just for layout you can manually/programaticaly compute the positions of each item in the tile and move it to right x/y pixel.
Also be careful with effects! They may work ok in an AIR simulator or in the desktop browser but eat a lot of resources on mobile devices.
If you still want to go for the flex tile component, and not create your own tile control, then I suggest you use fixed with renderers for all items you display inside, and also specify columnWidth and rowHeight inside the TileList declatation. This may improve a little the performance... :)
Also positing some sample code does not hurt. Maybe you are doing somethig wrong somehwre... donno: extra invalidation, bad rendering etc...!

Its been a while so I don't remember specifics. Here is the code that I used but please keep in mind, that the components I wrote are are for specific purpose and include some bad practices to accomplish a specific problem for very short time. However I hope to be useful.
pagedList
package components.pagedList
{
import components.pagedList.vo.PageItemVo;
import mx.collections.ArrayCollection;
import mx.collections.IList;
import mx.core.IFactory;
import mx.core.UIComponentGlobals;
import mx.events.CollectionEvent;
import mx.events.CollectionEventKind;
import spark.components.List;
import spark.layouts.HorizontalLayout;
/**
*
*/
public class PagedList extends List
{
//--------------------------------------------------------------------------
//
// Constructor
//
//--------------------------------------------------------------------------
public function PagedList()
{
super();
}
//--------------------------------------------------------------------------
//
// Variables
//
//--------------------------------------------------------------------------
/**
* Dimentions for the inner items should be specifyed because
* this component needs to know how many items can be placed in
* one page, but don't have the ability to create this items.
* Default value is 1 to avoid division by zero.
*/
public var innerItemWidth:Number = 1;
public var innerItemHeight:Number = 1;
/**
* When inner item dimentions are set the component calculates how many items
* (per row and col) can be shown on one scren and pass these data to the page
* trough the dataProvider for the page.
*/
private var pageRowCount:int = 1;
private var pageColCount:int = 1;
/**
* Count of the items that can fit in one page.
* This count is used to slice the data into picies
* with the same length.
*/
private var itemsPerPage:int = 0;
/**
* Original data provider is saved here so we can re-slice it when
* size change occure and listen for data change event.
*
* TODO: implement the data chage listener
*/
private var _originalDataProvider:IList;
/**
*
*/
private var cachedUnscaledWidth:Number = 0;
private var cachedUnscaledHeight:Number = 0;
//----------------------------------
// Custom tiles data
//----------------------------------
private var _customTilesData:Array;
private var customTilesDataChanged:Boolean;
public function set customTilesData(value:Array):void
{
_customTilesData = value;
customTilesDataChanged = true;
invalidateProperties();
}
public function get customTilesData():Array
{
return _customTilesData;
}
//----------------------------------
// dataProvider
//----------------------------------
private var originalDataProviderChanged:Boolean;
[Inspectable(category="Data")]
/**
*
*/
override public function set dataProvider(value:IList):void
{
if (_originalDataProvider)
_originalDataProvider.removeEventListener(CollectionEvent.COLLECTION_CHANGE, originalDataProvider_collectionChangeHandler);
originalDataProviderChanged = true;
if (value)
value.addEventListener(CollectionEvent.COLLECTION_CHANGE, originalDataProvider_collectionChangeHandler, false, 0, true);
_originalDataProvider = value;
updatePagedData();
}
/**
*
*/
private function originalDataProvider_collectionChangeHandler(event:CollectionEvent):void
{
//trace('data changed:', event.kind, 'location:', event.location);
if (event.kind == CollectionEventKind.REPLACE)
{
updateReplacedItem(event.location);
}
}
/**
*
*/
private function updateReplacedItem(index:int):void
{
if (dataProvider)
{
var pageNumber:int = int(index / itemsPerPage);
var itemIndex:int = index - (pageNumber * itemsPerPage);
if (dataProvider[pageNumber])
{
var pageData:PageItemVo = PageItemVo(dataProvider[pageNumber])
pageData.data[itemIndex] = _originalDataProvider[index];
}
}
}
//----------------------------------
// innerItemRenderer
//----------------------------------
private var _innerItemRenderer:IFactory;
private var innerItemRendererChanged:Boolean;
public function set innerItemRenderer(value:IFactory):void
{
_innerItemRenderer = value;
innerItemRendererChanged = true;
invalidateProperties();
}
public function get innerItemRenderer():IFactory
{
return _innerItemRenderer;
}
//----------------------------------
// gaps
//----------------------------------
/**
*
*/
public function set verticalGap(value:Number):void
{
_verticalGap = value;
gapsChanged = true;
invalidateProperties();
}
public function get verticalGap():Number
{
return _verticalGap;
}
/**
*
*/
public function set horizontalGap(value:Number):void
{
_horizontalGap = value;
gapsChanged = true;
invalidateProperties();
}
public function get horizontalGap():Number
{
return _horizontalGap;
}
private var _verticalGap:Number;
private var _horizontalGap:Number;
private var gapsChanged:Boolean;
//--------------------------------------------------------------------------
//
// Overridden methods
//
//--------------------------------------------------------------------------
protected function updatePagedData():void
{
if (_originalDataProvider)
{
var pagedData:IList = createPagedData(_originalDataProvider);
super.dataProvider = pagedData;
invalidateProperties();
}
}
private function createPagedData(value:IList):IList
{
var nestedData:Array = [];
var dataList:Array = value.toArray();
var pageData:PageItemVo;
if (itemsPerPage)
{
var customTilesCount:int = customTilesData ? customTilesData.length : 0;
var normalItemsPerPage:int = itemsPerPage - customTilesCount;
while (dataList.length)
{
pageData = new PageItemVo();
var data:Array = dataList.splice(0, normalItemsPerPage);
for (var i:int = 0 ; i < customTilesCount ; i++)
{
data.push( customTilesData[i] );
}
pageData.data = new ArrayCollection( data );
pageData.colsCount = pageColCount;
pageData.rowsCount = pageRowCount;
pageData.itemWidth = innerItemWidth
pageData.itemHeight = innerItemHeight;
pageData.horizontalGap = horizontalGap;
pageData.verticalGap = verticalGap;
pageData.innerItemRenderer = _innerItemRenderer;
nestedData.push(pageData);
}
}
return new ArrayCollection(nestedData);
}
//----------------------------------
// Component lifecycle
//----------------------------------
override protected function commitProperties():void
{
if (gapsChanged || innerItemRendererChanged || customTilesDataChanged)
{
updatePagedData();
gapsChanged = false;
innerItemRendererChanged = false;
customTilesDataChanged = false;
}
}
override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
{
super.updateDisplayList(unscaledWidth, unscaledHeight);
caluclateItemsPerPage(unscaledWidth, unscaledHeight);
// We have to update the dataProvider so it can re-slice the pages
// in case of orientation rotation or some other resize
if (_originalDataProvider)
if ( cachedUnscaledWidth != unscaledWidth || cachedUnscaledHeight != unscaledHeight )
dataProvider = _originalDataProvider;
cachedUnscaledWidth = unscaledWidth;
cachedUnscaledHeight = unscaledHeight;
}
protected function caluclateItemsPerPage(unscaledWidth:Number, unscaledHeight:Number):void
{
var myLayout:HorizontalLayout = HorizontalLayout(layout);
var horizontalPaddings:Number = myLayout.paddingLeft + myLayout.paddingRight;
var verticalPaddings:Number = myLayout.paddingTop + myLayout.paddingRight;
pageRowCount = (unscaledHeight - verticalPaddings) / (innerItemHeight + verticalGap);
pageColCount = (unscaledWidth - horizontalPaddings) / (innerItemWidth + horizontalGap);
itemsPerPage = pageRowCount * pageColCount;
}
}
}
PageItemRenderer
package skins.pagedList
{
import components.pagedList.vo.PageItemVo;
import flash.display.DisplayObject;
import mx.collections.ArrayCollection;
import mx.core.IDataRenderer;
import mx.core.IFactory;
import mx.core.UIComponent;
import mx.events.FlexEvent;
import spark.components.IItemRenderer;
//--------------------------------------
// Events
//--------------------------------------
/**
* Dispatched when the <code>data</code> property changes.
*
* <p>When you use a component as an item renderer,
* the <code>data</code> property contains the data to display.
* You can listen for this event and update the component
* when the <code>data</code> property changes.</p>
*
* #eventType mx.events.FlexEvent.DATA_CHANGE
*/
[Event(name="dataChange", type="mx.events.FlexEvent")]
/**
*
* ASDoc comments for this item renderer class
*
*/
public class PageItemRenderer extends UIComponent
implements IDataRenderer, IItemRenderer
{
//--------------------------------------------------------------------------
//
// Constructor
//
//--------------------------------------------------------------------------
public function PageItemRenderer()
{
super();
cacheAsBitmap = true;
}
//--------------------------------------------------------------------------
//
// Variables
//
//--------------------------------------------------------------------------
//----------------------------------
// data
//----------------------------------
/**
* #private
*/
private var _data:PageItemVo;
private var dataChanged:Boolean;
[Bindable("dataChange")]
/**
* The implementation of the <code>data</code> property
* as defined by the IDataRenderer interface.
* When set, it stores the value and invalidates the component
* to trigger a relayout of the component.
*/
public function get data():Object
{
return _data;
}
/**
* #private
*/
public function set data(value:Object):void
{
_data = PageItemVo(value);
colCount = _data.colsCount;
rowCount = _data.rowsCount;
itemWidth = _data.itemWidth;
itemHeight = _data.itemHeight;
horizontalGap = _data.horizontalGap;
verticalGap = _data.verticalGap;
innerItemRenderer = _data.innerItemRenderer;
_tilesData = ArrayCollection(_data.data);
dataChanged = true;
if (hasEventListener(FlexEvent.DATA_CHANGE))
dispatchEvent(new FlexEvent(FlexEvent.DATA_CHANGE));
invalidateProperties();
}
//----------------------------------
// gaps
//----------------------------------
/**
*
*/
public function set verticalGap(value:Number):void
{
_verticalGap = value;
gapsChanged = true;
invalidateProperties();
}
public function get verticalGap():Number
{
return _verticalGap;
}
/**
*
*/
public function set horizontalGap(value:Number):void
{
_horizontalGap = value;
gapsChanged = true;
invalidateProperties();
}
public function get horizontalGap():Number
{
return _horizontalGap;
}
private var _verticalGap:Number = 5;
private var _horizontalGap:Number = 5;
private var gapsChanged:Boolean;
//----------------------------------
// itemIndex
//----------------------------------
private var _itemIndex:int;
public function get itemIndex():int
{
return _itemIndex;
}
public function set itemIndex(value:int):void
{
if (value == _itemIndex)
return;
invalidateDisplayList();
_itemIndex = value;
}
//----------------------------------
// showsCaret
//----------------------------------
private var _showsCaret:Boolean = false;
public function get showsCaret():Boolean
{
return _showsCaret;
}
/**
* #private
*/
public function set showsCaret(value:Boolean):void
{
if (value == _showsCaret)
return;
_showsCaret = value;
invalidateDisplayList();
}
//----------------------------------
// selected
//----------------------------------
private var _selected:Boolean = false;
public function get selected():Boolean
{
return _selected;
}
public function set selected(value:Boolean):void
{
if (value == _selected)
return;
_selected = value;
}
//----------------------------------
// dragging
//----------------------------------
private var _dragging:Boolean = false;
public function get dragging():Boolean
{
return _dragging;
}
public function set dragging(value:Boolean):void
{
_dragging = value;
}
//----------------------------------
// label
//----------------------------------
private var _label:String;
public function get label():String
{
return _label;
}
/**
* #private
*/
public function set label(value:String):void
{
_label = value;
}
//----------------------------------
// item properties
//----------------------------------
/**
* Dimentions for the inner items should be specifyed because
* this component needs to know how many items can be placed in
* one page, but don't have the ability to create this items.
* Default value is 1 to avoid division by zero.
*/
public var itemWidth:Number = 1;
public var itemHeight:Number = 1;
/**
* When inner item dimentions are set the component calculates how many items
* (per row and col) can be shown on one scren and pass these data to the page
* trough the dataProvider for the page.
*/
public var rowCount:int = 1;
public var colCount:int = 1;
private var _tilesData:ArrayCollection;
private var sizeChanged:Boolean;
private var _tileContainer:UIComponent;
/**
*
*/
private var innerItemRenderer:IFactory;
//--------------------------------------------------------------------------
//
// Overridden methods
//
//--------------------------------------------------------------------------
//----------------------------------
// Component lifecycle
//----------------------------------
override protected function commitProperties():void
{
super.commitProperties();
if (dataChanged)
{
dataChanged = false;
createTiledContent();
}
if (gapsChanged)
{
createTiledContent();
}
}
override protected function measure():void
{
super.measure();
measuredMinHeight = measuredWidth = parent.width;
measuredMinHeight = measuredHeight = parent.height;
}
override protected function updateDisplayList(unscaledWidth:Number,
unscaledHeight:Number):void
{
super.updateDisplayList(unscaledWidth, unscaledHeight);
var tileCntWidth:Number = colCount * (itemWidth + horizontalGap);
_tileContainer.x = 0.5 * (unscaledWidth - tileCntWidth);
}
//--------------------------------------------------------------------------
//
// Methods
//
//--------------------------------------------------------------------------
protected function createTiledContent():void
{
clearChildren();
_tileContainer = new UIComponent();
var itemsCount:int = _tilesData.length;
var row:int, col:int, item:DisplayObject;
for (var i:int = 0 ; i < itemsCount ; i++ )
{
row = int( i / colCount );
col = i - row * colCount;
if (_tilesData[i].hasOwnProperty("itemFactory"))
{
item = IFactory(_tilesData[i].itemFactory).newInstance();
}
else
{
item = innerItemRenderer.newInstance();
}
Object(item).data = _tilesData[i];
item.x = col * (itemWidth + horizontalGap);
item.y = row * (itemHeight + verticalGap);
_tileContainer.addChild(item);
}
addChild(_tileContainer);
invalidateSize();
invalidateDisplayList();
}
//--------------------------------------------------------------------------
//
// Helper methods
//
//--------------------------------------------------------------------------
private function clearChildren():void
{
var numChildren:int = this.numChildren;
for (var i:int = 0 ; i < numChildren ; i++ )
{
this.removeChildAt(0);
}
}
}
}
PagedItemVo
package components.pagedList.vo
{
import flash.events.EventDispatcher;
import flash.events.IEventDispatcher;
import mx.collections.IList;
import mx.core.IFactory;
[Bindable]
public class PageItemVo extends EventDispatcher
{
public var data:IList;
public var rowsCount:int;
public var colsCount:int;
public var itemWidth:Number;
public var itemHeight:Number;
public var verticalGap:Number;
public var horizontalGap:Number;
public var innerItemRenderer:IFactory;
public function PageItemVo(target:IEventDispatcher=null)
{
super(target);
}
}
}
ScrollingListSkin
package skins.pagedList
{
import flash.display.Graphics;
import flash.display.Sprite;
import flash.events.MouseEvent;
import mx.core.DPIClassification;
import mx.core.ScrollPolicy;
import mx.events.FlexEvent;
import mx.events.TouchInteractionEvent;
import spark.events.IndexChangeEvent;
import spark.events.RendererExistenceEvent;
import spark.skins.mobile.ListSkin;
import spark.skins.mobile.supportClasses.MobileSkin;
public class ScrollingListSkin extends ListSkin
{
private var pageIndicator:Sprite;
private var indicatorSize:uint;
private var _isHorizontal:Boolean;
private var _suspendPageIndicatorShortcut:Boolean;
public function ScrollingListSkin()
{
super();
switch (applicationDPI)
{
case DPIClassification.DPI_320:
{
indicatorSize = 32;
break;
}
case DPIClassification.DPI_240:
{
indicatorSize = 24;
break;
}
default:
{
indicatorSize = 16;
break;
}
}
}
//--------------------------------------------------------------------------
//
// Overridden methods
//
//--------------------------------------------------------------------------
override protected function createChildren():void
{
super.createChildren();
scroller.setStyle("skinClass", PagedListScrollerSkin);
// page indicator
pageIndicator = new Sprite();
// TODO (jasonsj): extend pageIndicator hit area to use the entire
// width/height of the List as a shortcut. Currently this only works
// in the tiny area where the indicators are.
//pageIndicator.addEventListener(MouseEvent.MOUSE_DOWN, pageIndicaterMouseHandler);
//pageIndicator.addEventListener(MouseEvent.MOUSE_UP, pageIndicaterMouseHandler);
//pageIndicator.addEventListener(MouseEvent.MOUSE_MOVE, pageIndicaterMouseHandler);
addChild(pageIndicator);
// listen for changes to the list
dataGroup.addEventListener(FlexEvent.UPDATE_COMPLETE, dataGroupUpdateComplete);
scroller.addEventListener(TouchInteractionEvent.TOUCH_INTERACTION_START, touchInteractionStart);
scroller.addEventListener(TouchInteractionEvent.TOUCH_INTERACTION_END, positionChanged);
}
override protected function commitProperties():void
{
super.commitProperties();
// isHorizontal
/*var hScrollPolicy:Boolean = getStyle("horizontalScrollPolicy") == ScrollPolicy.ON;
var vScrollPolicy:Boolean = getStyle("verticalScrollPolicy") == ScrollPolicy.ON;
_isHorizontal = hScrollPolicy && !vScrollPolicy;*/
_isHorizontal = true;
}
override protected function drawBackground(unscaledWidth:Number, unscaledHeight:Number):void
{
super.drawBackground(unscaledWidth, unscaledHeight);
var pos:Number = (isHorizontal) ? scroller.viewport.horizontalScrollPosition :
scroller.viewport.verticalScrollPosition;
var viewportSize:Number = (isHorizontal) ? scroller.viewport.width :
scroller.viewport.height;
var selectedIndex:int = Math.round(pos / viewportSize);
var numElements:int = dataGroup.numElements;
var g:Graphics = pageIndicator.graphics;
g.clear();
// if we have only one page there shouldn't be any scrollbar visuals
if (numElements == 1) return;
var axisPos:Number = 0;
var centerPos:Number = indicatorSize / 2;
var radius:Number = indicatorSize / 4;
//TODO: make so the color could be specifyed outisde
var selectionColor:Number = 0x000000; //getStyle("selectionColor");
//var elementsPerPage:int = Math.floor(unscaledWidth/FileIconItemRenderer.ITEM_WIDTH) * Math.floor(unscaledHeight/FileIconItemRenderer.ITEM_HEIGHT);
for (var i:uint = 0; i < numElements; i++)
{
if (i == selectedIndex)
g.beginFill(selectionColor, 1);
else
g.beginFill(0, .25);
if (isHorizontal)
g.drawCircle(axisPos + centerPos, centerPos, radius);
else
g.drawCircle(centerPos, axisPos + centerPos, radius);
g.endFill();
axisPos += indicatorSize;
}
var pageIndicatorX:Number = (isHorizontal) ? (unscaledWidth - axisPos) / 2 :
unscaledWidth - (indicatorSize * 1.5);
var pageIndicatorY:Number = (isHorizontal) ? unscaledHeight - (indicatorSize * 1.5):
(unscaledHeight - axisPos) / 2;
setElementPosition(pageIndicator, Math.floor(pageIndicatorX), Math.floor(pageIndicatorY));
}
override public function styleChanged(styleProp:String):void
{
super.styleChanged(styleProp);
var allStyles:Boolean = !styleProp || styleProp == "styleName";
if (allStyles || styleProp == "horizontalScrollPolicy" ||
styleProp == "verticalScrollPolicy")
{
invalidateProperties();
invalidateDisplayList();
}
}
private function get isHorizontal():Boolean
{
return _isHorizontal;
}
//--------------------------------------------------------------------------
//
// Event Handlers
//
//--------------------------------------------------------------------------
private function dataGroupUpdateComplete(event:FlexEvent):void
{
invalidateDisplayList();
}
private function touchInteractionStart(event:TouchInteractionEvent):void
{
_suspendPageIndicatorShortcut = true;
}
private function positionChanged(event:TouchInteractionEvent):void
{
invalidateDisplayList();
_suspendPageIndicatorShortcut = false;
}
private function pageIndicaterMouseHandler(event:MouseEvent):void
{
event.preventDefault();
if (_suspendPageIndicatorShortcut)
return;
// Mouse events on the pageIndicator sprite will jump to the selected page
var pos:Number = (isHorizontal) ? event.localX : event.localY;
var size:Number = (isHorizontal) ? pageIndicator.width : pageIndicator.height;
pos = Math.min(Math.max(pos, 0), size) - (indicatorSize / 2);
var viewportSize:Number = (isHorizontal) ? scroller.viewport.width : scroller.viewport.height;
viewportSize = viewportSize * dataGroup.numElements;
var viewportPosition:Number = (pos / size) * viewportSize;
if (isHorizontal)
scroller.viewport.horizontalScrollPosition = viewportPosition;
else
scroller.viewport.verticalScrollPosition = viewportPosition;
}
}
}
Example:
<pagedList:PagedList id="displayList"
width="100%" height="100%"
itemRenderer="skins.pagedList.PageItemRenderer"
innerItemRenderer="components.pagedList.tiles.FolderTileItem"
innerItemWidth="146" innerItemHeight="150"
skinClass="skins.pagedList.ScrollingListSkin"
verticalScrollPolicy="off"
horizontalScrollPolicy="on"
pageScrollingEnabled="true"
horizontalGap="15" verticalGap="20"
contentBackgroundAlpha="0"
selectionColor="0xFFFFFF">
<pagedList:layout>
<s:HorizontalLayout columnWidth="{displayList.width}"
variableColumnWidth="false"
gap="0"
paddingTop="30"/>
</pagedList:layout>
Unfortunatly I can't share the FolderTileItem source with you, but its simple a simple component that extends Sprite.

Related

Can I use multiple ItemRenderer with one LineSeries?

Currently I have created below class which extends ItemRenderer to add labels for all data points in Line Chart.
package lib
{
public class LineChartLabelRenderer extends UIComponent implements IDataRenderer, IFactory
{
private var _label:Label;
public var solidColor:SolidColor;
public function newInstance():*
{
return new LineChartLabelRenderer();
}
public function LineChartLabelRenderer():void
{
super();
_label = new Label();
addChild(_label);
_label.setStyle("color",0x000000);
_label.setStyle("fontSize",9);
}
private var _chartItem:ChartItem;
public function get data():Object
{
return _chartItem;
}
public function set data(value:Object):void
{
if (_chartItem == value){
return;
}
_chartItem = ChartItem(value);
if(_chartItem != null){
_label.text = LineSeriesItem(_chartItem).yValue.toString();
}
}
override protected function updateDisplayList(unscaledWidth:Number,unscaledHeight:Number):void
{
super.updateDisplayList(unscaledWidth, unscaledHeight);
var ls:LineSeries = _chartItem.element as LineSeries;
solidColor = new SolidColor(ls.getStyle("fill"));
_label.setActualSize(_label.getExplicitOrMeasuredWidth(),20);
_label.move(unscaledWidth - _label.getExplicitOrMeasuredWidth() / 2 , this.unscaledHeight - _label.getExplicitOrMeasuredHeight() - 5);
}
}
}
It works well. However since the LineSeries may already be set with other ItemRenderer such as CircleItemRenderer or BoxItemRenderer. I found no where to let me set multiple ItemRender with one LineSeries. I want to know what the best way is to do this.
Thanks to Sunil D.
Finally I create extended ItemRenderer like below
public class LSLabelCircleItemRenderer extends CircleItemRenderer
{
private var _label:Label;
public function LSLabelCircleItemRenderer():void
{
super();
_label = new Label();
}
override protected function updateDisplayList(unscaledWidth:Number,unscaledHeight:Number):void
{
super.updateDisplayList(unscaledWidth, unscaledHeight);
if(data != null){
var ls:LineSeries = ChartItem(data).element as LineSeries;
label.text = LineSeriesItem(data).yValue.toString();
skin.parent.addChild(label);
label.setStyle("color",ls.getStyle("fill"));
label.move(skin.x - label.getExplicitOrMeasuredWidth() / 5,skin.y - label.getExplicitOrMeasuredHeight());
}
}
}

Upgrading from Flex 3.0 to Flex 3.6 - Custom Combobox getting TypeError in super.commitProperties()

I am working on upgrading Flex 3.0 application to Flex 3.6 SDK. The application has custom controls for input text, combo-boxes, etc. When I click on the combo-box I get exception when super.commitProperties() is called. This works fine with Flex 3.0 SDK.
TypeError: Error #1009: Cannot access a property or method of a null object reference.
at mx.controls::ComboBox/destroyDropdown()[C:\autobuild\3.x\frameworks\projects\framework\src\mx\controls\ComboBox.as:1681]
at mx.controls::ComboBox/styleChanged()[C:\autobuild\3.x\frameworks\projects\framework\src\mx\controls\ComboBox.as:1177]
at mx.core::UIComponent/setBorderColorForErrorString()[C:\autobuild\3.x\frameworks\projects\framework\src\mx\core\UIComponent.as:5048]
at mx.core::UIComponent/commitProperties()[C:\autobuild\3.x\frameworks\projects\framework\src\mx\core\UIComponent.as:6033]
at mx.controls::ComboBase/commitProperties()[C:\autobuild\3.x\frameworks\projects\framework\src\mx\controls\ComboBase.as:993]
at mx.controls::ComboBox/commitProperties()[C:\autobuild\3.x\frameworks\projects\framework\src\mx\controls\ComboBox.as:1291]
at com.hanover.utility.controls.autotestcontrols::AutotestComboBox/commitProperties()[C:\App\Control\PLCustomComboBox.as:59]
at mx.core::UIComponent/validateProperties()[C:\autobuild\3.x\frameworks\projects\framework\src\mx\core\UIComponent.as:5966]
at mx.managers::LayoutManager/validateProperties()[C:\autobuild\3.x\frameworks\projects\framework\src\mx\managers\LayoutManager.as:539]
at mx.managers::LayoutManager/doPhasedInstantiation()[C:\autobuild\3.x\frameworks\projects\framework\src\mx\managers\LayoutManager.as:689]
at mx.managers::LayoutManager/validateNow()[C:\autobuild\3.x\frameworks\projects\framework\src\mx\managers\LayoutManager.as:748]
at mx.controls::ComboBox/displayDropdown()[C:\autobuild\3.x\frameworks\projects\framework\src\mx\controls\ComboBox.as:1638]
at mx.controls::ComboBox/downArrowButton_buttonDownHandler()[C:\autobuild\3.x\frameworks\projects\framework\src\mx\controls\ComboBox.as:1796]
at flash.events::EventDispatcher/dispatchEventFunction()
at flash.events::EventDispatcher/dispatchEvent()
at mx.core::UIComponent/dispatchEvent()[C:\autobuild\3.x\frameworks\projects\framework\src\mx\core\UIComponent.as:9590]
at mx.controls::Button/http://www.adobe.com/2006/flex/mx/internal::buttonPressed()[C:\autobuild\3.x\frameworks\projects\framework\src\mx\controls\Button.as:2504]
at mx.controls::Button/mouseDownHandler()[C:\autobuild\3.x\frameworks\projects\framework\src\mx\controls\Button.as:2750]
at flash.events::EventDispatcher/dispatchEventFunction()
at flash.events::EventDispatcher/dispatchEvent()
at mx.core::UIComponent/dispatchEvent()[C:\autobuild\3.x\frameworks\projects\framework\src\mx\core\UIComponent.as:9590]
at mx.controls::ComboBase/textInput_mouseEventHandler()[C:\autobuild\3.x\frameworks\projects\framework\src\mx\controls\ComboBase.as:1384]
PLCustomCombobox.as pseudo-source with line # 59 indicated below
package com
{
import com.name.utility.events.PLControlEvent;
import com.name.utility.managers.ComboToolTipManager;
import flash.events.Event;
import mx.controls.ComboBox;
import mx.events.FlexEvent;
[Event(name="controlInitComplete", type="com.events.PLControlEvent")]
[Event(name="plusControlValueChanged", type="com.name.utility.events.PLControlEvent")]
public class PLControlComboBox extends ComboBox
implements IPLControlControl
{
/**
* A flag that indicates whether the control need to refresh.
*/
protected var isNeedRefresh:Boolean = false;
/**
* A flag that indicates whether the value of control is changed.
*/
protected var isValueChanged:Boolean = false;
/**
* Constructor.
*/
public function PLControlComboBox()
{
super();
this.addEventListener(FlexEvent.CREATION_COMPLETE, onCreationComplete);
}
/**
* #private
* override commitProperties for avoid dispatch event many times.
*/
override protected function commitProperties():void
{
super.commitProperties(); // <---- line # 59 is here
if(isNeedRefresh)
{
this.dispatchEvent(new PLControlEvent(PLControlEvent.VALUE_CHANGED));
isNeedRefresh = false;
}
if(isValueChanged)
{
this.dispatchEvent(new Event("PLControlComboboxChanged"));
isValueChanged = false;
}
}
/**
* Add item's tooltip.
* #private
*/
override public function set measuredWidth(value:Number):void
{
super.measuredWidth = value;
if(collection && collection.length > 0)
{
ComboToolTipManager.showItemToolTip(this);
}
}
protected function onCreationComplete(event:FlexEvent):void
{
this.removeEventListener(FlexEvent.CREATION_COMPLETE, onCreationComplete);
this.dispatchEvent(new PLControlEvent(PLControlEvent.INIT_COMPLETE));
}
public function checkValidity():void
{
if(_required && dataProvider && dataProvider.length > 0 && _defaultItemIndex == selectedIndex)
{
_validity = false;
errorString = "please select one";
}
else
{
_validity = true;
errorString = "";
}
}
// ======================================
// public function
// ======================================
private function itemChanged():void
{
isNeedRefresh = true;
isValueChanged = true;
checkValidity();
}
// ======================================
// properties
// ======================================
private var _required:Boolean = false;
private var _validity:Boolean = false;
private var _defaultItemIndex:int = 0;
public function get required():Boolean
{
return _required;
}
[Inspectable(defaultValue=false, category="Other")]
public function set required(value:Boolean):void
{
_required = value;
isNeedRefresh = true;
checkValidity();
}
[Bindable("PLControlComboboxChanged")]
public function get controlValue():Object
{
return this.selectedIndex;
}
public function set controlValue(value:Object):void
{
if(value is Number)
{
this.selectedIndex = int(value);
itemChanged();
}
}
/**
* #copy IPLControlControl#validity
*/
public function get validity():Boolean
{
return _validity;
}
/**
* #copy IPLControlControl#type
*/
public function get type():String
{
return PLControlTypeList.CONTROL_TYPE_COMBOBOX;
}
/**
* If required is true, the default Item can not be selected.
*/
public function get defaultItemIndex():int
{
return _defaultItemIndex;
}
[Inspectable(defaultValue=0, type="Number", category="Other")]
public function set defaultItemIndex(index:int):void
{
_defaultItemIndex = index;
isNeedRefresh = true;
checkValidity();
}
/**
* Override for check validity.
*/
override public function set selectedIndex(value:int):void
{
super.selectedIndex = value;
itemChanged();
}
/**
* Override for check validity.
*/
override public function set selectedItem(value:Object):void
{
super.selectedItem = value;
itemChanged();
}
}
}
Its coming from mx.controls.Comboxbox line # 1681 because tween is null.
private function destroyDropdown():void
{
if (inTween)
tween.endTween();//<---- exception on this line
displayDropdown(false, null, false);
}
Any suggestions of why this could be happening?
Issue fixed in Apache Flex 4.10 and above, see https://issues.apache.org/jira/browse/FLEX-33382.
If using an older SDK you could monkey patch mx:ComboBox like so adding the "&& tween" check:
private function destroyDropdown():void
{
if (inTween && tween)
tween.endTween();
displayDropdown(false, null, false);
}

accessing child movieClips FlashDevelop

I have a movieClip within another MovieClip. I gave the child movieClip the instance name "hSprite" and I added it to the parent movieClip stage. Now I get an error like the following.
[Fault] exception, information=ReferenceError: Error #1056: Cannot create property hSprite on com.objects.Hero.
If I remove the instance name from the child movieclip, then the error goes away. but when I add the instance name back, the error reappears again.
Keep in mind that both classes are set for export.
The parent class is embeded by a custom class which work perfectly fine. But the minute I give the child movieClip a instance name, the error starts up again.
Here is the class that embeds the parent class. What I initially want to do is access the child MovieClip that is in the Hero symbol
package com.objects
{
import flash.display.MovieClip;
import flash.events.*;
/**
* ...
* #author Anthony Gordon
*/
[Embed(source='../../../bin/Assets.swf', symbol='Hero')]
public class Hero extends GameObject
{
private var aKeyPress:Array;
private var jumpDisabled:Boolean = false;
//private var heroSprite:MovieClip;
public function Hero()
{
wY = 150;
wX = 90;
speed = .5;
aKeyPress = new Array();
TheGame.sr.addEventListener(KeyboardEvent.KEY_DOWN, keyDownListener);
TheGame.sr.addEventListener(KeyboardEvent.KEY_UP,keyUpListener);
}
private function keyDownListener(e:KeyboardEvent):void {
//trace("down e.keyCode=" + e.keyCode);
aKeyPress[e.keyCode]=true;
}
private function keyUpListener(e:KeyboardEvent):void {
//trace("up e.keyCode=" + e.keyCode);
aKeyPress[e.keyCode]=false;
}
override public function UpdateObject():void
{
Controls();
updatePosition();
}
private function Controls():void
{
if (aKeyPress[38])//Key press up
;//dy -= speed;
else if (aKeyPress[40])//Key press down
dy += speed;
if (aKeyPress[37])//left
{
dx -= speed;
}
else if (aKeyPress[39])//Right
{
dx += speed;
}
if (aKeyPress[32]){//space
jump();
}
}//End Controls
private function jump():void
{
if (!jumpDisabled)
{
if (onGround)
{
dy = -15;
jumpDisabled = true;
}
}
else
{
jumpDisabled = false;
}
}
}
}
You need to make the hSprite clip a public variable in the class.
public var hSprite:MovieClip;

Deep Binding in Flex

So I have a module in flex in which I add a custom component. I also have a class that handles the data I want to show, lets call this class DataHandler.
The DataHandler receives data from the back-end solution and then starts putting the data togheter for my Module and the custom component.
When the data is ready it dispatches an event that my Module catch. I send the new data in to my component.
example code for this in Module:
private function onDataChange(evt:Event=null):void
{
_customComponent.ItemData = _dataHandler.DataProvider;
}
The _customComponent then gets the data :
public function set ItemData(value:ItemDataVO):void
{
_itemdata = value;
}
// _itemdata is a custom class named ItemDataVO
Now in my custom component I just bind the data to my mxml components , for example
<mx:Label
text = "Text: {_itemdata.Text}"
fontFamily = "Hel"
fontSize = "12"
x = "83"
y = "40" />
When I get new data the label automaticly changes.
So far so good. But what I also have in my custom component is i List.
And this is my problem. When I bind the data to the List I do the following:
<mx:List
id = "_list"
dataProvider ="{_itemdata.Collection}"
itemRenderer = "components.renderers.CustomRenderer" />
// this _itemdata.Collection is an ArrayCollection that contains a collection of items based on a custom class.
The binding does not work, and I also get a varning for each item in the list at runtime:
warning: unable to bind to property 'parent' on class 'modules::CustomModule'
( I have also tried, as a workaround, to set the _list's itemrenderer each time the ItemData is set. The new listdata then update but I dont see any visual update in the list. )
Anyone knows how to make this binding work?
Regards Adlertz =)
Have you set the collection property on ItemDataVo to be bindable... something like
package
{
import mx.collections.ArrayCollection;
public class ItemDataVo
{
[Bindable]
public var text : String;
[Bindable]
public var collection : ArrayCollection
public function ItemDataVo()
{
}
}
}
I have made a simplified working example showing binding working on a model similar to yours (although there will obviously be differences) with binding working correctly so long as both the text and collection properties both have the Bindable meta data attached:
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical" xmlns:local="*" creationComplete="creationCompleteHandler(event)">
<mx:Script>
<![CDATA[
import mx.collections.ArrayCollection;
import mx.events.FlexEvent;
[Bindable]
protected var itemData : ItemDataVo;
private function creationCompleteHandler(event : FlexEvent) : void
{
generateItemData();
}
protected function generateItemData() : void
{
itemData = new ItemDataVo();
itemData.text = "New Text With Random " + Math.random() * 100;
itemData.collection = generateCollection();
}
protected function generateCollection() : ArrayCollection
{
var arrayCollection : ArrayCollection = new ArrayCollection();
arrayCollection.addItem("New Item With Random " + Math.random() * 100);
arrayCollection.addItem("New Item With Random " + Math.random() * 100);
arrayCollection.addItem("New Item With Random " + Math.random() * 100);
arrayCollection.addItem("New Item With Random " + Math.random() * 100);
arrayCollection.addItem("New Item With Random " + Math.random() * 100);
arrayCollection.addItem("New Item With Random " + Math.random() * 100);
return arrayCollection;
}
private function fullClickHandler(event : MouseEvent) : void
{
generateItemData();
}
private function collectionClickHandler(event : MouseEvent) : void
{
itemData.collection = generateCollection();
}
]]>
</mx:Script>
<mx:VBox width="100%" height="100%">
<mx:VBox>
<mx:Label text="{itemData.text}" />
<mx:List dataProvider="{itemData.collection}" />
</mx:VBox>
<mx:HBox>
<mx:Button label="UPDATE FULL ITEM DATA" click="fullClickHandler(event)"/>
<mx:Button label="UPDATE COLLECTION" click="collectionClickHandler(event)"/>
</mx:HBox>
</mx:VBox>
</mx:Application>
In the past I added my List into my Module and it never gave me any binding problems. But yes it seems very strange that the component in between breaks the binding.
The object stored in the Collection looks like this:
package code.converters.objects
{
public class LineupLI
{
private var _plid:int;
private var _lastname:String;
private var _firstname:String;
private var _shirtnumber:int;
private var _onpitch:Boolean;
private var _distance:Number;
private var _topspeed:Number;
private var _goals:int = 0;
private var _subin:Boolean = false;
private var _subout:Boolean = false;
private var _redcard:Boolean = false ;
private var _yellowcard:Boolean = false;
private var _teamid:int;
private var _teamlongdesc:String;
private var _second_increase:Number;
[Bindable]
public function get OnPitch():Boolean { return _onpitch; }
public function set OnPitch(value:Boolean):void { _onpitch = value; }
[Bindable]
public function get ShirtNumber():int { return _shirtnumber; }
public function set ShirtNumber(value:int):void { _shirtnumber = value; }
[Bindable]
public function get PlId():int { return _plid; }
public function set PlId(value:int):void { _plid = value; }
[Bindable]
public function get FirstName():String { return _firstname; }
public function set FirstName(value:String):void { _firstname = value; }
[Bindable]
public function get LastName():String { return _lastname; }
public function set LastName(value:String):void { _lastname = value; }
[Bindable]
public function get Distance():Number { return _distance; }
public function set Distance(value:Number):void { _distance = value; }
[Bindable]
public function get TopSpeed():Number { return _topspeed; }
public function set TopSpeed(value:Number):void { _topspeed = value; }
[Bindable]
public function get Goals():int { return _goals; }
public function set Goals(value:int):void { _goals = value; }
[Bindable]
public function get SubIn():Boolean { return _subin; }
public function set SubIn(value:Boolean):void { _subin = value; }
[Bindable]
public function get SubOut():Boolean { return _subout; }
public function set SubOut(value:Boolean):void { _subout = value; }
[Bindable]
public function get RedCard():Boolean { return _redcard; }
public function set RedCard(value:Boolean):void { _redcard = value; }
[Bindable]
public function get YellowCard():Boolean { return _yellowcard; }
public function set YellowCard(value:Boolean):void { _yellowcard = value; }
[Bindable]
public function get TeamId():int { return _teamid; }
public function set TeamId(value:int):void { _teamid = value; }
[Bindable]
public function get TeamLongDesc():String { return _teamlongdesc; }
public function set TeamLongDesc(value:String):void { _teamlongdesc = value; }
[Bindable]
public function get SecondIncrease():Number { return _second_increase; }
public function set SecondIncrease(value:Number):void { _second_increase = value; }
}
}
Looks ok to me... i think there could be a couple of problems here. One is the binding not working and the second (as David Hanak stated) being why the warning
warning: unable to bind to property 'parent' on class 'modules::CustomModule'
is being thrown which doesn't seem to relate to any of your objects. I would try to solve the latter first and try and figure out why that is happening. You may find that this fixes the original binding problem.

Flex: invalidateData

I've trouble getting my components to update when the params has changed:
package mycompany
{
import flash.events.Event;
import mx.events.SliderEvent;
import mx.controls.HSlider;
import mx.controls.sliderClasses.Slider;
public class FromToSlider extends HSlider
{
/* from: */
private var _from:int;
[Bindable]
public function get from():int
{
return _from;
}
public function set from(value:int):void
{
this._from = value;
this.values[0] = value;
invalidateProperties();
}
/* //from */
/* to: */
private var _to:int;
[Bindable]
public function get to():int
{
return _to;
}
public function set to(value:int):void
{
this._to = value;
this.values[1] = value;
}
/* //to */
override public function initialize():void
{
super.initialize();
addEventListener(SliderEvent.CHANGE, handleChange, false, 0, true);
}
protected function handleChange(event:SliderEvent):void
{
var ct:Slider=Slider(event.currentTarget);
this.from = ct.values[0];
this.to = ct.values[1];
}
}
}
When I set "from" and "to" the thumbs aren't updating. I've tried invalidateProperties but that didn't work.
Add a call to invalidateDisplayList() after invalidateProperties(). That will ensure that Flex redraws the component on the next keyframe.
You should also add the same to the 'set to()' function.

Resources