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:

  • Limit the number of columns using itemRenderers. Do you really need to have every column be a custom itemRenderer? Sometimes you do, but is all that glitz overwhelming the user?
  • Try not to change the style of the elements in your itemRenderer too frequenty. If you need to switch styles (eg, green for positive values, red for negative values), consider having 2 controls preset with those styles and making one visible. Changing styles is one of the more time-consuming tasks in Flex.
  • Do not use Containers as the basis for your itemRenderers. Containers have a lot of overhead. They are fine for limited use, but it would be more efficient to write your itemRenderers based on UIComponent.

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:

  • Avoid data binding to styles. Not only is changing styles slower than most operations, adding data binding code on top of it just makes it worse.
  • Use a Canvas or extend ListItemRenderer or as the root of the itemRenderer. This allows you to place controls on top of each other.

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.

  • UIComponent imposes no layout restrictions on its children (unlike a Container). You have to position and size the children yourself.
  • It is also possible to draw graphics and position children beyond the size specified in updateDisplayList().
  • If you plan on using variableRowHeight in your list, you should also override the measure() function to give the list an idea of how big the itemRenderer is.
  • To use UIComponent as an itemRenderer you must implement IDataRenderer.
  • To use the listData property you must implement IDropInListItemRenderer; that was covered in a previous article of this series.

20 Responses to itemRenderers: Part 5: Efficiency

  1. 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.

  2. These are so awesome! Keep ’em coming!

  3. Brent says:

    Fantastic post, thanks a ton!

  4. Denis says:

    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!

  5. Jed Wood says:

    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?

  6. Peter Ent says:

    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.

  7. Luke says:

    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…

  8. Jason Roberts says:

    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?

  9. sesli chat says:

    Excellent posts guys keep up the great work and Thanks!

  10. iongion says:

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

  11. Peter Ent says:

    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.

  12. Tyler says:

    Hi Peter, this was a great article, very similar to a blog post on Alex’s Flex Closet, http://blogs.adobe.com/aharui/item_renderers/ (Thinking About Item Renderers). I’ve been doing flex for a while and I feel there can’t be enough of these types of articles. Do you think that you can post something that gets into the nitty gritty when overriding the measure function? Thanks again for the post.

  13. bob says:

    Light gray text on a white background is hard to read. There’s probably good stuff here, but I can’t see it. 🙁

  14. Peter Ent says:

    There must be something going on with your browser. Are all of the articles appearing that way or just this one? I viewed the site with several browsers and they all look the same to me.

  15. sesli chat says:

    any clue why in flex set data on the first item

  16. Peter Ent says:

    I do not understand your question.

  17. Bill says:

    Fantastic post Peter!

  18. Vijay Anand Mareddy says:

    Hi Peter,
    I have extended an AdvancedDataGridHeaderRenderer.The set data method is getting called 4 times even though i limit to a single column in my ADG.
    Any pointers on how to get rid of this problem?
    When does the headererRenderer.setdata method get invoked?
    -Vijay

  19. Peter Ent says:

    This is a known issue and very frustrating, I agree. I hope they fix this in Flex 4. Please use bugs.adobe.com and search the bugbase. If you find a bug, add your own comment to it; the more people who complain about an issue the more likely it will get fixed. If you don’t find a bug, please enter one. The engineering team will evaluate it and if they have a solution, will post it. If they determine it isn’t exactly a bug, they should state why and change the entry to something more appropriate.

  20. Russ says:

    Any word on the next article regarding text sizing? I have been struggling with itemRenderers that have different sized text fields, and problems displaying when scrolling. I just can’t get it to work.

    Also I tried using the example above in a List, and I get a null object error in the commitProperties function on the _data object