Many Flex apps have DataGrids. Many of those DataGrids want to display something other than plain text which requires the use of custom item renderers. I’ve seen lots of questions and complaining about how hard it is to make simple custom item renderers. I didn’t design it to be that hard, so I decided to take some time and make some examples of how I’d do it to serve as a starting point for some of the things you want to do in DataGrids.
I found the difficulty level to be about what I expected, but along the way I reminded myself of somethings you might find useful when making your own item renderers. What follows are those things you should probably know, and then some examples.
Custom Item Renderers does take a mental shift from other kinds of UI development in Flex. This is because item renderers need to be completely data-driven, and because they are not mapped to the dataprovider in a one-to-one fashion. A single instance of an item renderer can end up displaying the data of several different items in your data provider. It might display the first item, then the tenth, then the second as you scroll up and down in the DataGrid. We do this for efficiency reasons: if we made a renderer for every item in the data provider it would consume tons of memory. Therefore we only create item renderers that are visible, plus a few others for measurement and buffering. This has the following implications:
You cannot assume the renderer has been freshly created. When it is time to update the renderer, the components might be holding old values from the item it was rendering before.
You cannot assume that there are only as many renderers created as you see on-screen.
You cannot assume that the first renderer created is for the first visible row.
The most common error I’ve seen is in not resetting values. For example, if you have code that makes the text red if the value is below zero, you might write:
if (data.price < 0) setStyle("color", 0xFF0000);
This is incorrect. It will work the first time the renderer is created since the default color is black and will work if values are greater than 0, and if the value is less than zero. But once the renderer displays a value less than zero and turns the text red, if it is later asked to renderer a different value that is greater than zero, there is no code to turn the color back to black again. What will happen is that the data will look fine until you start scrolling or receiving updates, then the red will start to appear in funny places. The correct way is to write code like this:
if (data.price < 0) setStyle("color", 0xFF0000); else setStyle("color", 0x000000);
That code will reset the color to black if the price is greater than 0.
When designing custom item renderers, you should keep performance in mind. While it is simple and fast to take a Canvas, HBox or VBox and put a few components in there and have a renderer, keep in mind that containers are really big and slow and if you are going to have lots of them visible in your DataGrid, you might run into performance problems. We often don’t see them because we develop on really fast machines, but you have to consider what kind of hardware your end-user will have. So, I’d discourage the use of any container from mx.containers in a custom item renderer if there’s going to be more than a half-dozen on screen. Unless you are laying out more than three or four widgets in your renderer, it will be much faster to start with UIComponent and just write some logic to layout the components in their correct positions. Containers from mx.containers also factor in deferred instantiation logic, scrollbars, clipping and a bunch of other things that your probably don’t need to use in your renderer.
That’s why you’ll see that all of the examples use ActionScript instead of MXML. Yes, that means that you can’t use FlexBuilder to code the renderer, and yes, some of these examples could be done in MXML, but it is easy in MXML to do things that generate extra code that you may not need. You’ll notice that I do not use databinding in these examples for the same reason. It is actually a bit wasteful (although very convenient) to use binding on something that isn’t going to change or only change once or twice. Instead, I set the dataprovider on the initialize event (and would do so on a result event if I used HTTPService).
One frequent complaint is that, if your renderer dispatches events, it is hard to find a way to listen to those events. This is true to a degree. We sort of figured that 80% of renderers would be passive. I’m now thinking that number is closer to 50%. However, it isn’t really that hard to get events, or even to be able to hook up to those events in MXML. First, you need Flex 2.0.1 or later. While some have suggested that you use event bubbling to get events out of the DataGrid, I think that’s potentially unscalable. If you don’t guarantee that the event has a unique name, there could be some parent container of DataGrid that uses the same event name and you’ll get runtime errors. Instead, simply dispatch an event from the owner as in:
var event:Event = new ListEvent("myCustomEventName"); event.itemRenderer = this; owner.dispatchEvent(event);
This event is then dispatched from the DataGrid, and has a reference to the renderer (and therefore the data) that was affected. To write a handler in MXML, you simply have to subclass DataGrid and add event metadata as in:
/** * Broadcast when the user does something in the item renderer */ [Event("myCustomEventName")] /** * Subclass of DataGrid that dispatches a custom event. */ public class MyDataGrid extends DataGrid ... ...
However, I want to introduce another way of handling events, and that is to use custom DataGridColumns. The DataGridColumn can act as a proxy for item renderers allowing maximum reuse by allowing the specification of parameters on the column that the renderers respond to, and by being a place to listen to for events from the columns. You’ll see many of these examples use subclasses of DataGridColumn that are paired with the custom renderer. One example, the ComboBoxHeader example, uses custom events from the DataGridColumn instead of through the DataGrid.
DataProviders and Collections
It is also possible to write custom DataProviders/Collections. You don’t have to work just with Arrays or XMLLists. The example NameValueTable takes a data object and displays it in a DataGrid by using a custom collection to enumerate the properties based on a ordered list of properties for the data object.
These examples come with the usual caveats. They are unsupported, might have bugs, might not work in your app. Also note that future releases of Flex will likely have more powerful DataGrid features built-in. However, if you can’t wait, maybe these examples will help.
BackgroundColor in Item Renderers
Background Color Demo
Background Color Source
While you can use a Canvas and the backgroundColor style to do this, it turns out that the default DataGridItemRenderer is based on TextField and TextField can have solid color backgrounds. This example has a custom DataGridColumn that allows customization of the background color and value as to when display the colored background.
BackgroundColor Changes When Data Changes
Blink When Data Changes Demo
Blink When Data Changes Source
This example is a variation of the BackgroundColor example where the background color is only shown briefly if the data in the renderer changes.
Text Color and Styles in Item Renderers
Text Styles Demo
Text Styles Source
This example changes the color of the text and makes the text bold based on criteria in a styleFunction that works like labelFunction. By specifying different styleFunctions you can get the color and other font styles to be different based on the data.
HTML in an Item Renderer
HTML Renderer Demo
HTML Renderer Source
I’ve seen many people use the Text component as a renderer for rendering HTML (the subset of HTML that Flash supports, that is). It turns out that the default DataGridItemRenderer can handle HTML with a few minor tweaks and is lighterweight than Text.
Sub-Object Item Renderers
The default DataGridColumn only handles properties on the dataProvider items. It doesn’t know how to look “deeper” for properties in the items’ sub-objects. That’s for performance reasons. Normally, we tell you to use labelFunction, which is a fine solution, but this example shows how a custom DataGridColumn and custom item renderer can also take the extra time to look at sub-objects.
Split Columns/Grouped Columns
Split Column Demo
Split Column Source
Several folks want to see a column split in two with a header over the two columns. Again, this can be done via custom DataGridColumns and custom item renderers. This example allows you to specify two custom sub-DataGridColumns that define the renderers for the sub-columns.
Custom Header Renderers (CheckBox)
CheckBox Header Renderer Demo
CheckBox Header Renderer Source
Making custom header renderers is a bit trickier, especially if they are interactive. You might need to disable certain default behavior in the DataGrid. This example shows how to get a CheckBox as the header for the column.
Custom Header Renderers (ComboBox)
ComboBox Header Demo
ComboBox Header Renderer Source
Another classic custom header is a ComboBox that allows you to pick filters for the DataGrid data. This example uses a custom ComboBox that allows specification of separators in the list of items, to mimic the look some people use in Excel. This example dispatches a custom event via the custom DataGridColumn.
List of CheckBoxes
CheckBox List Demo
CheckBox List Source
This doesn’t use DataGrid, but I put it here to show one way to do this. It uses an inline item renderer to modify the data provider directly. It is tempting to use rendererIsEditor for this functionality, but it feels better if you handle the change event directly instead of relying on the editable renderer workflow which doesn’t update the data provider until after you click out of the renderer.
Name Value Table/Custom Collection
Custom Collection Demo
Custom Collection Source
This also doesn’t use DataGrid, but is a good example of how to build a custom collection. In theory, a custom collection could insert groupings into the array of email much like Outlook does. Maybe I’ll prove that in an example some day, but future releases of Flex should have this functionality built-in, and there are some good versions from third-parties already. I put up this example to get you to consider using custom collections to solve some of your problems
Someday there will be other examples available as well (maybe).. Hope these help.