List-based controls such as lists, data grids, and trees have standard ways in which they display data. For example, a list displays one column of text, data grids display one or more columns of text, and trees display a hierarchical view of data. For many if not most applications, the default ways in which these controls display data are perfectly sufficient. However, there are cases in which you need to alter the displays in one way or another. For example, you may want to display a checkbox in a data grid column rather than standard text.
When you want to customize the way in which a list-based component
displays elements, you can use what is called an item
renderer. Item renderers allow you to specify what component to use in place of the
standard text or text and icon that appear in the component, thus
customizing the appearance of the elements in the component. The following
components support custom item renderers: List
, HorizontalList
, DataGrid
,
Menu
, TileList
, and Tree
.
There are two basic ways in which you can use custom item renderers:
These are the simplest types of item renderers to implement. With a drop-in item renderer you simply specify a standard UI component to use in a particular column. For example, you can use a checkbox as a drop-in item renderer.
These types of item renderers are still rather simple to implement, but they allow you to exert more control over the component. For example, with a drop-in item renderer you cannot specify the property settings for the component, but with an inline item renderer you can.
You can use standard or custom components as item renderers with either of these approaches.
Drop-in item renderers are extremely simple to implement. All you need to do is
set the itemRenderer
property
of the component for which you want to customize the item
views. The itemRenderer
property
should be a reference to a component class you want to use. For example,
the following creates a list component that uses a date field component
for each item:
<mx:Script> <![CDATA[ import mx.collections.ArrayCollection; [Bindable] private var dataSet:ArrayCollection = new ArrayCollection([new Date(2010, 1, 1), new Date(2010, 4, 15)]); ]]> </mx:Script> <mx:List itemRenderer="mx.controls.DateField" dataProvider="{dataSet}"
The results of this are shown in Figure 10-1.
All the list-based components that allow you to use item renderers
allow you to set the itemRenderer
property for the component itself, except in the case of the data grid,
which requires that you set the itemRenderer
property at the column level.
Example 10-11 sets one of the columns
of a data grid to display a checkbox.
Example 10-11. Using a drop-in item renderer
<?xml version="1.0"?> <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"> <mx:VBox> <mx:DataGrid editable="false"> <mx:columns> <mx:DataGridColumn headerText="Song Title" dataField="title"/> <mx:DataGridColumn headerText="Artist" dataField="artist"/> <mx:DataGridColumn headerText="In Favorites" dataField="inFavorites" itemRenderer="mx.controls.CheckBox" /> </mx:columns> <mx:dataProvider> <mx:ArrayCollection> <mx:Array> <mx:Object songId="0" title="Astronaut" artist="David Byrne" rating="5" inFavorites="true" /> <mx:Object songId="1" title="Rio" artist="Duran Duran" rating="3" /> <mx:Object songId="2" title="Enjoy the Silence" artist="Depeche Mode" rating="4" /> <mx:Object songId="3" title="Mesopotamia" artist="B-52s" rating="5" inFavorites="true" /> </mx:Array> </mx:ArrayCollection> </mx:dataProvider> </mx:DataGrid> </mx:VBox> </mx:Application>
Drop-in item renderers are ideal when you want to use a simple type of item renderer. However, they have several major limitations.
You can use only a handful of standard UI components as drop-in item renderers. Table 10-2 lists those components.
The data value for an item always corresponds to one property
of the item renderer. In other words, a list-based component is a
view for the data model assigned to the dataProvider
property. For example, the
item value is always assigned to the value of a numeric stepper used
as an item renderer. You cannot specify that the item value should
be assigned to the maximum property of the numeric stepper.
You can't customize the components used as item renderers.
Although drop-in item renderers are extremely simple to implement, they are also quite limited in terms of how you can configure them. For instance, in Example 10-11 you can display the checkbox in the data grid columns, but you cannot change any of the properties of the components used as item renderers.
Inline item renderers are a slight step up from drop-in item
renderers in that you can configure the settings of the component used
as the item renderer. For example, you can use an inline item renderer
to set the enabled
property of the
checkbox to disable it so that the user cannot check or uncheck the
box.
Inline item renderers require that you specify the itemRenderer
value using nested MXML tags
rather than attributes. You must then nest within the itemRenderer
tag a Component
tag with a nested tag to create the
type of component you want to use as an item renderer. Example 10-12 specifies the checkbox item
renderer as an inline item renderer. It also applies a label to the
checkbox, and it disables the checkbox so that the user cannot select or
deselect it.
Example 10-12. Using inline item renderers
<?xml version="1.0"?> <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"> <mx:VBox> <mx:DataGrid editable="false"> <mx:columns> <mx:DataGridColumn headerText="Song Title" dataField="title"/> <mx:DataGridColumn headerText="Artist" dataField="artist"/> <mx:DataGridColumn headerText="In Favorites" dataField="inFavorites"> <mx:itemRenderer> <mx:Component> <mx:CheckBox label="Song in favorites" enabled="false" /> </mx:Component> </mx:itemRenderer> </mx:DataGridColumn> </mx:columns> <mx:dataProvider> <mx:ArrayCollection> <mx:Array> <mx:Object songId="0" title="Astronaut" artist="David Byrne" rating="5" inFavorites="true" /> <mx:Object songId="1" title="Rio" artist="Duran Duran" rating="3" /> <mx:Object songId="2" title="Enjoy the Silence" artist="Depeche Mode" rating="4" /> <mx:Object songId="3" title="Mesopotamia" artist="B-52s" rating="5" inFavorites="true" /> </mx:Array> </mx:ArrayCollection> </mx:dataProvider> </mx:DataGrid> </mx:VBox> </mx:Application>
The Component
tag is a powerful
MXML tag. It creates an entirely new scope within the MXML document.
The code within the Component
tag is
essentially an MXML component, and the rules that apply to MXML
components generally apply to the code within the Component
tag. You can have just one root
node, and within that root node you can use Style
, Script
, and all standard MXML tags you could
use in an MXML component document. Because the Component
tag creates its own scope, you don’t
have to worry about conflicts within the Component
tag and the document within which
the Component
tag exists. However, this
also means that you cannot reference data from the MXML document within
the Component
tag. For example, the
following will cause a compile error:
<mx:Script> <![CDATA[ private var maximumCount:uint = 5; ]]> </mx:Script> <mx:List> <mx:itemRenderer> <mx:Component> <mx:NumericStepper maximum="{maximumCount}" /> </mx:Component> </mx:itemRenderer> </mx:List>
The error occurs because maximumCount
is defined in the MXML document,
but it is referenced within the Component
tag, which has a different
scope.
Although the Component
tag is
powerful, we strongly recommend that you use it only to the extent
illustrated by Example 10-12 in which
we set the label
and enabled
properties of the checkbox. If you
need to create more sophisticated item renderers, it is far better to
define them as MXML or ActionScript components. We’ll look at how to do
that next.
You can use a property called outerDocument
within a Component
tag to reference the MXML document containing the Component
tag. You can then reference any
public or internal properties, as in the following example:
<?xml version="1.0"?> <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"> <mx:Script> <![CDATA[ internal var maximumCount:uint = 5; ]]> </mx:Script> <mx:List> <mx:itemRenderer> <mx:Component> <mx:NumericStepper maximum="{outerDocument.maximumCount}"/> </mx:Component> </mx:itemRenderer> </mx:List> </mx:Application>
However, although this is possible, it is generally not recommended because it is much clearer to break out sophisticated item renderers into new custom components, as discussed in the next section.
To exert the most control over item renderers you can use a custom component. Using a custom component (either MXML or ActionScript) you can create extremely sophisticated item renderers. For example, you could create an item renderer that displays a rating using colored shapes, as we’ll do in this section.
A component must implement certain interfaces to work as an item
renderer. There are three basic interfaces for item renderers: IListItemRenderer
, IDropInListItemRenderer
, and IDataRenderer
. All item renderers must implement IListItemRenderer
and IDataRenderer
. Because IListItemRenderer
extends IDataRenderer
, you simply need to implement
IListItemRenderer
in most cases. The
IListItemRenderer
interface requires
many getter/setter methods and public methods, and the best way to
implement the interface is simply to extend a class that already
implements the interface. The following classes already implement the
interface: Button
, ComboBox
, Container
, DataGridItemRenderer
, DateField
, Image
, Label
, ListBase
, ListItemRenderer
, MenuBarItem
, MenuItemRenderer
, NumericStepper
, TextArea
, TextInput
, TileListItemRenderer
, and TreeItemRenderer
. Because Container
implements the interface, you can
extend any type of container.
The IDataRenderer
interface
requires that the implementing class defines a data
getter and setter of type Object
. The data
setter is
automatically called every time the data provider is updated and the
item renderer needs to update. The data
setter is always passed the data provider
element corresponding to the item. For example, in a data grid the data
is the object representing the row. Even though your custom item
renderer component is likely to inherit the data
implementation, you’ll generally want to
override that implementation.
Example 10-13 is
saved in an MXML document called Rating.mxml, and it draws five squares using
10-by-10 canvases. The squares are blue by default, and they are colored
red if they are activated by the value of the rating property from the
data passed to the component. Notice that this component overrides the
data
getter and setter. The setter
retrieves the rating value, and it draws the canvases with the
appropriate colors based on the rating value.
Example 10-13. A custom component for use as an item renderer
<?xml version="1.0" encoding="utf-8"?> <mx:HBox xmlns:mx="http://www.adobe.com/2006/mxml"> <mx:Script> <![CDATA[ import mx.containers.Canvas; private var _data:Object; override public function set data(value:Object):void { _data = value; var rating:uint = uint(value.rating); removeAllChildren(); var canvas:Canvas; for(var i:uint = 0; i < 5; i++) { canvas = new Canvas(); canvas.setStyle("backgroundColor", i < rating ? 0xFF0000 : 0x0000FF); canvas.width = 10; canvas.height = 10; addChild(canvas); } } override public function get data():Object { return _data; } ]]> </mx:Script> </mx:HBox>
The MXML application document in Example 10-14 uses the custom component as an item renderer in a data grid using drop-in syntax.
Example 10-14. Using a custom component as an item renderer
<?xml version="1.0"?> <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"> <mx:VBox> <mx:DataGrid editable="false"> <mx:columns> <mx:DataGridColumn headerText="Song Title" dataField="title"/> <mx:DataGridColumn headerText="Artist" dataField="artist"/> <mx:DataGridColumn headerText="Rating" dataField="rating" itemRenderer="Rating" /> </mx:columns> <mx:dataProvider> <mx:ArrayCollection> <mx:Array> <mx:Object songId="0" title="Astronaut" artist="David Byrne" rating="5" inFavorites="true" /> <mx:Object songId="1" title="Rio" artist="Duran Duran" rating="3" /> <mx:Object songId="2" title="Enjoy the Silence" artist="Depeche Mode" rating="4" /> <mx:Object songId="3" title="Mesopotamia" artist="B-52s" rating="5" inFavorites="true" /> </mx:Array> </mx:ArrayCollection> </mx:dataProvider> </mx:DataGrid> </mx:VBox> </mx:Application>
If the item renderer MXML or ActionScript class is in a package,
you would specify the fully qualified path to the MXML document or
class in the itemRenderer
property
value.
The data grid with the custom item renderer is shown in Figure 10-2 on the following page.
When components are editable, they utilize standard text inputs
when the user is editing a value. For example, the following code
creates a list with editable values simply by setting the editable
property to true
. But the user can edit the values only by using the
standard text input when she clicks on an item.
<mx:List editable="true" width="200" labelField="rating"> <mx:dataProvider> <mx:ArrayCollection> <mx:Array> <mx:Object songId="0" title="Astronaut" artist="David Byrne" rating="5" inFavorites="true" /> <mx:Object songId="1" title="Rio" artist="Duran Duran" rating="3" /> <mx:Object songId="2" title="Enjoy the Silence" artist="Depeche Mode" rating="4" /> <mx:Object songId="3" title="Mesopotamia" artist="B-52s" rating="5" inFavorites="true" /> </mx:Array> </mx:ArrayCollection> </mx:dataProvider> </mx:List>
You can customize the way in which a user can edit data using an
item editor. You assign an item editor using the itemEditor
property in exactly the same ways you can set an item renderer
using the itemRenderer
property. You
must also specify a value for the editorDataField
property that tells the component which property of the item
renderer should be bound to the data provider. The following illustrates
how to rewrite the List
tag from the
preceding example so that it uses a numeric stepper rather than a
standard text input to edit the value. Note that it specifies value
as the editorDataField
because value
is the name of the numeric stepper
property that should be linked to the data provider.
<mx:List editable="true" width="200" labelField="rating" itemEditor="mx.controls.NumericStepper" editorDataField="value"> <mx:dataProvider> <mx:ArrayCollection> <mx:Array> <mx:Object songId="0" title="Astronaut" artist="David Byrne" rating="5" inFavorites="true" /> <mx:Object songId="1" title="Rio" artist="Duran Duran" rating="3" /> <mx:Object songId="2" title="Enjoy the Silence" artist="Depeche Mode" rating="4" /> <mx:Object songId="3" title="Mesopotamia" artist="B-52s" rating="5" inFavorites="true" /> </mx:Array> </mx:ArrayCollection> </mx:dataProvider> </mx:List>
One question that you might ask when working with the item editor
is why you couldn’t simply use an item renderer. For example, it’s possible to list a numeric stepper as
the item renderer in the preceding example. The user could update the
numeric stepper value used as an item renderer. However, item renderers
simply render the data. They do not create data binding with the data
provider. Therefore, changing a value in a numeric stepper used as an
item renderer will not affect the data provider whereas it will update
the data provider when used as an item editor. However, you can tell a
component to use the item renderer as the item editor by setting the
rendererIsEditor
property to true
:
<mx:List editable="true" width="200" labelField="rating"
itemRenderer="mx.controls.NumericStepper"
rendererIsEditor="true"
editorDataField="value">
<mx:dataProvider>
<mx:ArrayCollection>
<mx:Array>
<mx:Object songId="0" title="Astronaut" artist="David Byrne" rating="5"
inFavorites="true" />
<mx:Object songId="1" title="Rio" artist="Duran Duran" rating="3" />
<mx:Object songId="2" title="Enjoy the Silence" artist="Depeche Mode"
rating="4" />
<mx:Object songId="3" title="Mesopotamia" artist="B-52s" rating="5"
inFavorites="true" />
</mx:Array>
</mx:ArrayCollection>
</mx:dataProvider>
</mx:List>
Any custom component you can use as an item renderer you can also
use as an item editor. The only additional rule is that the component
must set a public getter/setter method pair as bindable and you must
specify that as the editorDataField
value for the component using the custom editor. Example 10-15 modifies Rating
so that it can be used as an editor. In
this example we define Rating.mxml
such that it has a bindable data
getter and setter, and when the clicks on the canvases, we update the
rating
property of the data provided
correspondingly.
Example 10-15. A custom item editor
<?xml version="1.0" encoding="utf-8"?> <mx:HBox xmlns:mx="http://www.adobe.com/2006/mxml"> <mx:Script> <![CDATA[ import mx.containers.Canvas; import flash.events.MouseEvent; private var _data:Object; [Bindable(event="dataChanged")] override public function set data(value:Object):void { _data = value; draw(); dispatchEvent(new Event("dataChanged")); } override public function get data():Object { return _data; } public function clickHandler(event:MouseEvent):void { _data.rating = uint(event.currentTarget.name); draw(); } private function draw():void { var rating:uint = uint(_data.rating); removeAllChildren(); var canvas:Canvas; for(var i:uint = 0; i < 5; i++) { canvas = new Canvas(); canvas.setStyle("backgroundColor", i < rating ? 0xFF0000 : 0x0000FF); canvas.width = 10; canvas.height = 10; canvas.name = String(i + 1); canvas.addEventListener(MouseEvent.CLICK, clickHandler); addChild(canvas); } } ]]> </mx:Script> </mx:HBox>
Example 10-16 uses Rating
as both the item renderer and the item
editor.
Example 10-16. Using a custom item editor
<?xml version="1.0"?> <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"> <mx:VBox> <mx:DataGrid editable="false"> <mx:columns> <mx:DataGridColumn headerText="Song Title" dataField="title"/> <mx:DataGridColumn headerText="Artist" dataField="artist"/> <mx:DataGridColumn headerText="Rating" dataField="rating" itemRenderer="Rating" rendererIsEditor="true" editorDataField="data" /> </mx:columns> <mx:dataProvider> <mx:ArrayCollection> <mx:Array> <mx:Object songId="0" title="Astronaut" artist="David Byrne" rating="5" inFavorites="true" /> <mx:Object songId="1" title="Rio" artist="Duran Duran" rating="3" /> <mx:Object songId="2" title="Enjoy the Silence" artist="Depeche Mode" rating="4" /> <mx:Object songId="3" title="Mesopotamia" artist="B-52s" rating="5" inFavorites="true" /> </mx:Array> </mx:ArrayCollection> </mx:dataProvider> </mx:DataGrid> </mx:VBox> </mx:Application>