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 ;)