Apache Adobe Flex TutorialTutoriaux Adobe Flex & AIR en Français

16mar/094

Flex Item Renderer – Créer un ItemRenderer externe (MXML et ActionScript)

Dans ce article sur les Item Renderer Flex, je vais parler des ItemRenderer externes, c'est-à-dire ceux contenus dans un fichier MXML ou AS externe. Puisque c'est l'équipe d'Adobe qui en parle le mieux, voici une traduction de l'excellent article de Peter Ent:

Understanding Flex itemRenderers — Part 2: External renderers

Comme vous le savez si vous suivez Flex Tutorial, vous pouvez créer des item Renderers de deux manières principalement. En inline (directement dans le code MXML)  ou bien dans des composants externes (MXML ou ActionScript). En fait, pendant la compilation, le compilateur Flex extrait le code inline et en fait une classe pour vous sans que vous le sachiez. Pour résumer, utiliser un itemRenderer a les avantages suivants:

  • L'itemRenderer peut être facilement utilisé dans plusieurs listes
  • Le code est plus simple à maintenir
  • Vous pouvez utiliser la vue Design de Flex Builder pour construire l'itemRenderer initial

Transformer un itemRenderer Inline en itemRenderer externe

Voici un itemRenderer complexe utilisé dans une DataGrid Flex:

<mx:DataGridColumn headerText="Title" dataField="title">
	<mx:itemRenderer>
		<mx:Component>
			<mx:HBox paddingLeft="2">
				<mx:Script>
				<![CDATA[
					override public function set data( value:Object ) : void {
						super.data = value;
						var today:Number = (new Date()).time;
						var pubDate:Number = Date.parse(data.date);
						if( pubDate > today ) setStyle("backgroundColor",0xff99ff);
						else setStyle("backgroundColor",0xffffff);
					}
				]]>

				</mx:Script>
				<mx:Image source="{data.image}" width="50" height="50" scaleContent="true" />
				<mx:Text width="100%" text="{data.title}" />
			</mx:HBox>
		</mx:Component>

	</mx:itemRenderer>
</mx:DataGridColumn>

L'itemRenderer est basé sur HBox, contient une Image et un Text. La couleur de fond (backgroundColor) est fixée suivant la valeur du champ pubDate de l'item. Vous pouvez écrire le même itemRenderer en tant que fichier externe en suivant ces étapes:

  1. Si vous utilisez Flex Builder, créez simplement un nouveau fichier MXML Component (nommé GridColumnSimpleRenderer dans cet exemple mais vous pouvez choisir un nom à vous) et donner comme tag root (celui qui englobe tout), un tag mx:HBox sans vous soucier de la taille.
  2. Si vous utilisez le SDK seul, créez un nouveau fichier MXML (appelé  GridColumnSimpleRenderer.mxml) et donnez-lui HBox comme tag principal
  3. Une fois que votre fichier est ouvert, copiez tout ce qu'il y a entre <mx:HBox> et </mx:HBox> mais ne copiez pas ces tags puisqu'ils sont déjà dans le fichier. Voici le résultat que vous devriez obtenir:
<?xml version="1.0" encoding="utf-8"?>
<mx:HBox xmlns:mx="http://www.adobe.com/2006/mxml" width="400" height="300">
	<mx:Script>
	<![CDATA[
		override public function set data( value:Object ) : void {
			super.data = value;
			var today:Number = (new Date()).time;
			var pubDate:Number = Date.parse(data.date);
			if( pubDate > today ) setStyle("backgroundColor",0xff99ff);
			else setStyle("backgroundColor",0xffffff);
		}
	]]>

	</mx:Script>
	<mx:Image source="{data.image}" width="50" height="50" scaleContent="true" />
	<mx:Text width="100%" text="{data.title}" />
</mx:HBox>

Sauvegardez le fichier. Modifiez maintenant la définition de la DataGridColumn en supprimant l'itemRenderer Inline et en le remplaçant par ceci:

<mx:DataGridColumn headerText="Title" dataField="title"
itemRenderer="GridColumnSimpleRenderer">

Relancez votre application et vous aurez la surprise de voir la hauteur de chaque ligne. Cela est du à la présence de height="300" dans l'itemRenderer.

Déterminer la width / height d'un ItemRenderer

Un composant de type Liste fixe toujours la largeur (width) de son itemRenderer. Si vous indiquez une width (par exemple, width="400"), elle sera ignorée. Vous devrez donc écrire vos itemRenderer en assumant le fait que la largeur va changer lorsque l'utilisateur va changer la largeur d'un colonne ou d'une liste.

Il n'en va pas de même pour la hauteur (height). Si la liste à sa propriété "rowHeight" fixée,  elle va imposer cette height à chaque ligne (row), ignorant les hauteur que vous avez fixé dans votre itemRenderer. Cependant, si vous fixez la propriété variableRowHeight d'une List à true, alors la liste va regarder la hauteur de chaque itemRenderer.

Changer l'itemRenderer de manière dynamique

Dans l'exemple précédent, on fait un override de la fonction set data() pour examiner la data et fixer la couleur de fond de l'itemRenderer. Cette pratique est très commune. Faire un override sur set data() vous permet d'intercepter le moment pendant lequel la data va être modifiée.

Voici les erreurs généralement constatées avec les itemRenderer personnalisés et le set data():

  • Oublier de faire un appel à super.data = value;. Ce point est vital si vous voulez que votre itemRenderer fonctionne correctement
  • Oublier de remettre les styles à zéro si le test échoue. Il peut être tentant de simplement fixer la couleur quand le test est positif mais il faut garder en tête que les itemRenderer sont recyclés et que les instructions else sont donc absolument nécessaires.

Créer un ItemRenderer en ActionScript

Pour faciliter la compréhension, on va prendre un itemRenderer écrit en MXML utilisé dans une TileList et le porter en ActionScript. Voici le MXML inline:

<mx:itemRenderer>
	<mx:Component>
		<mx:HBox verticalAlign="top">
			<mx:Image source="{data.image}" />
			<mx:VBox height="115" verticalAlign="top" verticalGap="0">
				<mx:Text text="{data.title}" fontWeight="bold" width="100%"/>

				<mx:Spacer height="20" />
				<mx:Label text="{data.author}" />
				<mx:Label text="Available {data.date}" />
				<mx:Spacer height="100%" />
				<mx:HBox width="100%" horizontalAlign="right">
					<mx:Button label="Buy" fillColors="[0x99ff99,0x99ff99]">

						<mx:click>
						<![CDATA[
							var e:BuyBookEvent = new BuyBookEvent();
							e.bookData = data;
							dispatchEvent(e);
						]]>
						</mx:click>
					</mx:Button>

				</mx:HBox>
			</mx:VBox>
		</mx:HBox>
	</mx:Component>
</mx:itemRenderer>

On va donc transformer cet itemRenderer en classe ActionScript.

Tout d'abord, créez une nouvelle classe appellée BookTileRenderer.as et faîtes la hériter de HBox tout comme votre itemRenderer inline:

Actionscript:
  1. package
  2. {
  3.     import flash.events.MouseEvent;
  4.  
  5.     import mx.containers.HBox;
  6.     import mx.containers.VBox;
  7.     import mx.controls.Button;
  8.     import mx.controls.Image;
  9.     import mx.controls.Label;
  10.     import mx.controls.Spacer;
  11.     import mx.controls.Text;
  12.  
  13.     public class BookTileRenderer extends HBox
  14.     {
  15.         public function BookTileRenderer()
  16.         {
  17.             super();
  18.         }
  19.  
  20.     }
  21. }

Créez des variables membres pour avoir des références vers les composants enfant:

Actionscript:
  1. private var coverImage:Image;
  2. private var titleText:Text;
  3. private var spacer1:Spacer;
  4. private var authorLabel:Label;
  5. private var pubdateLabel:Label;
  6. private var spacer2:Spacer;
  7. private var buyButton:Button;

Faîtes un override sur la fonction createChildren() pour créer les composants et les ajouter à la HBox:

Actionscript:
  1. override protected function createChildren():void{
  2.     coverImage = new Image();
  3.     addChild(coverImage);
  4.  
  5.     var innerBox:VBox = new VBox();
  6.     innerBox.explicitHeight = 115;
  7.     innerBox.percentWidth = 100;
  8.     innerBox.setStyle("verticalAlign","top");
  9.     innerBox.setStyle("verticalGap", 0);
  10.     addChild(innerBox);
  11.  
  12.         titleText = new Text();
  13.         titleText.setStyle("fontWeight","bold");
  14.         titleText.percentWidth = 100;
  15.         innerBox.addChild(titleText);
  16.  
  17.         spacer1 = new Spacer();
  18.         spacer1.explicitHeight = 20;
  19.         innerBox.addChild(spacer1);
  20.  
  21.         authorLabel = new Label();
  22.         innerBox.addChild(authorLabel);
  23.  
  24.         pubdateLabel = new Label();
  25.         innerBox.addChild(pubdateLabel);
  26.  
  27.         spacer2 = new Spacer();
  28.         spacer2.percentHeight = 100;
  29.         innerBox.addChild(spacer2);
  30.  
  31.         var buttonBox:HBox = new HBox();
  32.         buttonBox.percentWidth = 100;
  33.         buttonBox.setStyle("horizontalAlign","right");
  34.         innerBox.addChild(buttonBox);
  35.  
  36.             buyButton = new Button();
  37.             buyButton.label = "Buy";
  38.             buyButton.setStyle("fillColors",[0x99ff99,0x99ff99]);
  39.             buyButton.addEventListener(MouseEvent.CLICK, handleBuyClick);
  40.             buttonBox.addChild(buyButton);
  41. }

Le code est indenté pour bien que vous identifiez les relations parent-enfant. On ajoute aussi un eventListener au bouton car de base, il ne fait rien.

Faîtes ensuite un override sur la fonction commitProperties() pour modifier les composants UI suivant le data:

Actionscript:
  1. override protected function commitProperties():void
  2. {
  3.     super.commitProperties();
  4.  
  5.     coverImage.source = data.image;
  6.     titleText.text = data.title;
  7.     authorLabel.text = data.author;
  8.     pubdateLabel.text = data.date;
  9. }

Enfin, ajouter un event handler pour le bouton Buy:

Actionscript:
  1. private function handleBuyClick( event:MouseEvent ) : void
  2. {
  3.     var e:BuyBookEvent = new BuyBookEvent();
  4.     e.bookData = data;
  5.     dispatchEvent(e);
  6. }

Modifier ensuite l'application pour utiliser la classe ActionScript comme itemRenderer. Supprimez simplement le renderer inline MXML pour le remplacer par le nom de la classe comme propriété itemRenderer:

<mx:TileList id="mylist" x="29" y="542" width="694" itemRenderer="BookTileRenderer"
       dataProvider="{testData.book}" height="232" columnWidth="275" rowHeight="135" >

Si vous vous basez sur un conteneur comme ici HBox, vous pouvez voir que de faire l'itemRenderer est plus difficile en ActionScript. Vous pouvez tout aussi bien utiliser un fichier MXML car la différence de performance ne sera pas flagrante.

Articles similaires

Commentaires (4) Trackbacks (0)
  1. Sur la base de cet excellent exemple, j'ai créé un itemRender utilisant un radioButton.
    En d'autre termes, l'une des colonnes de ma dataGrid affiche un radioButton qui permet de choisir une seule des lignes de celle-ci.

    Tout se passe bien tant que je n'ai qu'une dataGrid par application
    Le problème apparaît quand il y en a plusieurs : le caractère mutuellement exclusif des boutons fait que le chox d'une ligne dans une dataGrid déselectionne les choix faits dans les autres.

    si je crée les dataGrid depuis le design, j'ai la possibilité, dans le mxml, de créer préalablement un RadioButtonGroup, pour chacune des grilles ; j'affecte une id différente à chaque groupe, je spécifie le groupName aux boutons et tout est parfait.

    Par contre, si je crée mon interface en AS3, je suis bloqué car il m'est impossible d'affecter une id à un RadioButtonGroup.

    Est-ce un problème de code ou d'architecture ?

    merci

  2. Bonjour,
    Si vous souhaitez avoir des DataGrid avec ce comportement de manière indépendante, il faudra effectivement créer un RadioButtonGroup pour chaque DataGrid car on ne peut faire qu'un choix de RadioButton pour chaque Group. Cela peut aussi être fait en AS3, l'id du RadioButtonGroup est en fait le nom de votre instance de RadioButtonGroup (et c'est vrai pour tous les composants).
    Par exemple:
    var monRBG: RadioButtonGroup = new RadioButtonGroup;
    est équivalent à

    Le plus simple si vous voulez réutiliser ce composant DataGrid + RBG, est peut être de faire un composant MXML personnalisé comprenant ces 2 éléments. Le développement sera ensuite plus aisé.

    Fabien

  3. Merci pour cet exemple, très utile pour moi!

  4. Idem, très utile merci beaucoup.


Leave a comment

(required)

Aucun trackbacks pour l'instant