Flex Item Renderer – Communication entre un itemRenderer et l'application
Voici la suite des articles de Peter Ent sur les item renderers. Cette fois-ci, il nous parle de la communication entre un itemRenderer et une application. Dans certains des exemples précédents, il a utilisé un Button qui propage un évènement personnalisé (BuyBookEvent) pour que l'application réagisse au clic sur le bouton. Cet article couvre quant à lui les différentes options pour communiquer entre votre itemRenderer et votre application MXML plus en détail.
Quelques règles et stratégies
La première règle à respecter est qu'il ne faut pas garder de référence à une instance d'itemRenderer et la modifier (en modifiant ses propriétés public) ou appeler ses méthodes public. Les itemRenderer sont difficiles d'accès depuis l'extérieur pour une raison assez simple: les item Renderer sont recyclés et réutilisés. En récupérer une instance un pourrait troubler le comportement du Framework Flex.
Une fois que vous avez cette règle primordiale en tête, voici les différentes stratégies de communication que vous pouvez utiliser avec un itemRenderer:
- Un itemRenderer peut propager (dispatch) des évènements via son "list owner" (le composant qui utilise l'itemRenderer en question).
- Un itemRenderer peut utiliser des variable membre de classe static. Cela peut inclure par exemple Application.application. Si vous avez stocké des valeurs de manière globale dans votre objet application, vous pouvez les atteindre de cette manière. Cette méthode fonctionne mais n'est pas conseillée car elle limite l'encapsulation).
- Un itemRenderer peut utiliser des variables public de la liste qui le contient (exemple plus loin)
- Un itemRenderer peut utiliser n'importe quelle data de votre enregistrement. Par exemple, vous pouvez avoir un item dans l'enregistrement qui ne sert pas à l'affichage direct mais qui influence le comportement de l'itemRenderer
Modifier les itemRenderer de manière dynamique
Voici l'itemRenderer MXML utilisé pour une <mx:TileList> dans les articles précédents. On va le rendre plus dynamique en le faisant réagier aux changements d'une source externe. (Ce composant est appelé BookItemRenderer.mxml):
<?xml version="1.0" encoding="utf-8"?>
<mx:HBox xmlns:mx="http://www.adobe.com/2006/mxml" width="250" height="115" >
<mx:Image id="bookImage" 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>
Supposons que vous affichiez une catalogue d'item dans une TileList. Vous avez aussi un Slider (en dehors de l'itemRenderer) qui laisse l'utilisateur donne un intervalle de prix. Tous les items qui sont en dehors de cet intervalle de prix font légèrement disparaitre (l'alpha de l'itemRenderer sera modifié). Vous devrez donc dire à tous les itemRenderer que le critère a changé et qu'ils doivent mettre à jour leur valeur d'alpha.
Pour cela, vous pouvez faire une override sur la méthode set data() comme ceci:
override public function set data( value:Object ) : void{
super.data = value;
if( data.price < criteria ) alpha = 0.4;
else alpha = 1;
}
La question que vous devez maintenant vous poser est: comment modifier cette variable criteria ? La meilleure manière à adopter avec un itemRenderer est de toujours travailler sur la donnée qu'on leur demande d'afficher. Dans ce cas, il est assez maladroit que le "criteria" fasse partie de la data. On doit donc chercher en dehors de la data. Vous avec donc 2 choix:
- Que le "criteria " fasse partie du composant (de type liste) lui même (List, DataGrid, TileList ou autre). C'est à dire que votre composant, doit hériter d'un composant de type liste et déclarer un "criteria" comme variable membre public
- Que le "criteria" soit une variable globale à l'application.
Si vous connaissez les "Best Practices" en code, vous savez qu'il faut toujours éviter de déclarer des variables globales. Le meilleur choix est donc le premier, faire un extends sur la classe du composant et déclarer un variable membre dans cette classe. Après tout, la classe est utilisée pour afficher de la data et le criteria fait partie de l'affichage.
Pour cet exemple, on va hériter de TileList et avoir une variable criteria comme membre public:
package{
import mx.controls.TileList;
public class CatalogList extends TileList{
public function CatalogList(){
super();
}
private var _criteria:Number = 10;
public function get criteria() : Number{
return _criteria;
}
public function set criteria( value:Number ) : void{
_criteria = value;
}
}
}
L'idée est qu'un composant en dehors de l'itemRenderer peut modifier cette variable criteria en modifiant la propriété public du composant TileList. L'itemRenderer pourra quant à lui accéder à cette variable grâce à listData.
Accéder à une variable du composant parent en utilisant listData
Les itemRenderer ont aussi accès à une autre donnée: une information sur la liste elle-même et quelle ligne/colonne (si le composant a des colonnes) ils sont en train d'afficher. Cette information est connue sous le nom de listData et pourrait être utilisée dans notre itemRenderer exemple BookItemRenderer.mxml:
override public function set data( value:Object ) : void
{
super.data = value;
var criteria:Number = (listData.owner as CatalogList).criteria;
if( data.price < criteria ) alpha = 0.4;
else alpha = 1;
}
Placez ce code dans un bloc <mx:Script> du code de BookItemRenderer.mxml présent plus haut. La propriété listData d'un itemRenderer a un champ "owner", qui référence le composant auquel l'itemRenderer appartient. Dans cet exemple, le "owner" est un composant CatalogList (la classe que l'on a crée qui extends TileList). On fait ensuite une conversion de typeen CatalogList qui permet d'accéder à la variable "criteria".
Accéder à la propriété listData avec un composant conteneur (HBox, VBox, ...)
Si vous avez testé cet exemple, vous avez sûrement eu l'erreur:
1120: Accès à la propriété non définie listData.
Alors pourquoi ne peut on pas accéder à listData ? Et bien c'est du au fait que la propriété listData n'est disponible que quand la classe de l'itemRenderer implémente l'interface IDropInListItemRenderer. Malheureusement, les composants conteneur n'implémentent pas cette interface qui permet d'accéder à listData facilement. Des composants comme Button ou Label l'implémentent mais pour les conteneurs, vous devrez le faire vous-même.
Implémenter cette interface est assez simple et vous pouvez trouver comment en fouillant dans la doc Flex. Voici ce que vous devez ajouter pour la classe BookItemRenderer:
- Indiquez que la classe implémente l'interface:
<mx:HBox xmlns:mx="http://www.adobe.com/2006/mxml" ... implements="mx.controls.listClasses.IDropInListItemRenderer">
- Ajouter des getters/setters dans une bloc mx:Script du fichier de l'itemRenderer pour listData:
import mx.controls.listClasses.BaseListData;
private var _listData:BaseListData;
public function get listData() : BaseListData{
return _listData;
}
public function set listData( value:BaseListData ) : void{
_listData = value;
}
Quand le composant liste va voir que l'itemRenderer implémente l'interface IDropInListItemRenderer, il va créer un item listData et l'assigner à toutes les instance de l'itemRenderer.
Utiliser invalidateList() pour rafraîchir l'affichage
Fixer la propriété "criteria" de la classe n'est pas aussi simple que de lui assigner une valeur. Assigner une valeur de va pas dire à Flex que la data a été modifiée. Le changement de valeur de criteria doit donc propager un évènement. Voici les modifications à faire à la fonction set criteria():
public function set criteria( value:Number ) : void{
_criteria = value;
invalidateList();
}
Une fois que _criteria a été modifie, on appelle invalidateList(). Cela va faire une remise à zéro de l'itemRenderer avec les valeurs du dataProvider.
Voici le processus complet:
- L'itemRenderer regarde dans son list owner, la variable criteria pour l'aider à déterminer comment afficher la donnée
- La classe list owner, une extension d'un classe de base de Flex contient des propriété publiques lues par le/les itemRenderer pouvant être modifiée par du code externe ou par un autre composant.
- Quand la propriété de la liste est modifiée, elle appelle la méthode invalidateList(). Cette méthode va lancer un rafraichissement de l'itemRenderer.
Voici l'exemple complet (simpliste) que j'ai concocté. Manipulez le Slider en bas pour modifier l'alpha des livres (les prix sont de 35/48/58):
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" xmlns:local="*">
<mx:XML id="XMLData" source="data.xml"/>
<local:CatalogList id="mylist" width="560" itemRenderer="BookItemRenderer"
dataProvider="{XMLData.book}" height="350" columnWidth="275" rowHeight="135" />
<mx:HSlider id="valueSlider" minimum="20" maximum="80" tickInterval="10" value="50"
change="{mylist.criteria = valueSlider.value}" snapInterval="1"
/>
</mx:Application>
Flex Source Code Download: Télécharger le code source complet de l'application
Articles similaires
- Flex Item Renderer – Communiquer avec un itemRenderer grâce à des Event
- Flex Item Renderer – Créer un ItemRenderer réutilisable avec listData
- Flex Item Renderer – Modifier la taille d'un item avec des Transition
- Flex Item Renderer – Créer un ItemRenderer externe (MXML et ActionScript)
- Flex Item Renderer – Un itemRenderer optimisé en héritant de UIComponent






24 mars 2009
Merci, je faisais justement avec une variable globale jusqu'à présent...
Par contre une petite interrogation, comment fait-on pour disablé un item de notre list via notre itemrenderer, je m'explique:
Au lieu de faire jouer l'alpha j'aimerais que mon utilisateur ne puisse plus cliquer sur mon livre pour remprendre ton exemple, cad qu'il ne puisse plus cliquer sur Buy...
Merci d'avance
24 mars 2009
Et bien il suffit de donner un id à ton bouton. Ensuite, au lieu de faire alpha = 0.4, tu peux faire this.myBtn.enabled = false ou true dans ton itemRenderer.
Et voila ^^
Fabien