Yesterday I wasted an hour or so debugging a Flex itemRenderer that I was using to display an image instead of a value in a DataGrid column. The renderer had to simply pick one of six images based on the column value, and so it contained a single tag and a function that set the Image source dynamically. And then I called that function on the renderer’s creationComplete event.
Simple enough. Except that the wrong images were sometimes being displayed, the column had the right data, but the code used to select the image seemed to sometimes pick the wrong image. And what it picked seem to change each time I scrolled the DataGrid up and down!
I actually ran into a very similar issue with a TileList renderer a few weeks ago, but then I had no time to figure out the cause, and so I hacked a workaround. But this time, having been bitten by the same issue twice, I had to find out what was going on.
And what I discovered (by using traces and alerts) is that the creationComplete event does not get fired as I had expected. Rather, it seemed to fire only occasionally, and not once per DataGrid row, and so my image selection function was not being executed as expected.
Once I had figured out the problem I searched the docs for any info on renderers and creationComplete, and found this page. And sure enough, “Flex might reuse an instance of the item renderer or item editor, a reused instance of an item renderer or item editor does not redispatch the creationComplete event”. Well, that explained it.
The right way to do what I wanted is to trap the dataChange event instead of creationComplete, as “Flex dispatches the dataChange event every time the data property changes”.
And so I am posting this for my own future reference, just to make sure I don’t run into it a third time.

26 thoughts

  1. You nailed a major problem I had with a project recently! Since we were only displaying 4 items at a time, controlling scrolling with custom arrow components, I ended up making a new model with the next 4 items every time the user wanted to move back or forth. Like you, I didn’t have the time to figure out what was wrong. Thanks for posting this!

  2. Hey Ben,
    I went through the same thing when I first picked up Flex 2…once I thought about it, the idea of recycling item renderers made total sense from a performance standpoint.
    Instead of catching dataChange and going through event mechanics, I’ve often found it easier to deal with this issue by updating the setter for the data property that’s on most visual components (I think it’s the implementation of IListDataRenderer?):
    override public function set data(value:Object):void
    {
    super.data = value;
    // do my stuff
    switch (value)
    {
    case "foo":
    myImage.source = "foo.png";
    break;
    }
    }
    I’ll often go further, adding a bindable private property that is of the type of data being set in, allowing components in the renderer to bind cleanly to it.

  3. Joe, yep, same here, and that’s exactly what I am doing now. And you actually are indeed catching the dataChange event by overriding set data. One comment though, just to be safe, when overriding set data you should dispatch the DATA_CHANGE event when done, so at the end of your function add:
    dispatchEvent(new FlexEvent(FlexEvent.DATA_CHANGE));
    — Ben

  4. In such cases, I believe "binding" would do the job…
    Every itemRenderer (inline or custom component) has a "data" property linked to the its parent’s dataProvider. So, you can "bind" it to dynamically transfer the corrent row info to the itemRenderer on Flex’s display framework.
    Samples (with sources):
    – inline:
    http://www.vpmjr.com.br/downloads/benforta/itemRenderers001/inline/
    – external component:
    http://www.vpmjr.com.br/downloads/benforta/itemRenderers001/withComponent/

  5. This killed me when I first got into Flex. I spend two weeks trying to figure out the exact same problem. Luckily for me, Alex Harui was very helpful in explaining the idea of itemRenderer recycling to me. I’m glad to see I wasn’t the only one that ran into this problem.

  6. I am trying to use an external itemrenderer to edit a row of data in a datagrid. The itemrenderer displays a button and when the button is pressed a popup appears with the data. The user updates the data and presses "Save" button in the popup, which triggers the data to be saved in the database and closes the popup.
    It is at this point I am stuck. The datagrid needs to know the popup updated the data. I am not sure how to communicate this. Because of the layers of components I am having trouble communicating back to the datagrid. I tried adding dataChange to the datagrid, but it did not recognize the updated data.
    I can email you the test code as it uses AdventureWorks database if you’d like to see it.
    Thanks,
    Mary

  7. @Ben and Joao – If you’re extending a container like HBox or VBox, Joao is correct – you don’t need to dispatch FlexEvent.DATA_CHANGE. The implementation of set data() in Container itself does this for you. If you’re extending UIComponent (or even Displ

  8. Mary,
    When the item renderer is also a value editor, you’ll need to set "rendererIsEditor=’true’" and set what field on the component modifies the "dataField" property using column’s "editorDataField" property.
    So, in this case, you can make use of custom events too to make the opened window dispatch an event that holds in its properties the new value, so you can listen to it inside your component and set the new value to the property at the component you set up to be the "editorDataField".
    Without looking to what you’re doing, it’s that I can tell you.

  9. Joe, interesting… thank you…
    I didn’t practice it yet (I’ll do some labs…) but I believe using getter/setter in the component to interface the access to the "data" property, would workaround it. So I just change the bind inside the component to it.

  10. @Vicente – there’s no need to use the dataChange event. Here’s what I do, and what I’ve seen others do:
    <mx:Script><![CDATA[
    import model.Contact;
    [Bindable]
    private var contact:Contact;
    override public function set dat

  11. Generally we would take care of this by overriding commitProperties().
    When the data prop or listData prop is changed on an itemRenderer it should invalidate its properties by invoking invalidateProperties(), which in turn will invoke commitProperties where you would commit the properties that had changed (i.e. assign the data to you Image).
    Firstly we’d check to see if the data or listData were different (i.e. the value hadn’t just been set again) before invalidating the properties.
    ItemRenderers are re-used in Flex to keep the number of instances created down (i.e. you create enough to display on the screen and re-use them).

  12. Ben, why couldn’t you have posted this Monday! I spent about 4 hours Tuesday on the same problem.
    Oh well, not anymore!
    Kevin

  13. One thing to keep in mind; dataChange is not only fired when data is set, but also when data is cleared. Therefore you might want to wrap you logic (even with the setter/getter approach) in:
    if( data != null ){
    }
    and if you reuse renderers or the data is complex and/or variable (e.g. sparsely populated XML)
    if( (data != null) && (data.someObject != null) && … (data.someObject.someProperty.@label != null) ){
    }

  14. Guys I have this issue whenever I try to sort my grid which has 5 columns, and one of them is a component itemrenderer, Ive been looking for hours, Whenever I click on the column header to sort a new BLANK line shows with no data in it, just the progress bar, and the following lines got the same data as the 1st row.
    here’s my code
    <?xml version="1.0" encoding="utf-8"?>
    <mx:HBox xmlns:mx="http://www.adobe.com/2006/mxml&quot; minWidth="225" width="225" >
    <mx:Script>
    <![CDATA[
    import mx.events.FlexEvent;
    import mx.rpc.xml.DataType;
    import mx.controls.Alert;
    import com.*.*.lsuNamespace;
    import com.*.*.util.datetime.LSUDateFormatter;
    import com.*.*.common.commonFunctions;
    use namespace lsuNamespace;
    [Bindable]
    private var progressBarColor:uint=0x75ff75; //Default progress bar color (green color) (hexadecimal)
    protected var _mydata:XML;
    override public function set data(value:Object):void
    {
    super.data=value;
    if(value is XML)
    {
    if (value!=null)
    {
    //_mydata=value as XML
    pb.setProgress(Number(value.StepsCompleted),Number(value.StepCount));
    }
    //super.data = value;
    /*if(value)
    {
    pb.setProgress(Number(value.StepsCompleted),Number(value.StepCount));
    }*/
    }else
    {
    _mydata=null
    }
    //dispatchEvent(new FlexEvent(FlexEvent.DATA_CHANGE));
    super.invalidateDisplayList();
    }
    private function progressBarStatus(value:Object):String
    {
    if(value is Object)
    {
    var statusStr:String = value.Status.toString();
    switch(statusStr)
    {
    case ‘1’:
    case ‘2’:
    case ‘3’:
    case ‘4’:
    progressBarColor=0xffff00; //Yellow color (Status: Paused)
    return "4 %3%%";
    break;
    case ‘5’:
    //Sets the progress bar to complete
    value.StepsCompleted=100;
    value.StepCount=100;
    data=value;
    progressBarColor=0xff5555; //Red color (Status: Failed)
    return "5";
    break;
    default:
    //if(Number(value.StepCount)==Number(value.StepsCompleted))
    // progressBarColor=0x94d8fc; //Blue color (Status: Completed – 100%)
    //else
    progressBarColor=0x75ff75; //Default color (Green color) (Status: In progress)
    return "%3%%";
    break;
    }
    }
    else
    return "";
    }
    ]]>
    </mx:Script>
    <mx:ProgressBar id="pb" label="{progressBarStatus(data)}" barColor="{progressBarColor}" labelPlacement="center" width="100" left="5" top="0" mode="manual"/>
    <!–mx:Label text="{displayLabel(data.InfrastructureService.LastModifiedEpoch)}" left="113" top="0"/–>
    </mx:HBox>

  15. I’m having similar issues with ItemRenderers now and I find that it is always safer to override set data. You can analyse the data coming in and update your ItemRenderer accordingly.

Leave a Reply