Pratiques Flex (1/?) : les bindings
Par -Alexandre LEGOUT aka LAlex- le 3 septembre 2008, 15:41 - AS3 / Flex2 - Lien permanent
Les bindings sont un aspect de Flex bien séduisants car il permettent de refléter automatiquement la modification du contenu d'une variable vers une autre. Seulement, lorsqu'ils sont utilisés intensément, ils peuvent également devenir un goulot d'étranglement en terme de performances.
Pour éviter cela, la solution est plutôt simple, et nécessite de savoir comment fonctionne la balise [Bindable] et comment elle est utilisée par le framework Flex...
Les bindings fonctionnent grâce à des meta-balises qui sont interprétés par le compilateur Flex (ou plutôt le traducteur MXML/AS3). Cette meta-balise va servir à dire quel évenement va signaler le changement de la valeur d'une propriété.
En prenant un exemple simple:
<mx:Button id="enableButton" toggle="true" />
<mx:TextInput id="textInput" enabled="{enableButton.selected}" />
Dans cet exemple, si le bouton est enfoncé (selected=true), la zone de saisie sera activée, et si le bouton est relevé (selected=false), la zone de saisie sera désactivée. Cela se fait grâce a un binding, qui met a jour la propriété enabled du champ de saisie lorsqu'un événement signale que la propriété selected du bouton à été modifiée. Tout cela est géré automatiquement par Flex car la propriété selected de la classe Button est déclarée comme étant [Bindable].
Il est possible de créer ses propres propriétés [Bindable], de manière plus ou moins assistée par Flex:
[Bindable]
Le traducteur MXML/AS3 (même si la meta-balise est dans une classe AS3 et non pas un MXML) va créer à la place un getter/setter qui va dispatcher un événement standard pour signaler le changement de valeur. Cet événement sera un PropertyChangeEvent de type PropertyChangeEvent.PROPERTY_CHANGE. Vous verrez ci-aprés un exemple de code AS3 avant (avec son [Bindable]) et aprés.
Avant
[Bindable]
protected var mc:MessageController;
Aprés
/**
* generated bindable wrapper for property mc (protected)
* - generated setter
* - generated getter
* - original protected var 'mc' moved to '_3478mc'
*/
[Bindable(event="propertyChange")]
protected function get mc():MessageController
{
return this._3478mc;
}
protected function set mc(value:MessageController):void
{
var oldValue:Object = this._3478mc;
if (oldValue !== value)
{
this._3478mc = value;
this.dispatchEvent(mx.events.PropertyChangeEvent.createUpdateEvent(this, "mc", oldValue, value));
}
}
[Bindable(event="eventName")]
Lorsque l'on précise dans la méta-balise le nom de l'événement qui sera diffusé lors d'un changement de valeur de la variable, le traducteur MXML/AS3 n'interviendra pas sur le code: le framework se contentera d'écouter l'événement précisé pour mettre à jour les variables de destination. C'est donc au développeur de diffuser lui-même l'événement dans son code, que ce soit dans le setter de la propriété, ou n'importe quel méthode qui va affecter la propriété. On voit d'ailleurs dans l'évenement précédent qu'un [Bindable] est transformé en [Bindable(event="propertyChange"].
Étant donné qu'il n'est pas possible d'utiliser de variable dans les meta-balises, j'ai également pris l'habitude d'utiliser systématiquement des chaînes de caractères pour le type de mes événements: étant donné qu'à priori on n'utilise nulle part ailleurs dans le code l'écoute de cet événement, ça permet de différencier le dispatch d'un événement destiné à être écouté et celui d'un événement destiné au binding... J'ai également pris l'habitude de créer pour chaque binding "à la main" une méthode notifyMonBinding qui dispatche l'évenement qui va bien.
Les performances
Voyons maintenant comment Flex se sert des bindings: un BindingManager (classe non documentée) va écouter les différents événements spécifiés dans les balises [Bindable] et lorsqu'elle en reçoit ce fameux événement, elle va relire la propriété concernée et l'appliquer à tous les endroits où elle est utilisée en tant que source.
On peut voir facilement ce qu'il se passe si on utilise partout uniquement
la balise [Bindable] simple.
Imaginons une classe disposant de 10 propriétés [Bindable] (sans event
spécifié). Imaginons également que ces dix propriétés sont utilisées deux fois
chacune en tant que source via la binding. Lorsqu'une propriété va changer, un
événement PropertyChangeEvent.PROPERTY_CHANGE va être diffusé pour
signaler ce changement. Hors, chaque propriété étant liée au même événement
(propertyChange, le type d'évenement généré par défaut), Flex va faire appel
aux getter de chaque propriété, et appliquer les modifications (qui en
l'occurrence n'ont pas eu lieu sur 9 d'entre elles) sur la totalité des 20
bindings, avec toute la procédure qui s'ensuit, soit le calcul du layout et le
dessin des différents éléments affectés. Pour ne pas être alarmiste non
plus, la plupart des setters bien fait évitent de provoquer une action si la
nouvelle valeur qu'on lui affecte est la même que l'ancienne valeur, et le
processus d'invalidation permet de ne redessiner qu'une fois un composant dont
on change plusieurs propriétés.
C'est pourquoi pour soulager l'application, il est très important de:
- définir ses propriétés de binding sous la forme [Bindable(event="eventName")]
- définir un événement de binding différent par propriété (ou par groupes de propriétés analogues)
Conclusion
Les bindings sont pratiques et sexys: par une simple affectation dans un
fichier XML (ou création d'un binding avec BindingUtils), on arrive a faire
interagir deux entités Flex facilement. Mais attention à ne pas provoquer une
cascade d'instructions lourdes à l'exécution sur une action bénigne! C'est pour
cela qu'il est important de savoir le fonctionnement du binding et ses
conséquences.
Parfois, s'en tenir au schéma EventDispatcher.addEventListener est
largement suffisant ![]()
Commentaires
Fil des commentaires de ce billet