« itemRenderers: Part 4: States and Transitions | Main | Adobe Media Player »

April 02, 2008

itemRenderers: Part 5: Efficiency

,,,

If you are displaying a large number of itemRenderers - either in the DataGrid or AdvancedDataGrid - your application's performance may be adversely affected if you do not code these itemRenderers effeciently. Here are some tips that might help:

Switching Styles

Here's an itemRenderer which switches components depending on the value of the data field.

<mx:Canvas>
    <mx:Script><![CDATA
        private function lessThanZero() : Boolean {
           return data.price < 0;
        }
    ]]></mx:Script>
    <mx:Label text="{data.price}" color="#FF0000" visible="{lessThanZero()}" />
    <mx:Label text="{data.price}" color="#00FF00" visible="{!lessThanZero()}" />
</mx:Canvas>

This will be faster than setting the style. Some other things to keep in mind:

Extending UIComponent

By far the most efficient way to write an itemRenderer is to extend UIComponent using an ActionScript class. You'll have complete control of the code and the renderer will be as efficient as possible.

Let's start with the example above, switching styles, and write a simple itemRenderer extending UIComponent.

package renderers
{
	import mx.controls.listClasses.IListItemRenderer;
	import mx.core.UIComponent;

	public class PriceItemRenderer extends UIComponent implements IListItemRenderer
	{
		public function PriceItemRenderer()
		{
			super();
		}
		
	}
}

You'll notice that not only did I write the class to extend UIComponent, I also have it implementing the IListItemRenderer interface. It is necessary to do this because a list control will expect any renderer to implement this interface and if you do not, you'll get a runtime error as the list attempts to cast the renderer to this interface.

If you read the documentation on IListItemRenderer you'll see that is an amalgamation of many other interfaces, most of which UIComponent implements for you. But there is one interface extended by IListItemRenderer that UIComponent does not implement: IDataRenderer. This requires you to add the code to give the itemRenderer class the data property you've been using all along.

If you attempt to use this class without implementing IDataRenderer you'll get these errors when you compile the code:

1044: Interface method get data in namespace mx.core:IDataRenderer not implemented by class renderers:PriceItemRenderer.
1044: Interface method set data in namespace mx.core:IDataRenderer not implemented by class renderers:PriceItemRenderer.

Edit this class and change it to the following:

package renderers
{
	import mx.controls.listClasses.IListItemRenderer;
	import mx.core.UIComponent;
	import mx.events.FlexEvent;


	public class PriceItemRenderer extends UIComponent implements IListItemRenderer
	{
		public function PriceItemRenderer()
		{
			super();
		}
		
		// Internal variable for the property value.
	    private var _data:Object;
	    
	    // Make the data property bindable.
	    [Bindable("dataChange")]
	    
	    // Define the getter method.
	    public function get data():Object {
	        return _data;
	    }
	    
	    // Define the setter method, and dispatch an event when the property
	    // changes to support data binding.
	    public function set data(value:Object):void {
	        _data = value;
	    
	        dispatchEvent(new FlexEvent(FlexEvent.DATA_CHANGE));
	    }
		
	}
}

I took the code directly from the Flex documentation for IDataRenderer, so you don't even have to type it yourself.

With that out of the way we can add in the two labels.

  1. Add variables to hold the two labels.
    		private var posLabel:Label;
    		private var negLabel:Label;
  2. Modify the set data function to call invalidateProperties(). This is important because the change of the data has to make the text in the labels change AND to change their visibility.
    	    public function set data(value:Object):void {
    	        _data = value;
    	    
    	    	invalidateProperties();
    	        dispatchEvent(new FlexEvent(FlexEvent.DATA_CHANGE));
    	    }
    Calling invalidateProperties() tells the Flex framework to call the commitProperties() function at the apppriate time.
  3. Override createChildren() and create the labels, adding them to the display list of the component. Notice that in addition to creating the labels, their styles and visible are also set.
    		override protected function createChildren() : void
    		{
    			super.createChildren();
    			
    			posLabel = new Label();
    			posLabel.visible = false;
    			posLabel.setStyle("color", 0x00FF00);
    			addChild(posLabel);
    			
    			negLabel = new Label();
    			negLabel.visible = false;
    			negLabel.setStyle("color", 0xFF0000);
    			addChild(negLabel);
    		}
  4. Override commitProperties() to set the labels' text and visibility. In the past you've been overriding set data to make this type of change, and you can do that in this class, too, if you prefer.
    		override protected function commitProperties():void
    		{
    			super.commitProperties();
    	        posLabel.text = data.price;
    	        negLabel.text = data.price;
    	        
    	        posLabel.visible = Number(data.price) > 0;
    	        negLabel.visible = Number(data.price) < 0;
    		}
  5. Override updateDisplayList() to size and position the labels. You must size the labels because their default size is 0x0. This is another thing a Container class will do for you. Since this is a pretty simple itemRenderer you can just set the labels' size to match the size of the itemRenderer.
    		override protected function updateDisplayList( unscaledWidth:Number, unscaledHeight:Number ) : void
    		{
    			super.updateDisplayList(unscaledWidth, unscaledHeight);
    			
    			posLabel.move(0,0);
    			posLabel.setActualSize(unscaledWidth,unscaledHeight);
    			
    			negLabel.move(0,0);
    			negLabel.setActualSize(unscaledWidth, unscaledHeight);
    		}

All this probably seems a bit complicated just to do this, but keep in mind that using a container will add a lot more code than this.

UIComponent Notes

The UIComponent class is the basis for all visual Flex components - controls and containers. Here are some tips about using UIComponent as your itemRenderer.


Posted by pent at April 2, 2008 11:50 AM

Comments

Hi Peter

Thanks for the great articles on Item Renderers.

I am pretty new to item renderers. One of your previous articles helped me fix a problem I was having with my data disappearing.
Thanks.
I do have a question about overriding the measure function when using variableRowHeight.
I created an itemRenderer for a List Component. The list is going to show a word glossary, a word and its definition. Sense the definition can either be long or short. How would you determine the height of the text component holding this string?

Could you please provide some tips/advice?
---------------
Peter: I'll be coming out with an article on text sizing shortly.

Posted by: Jonathan Marecki at April 2, 2008 01:23 PM

These are so awesome! Keep 'em coming!

Posted by: Jim Rutherford at April 2, 2008 04:01 PM

Fantastic post, thanks a ton!

Posted by: Brent at April 30, 2008 01:17 PM

Fantastic set of articles. Now I have to go back and rewrite my code with all this knowledge. Thanks for taking time to do this Peter!

Posted by: Denis at May 7, 2008 04:30 PM

I'm assuming that using graphics.clear() followed by graphics.beginFill,drawRect,endFill would be faster than creating 5 separate shapes of different colors and flipping their visibility?

Posted by: Jed Wood at May 10, 2008 06:55 PM

I would say that using graphics directly would be (marginally) faster than using shapes. Shapes are more convenient to use and the program logic might be easier to understand than having a lot of IF statements and drawing calculations going on. But that's up to you.

Posted by: Peter Ent at May 12, 2008 08:55 AM

Love the article... I got my first renderer up and working great, but my second one has been a huge hassle for me... No matter what I do I just keep getting items bouncing around... it's mainly text based, but text from boxes at the bottom are moving into the item renderers above and vice versa...

Posted by: Luke at May 14, 2008 03:49 PM

Peter, thanks for these articles! Any chance that you'll dive into item editors at some point?

I'm working on a fraction format datagrid item editor that needs to first reformat the data value (decimal to fraction), then validate the new entry (valid fraction string), then reformat the data back into the data object (fraction to decimal). I have the formatters and validators working together within a form, so I just need to apply it to the item editor.

So far I've implemented the formatters using the itemEditBegin and itemEditEnd events from the datagrid. I was planning on implementing the validator and setting the editors errorString within the itemEditEnd handler. I just don't like this approach since I have 3 columns that need the same editor (width, height, depth columns) and handling the formatting and validation through itemEditEnd/Begin has lots of duplicate code that I'd rather put within a self-contained item editor class. Any suggestions?

Posted by: Jason Roberts at May 14, 2008 09:24 PM

Excellent posts guys keep up the great work and Thanks!

Posted by: sesli chat at May 19, 2008 01:54 AM

any clue why in flex set data on the first item calls itself twice ?

Posted by: iongion at May 29, 2008 11:31 AM

The first item is being set twice because it is used to measure the itemRenderer during the Flex framework's measure phase. After that it is set for the layout phase.

Posted by: Peter Ent at May 29, 2008 03:19 PM

Post a comment




Remember Me?