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

1juin/104

Flex 4 – (4) Structure des composants Flex 4 et comparaison avec Flex 3

La hiérarchie interne des composants Flex a légèrement changé. Parfois, certains noms ont changé, et parfois, on va exposer ce qui vous était auparavant caché.

Le cas des composants

Prenons par exemple, l'instanciation d'un bouton en Flex:

<s:Button />

Lorsque vous instanciez un bouton, plus d'un objet sera crée. Voici schématiquement ce qui sera crée d'un point de vue Layout:

Slide3

Note: Dans ce schéma, TextBox est en fait Label.

Un fichier Skin sera instancié et mis dans la liste d'affichage du Button. Voici à quoi ressemble la skin d'un Button Flex 4:

<s:Skin xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark"  minWidth="23" minHeight="23">

    <fx:Metadata>
    	[HostComponent("mx.components.Button")]
    </fx:Metadata>

    <s:states>
        <s:State name="up" />
        <s:State name="over" />
        <s:State name="down" />
        <s:State name="disabled" />
    </s:states>

    <!-- background -->
    <s:Rect left="0" right="0" top="0" bottom="0"
    	  width="70" height="23"
    	  radiusX="2" radiusY="2">
        <s:stroke>
            <s:SolidColorStroke color="0x5380D0" color.disabled="0xA9C0E8" />
        </s:stroke>
        <s:fill>
            <s:SolidColor color="0xFFFFFF" color.over="0xEBF4FF" color.down="0xDEEBFF" />
        </s:fill>
    </s:Rect>

    <!-- label -->
    <s:Label id="labelDisplay" />

</s:Skin>

Même si dans votre code MXML, on dirait que votre composant est tout en bas de la hiérarchie, à travers sa skin, d'autres composants enfants seront créés. Pour accéder à ces éléments, vous pouvez utiliser la propriété "skin", présente sur tous les composants de type SkinnableComponent (Flex 4).

A travers la propriété "skin", vous aurez accès à l'instance de ButtonSkin, qui vous permettra d'accéder au Rectangle et au Label. Pour accéder au Label, vous pourriez faire:

myButton.skin.getElementAt(2) ou myButton.skin.labelDisplay

Vous pouvez accéder directement au Label par "labelDisplay" car c'est une "skin part". On reviendra plus en détail sur les "skin part" dans un prochain tutorial de ce fil rouge.

Le cas des conteneurs

Principalement, les mêmes règles s'appliquent aux conteneurs, qui sont appelés dérivent de SkinnableContainers en Flex 4.

Les SkinnableContainers sont des conteneurs qui ont nativement des enfants, mais ils sont aussi (par héritage), des SkinnableComponent, ayant une skin, et ont donc aussi des enfants par cette skin.

Prenons un exemple sur un Panel:

<s:Panel>
    <s:Button />
    <s:Label />
    <s:CheckBox />
</s:Panel>

Ce panel  trois enfants: un bouton, un label et une checkbox. Pour y accéder, vous pouvez utiliser l'API définie sur SkinnableContainer.

Pour résumer, voici ce qui se passe pour un conteneur, d'un point de vue purement composant:

Slide4

Ceci était une vision plutôt "haut niveau". Avec le Skinning, voici ce à quoi la vraie hiérarchie de composants du Panel va ressembler:

Slide5

N'ayez pas peur du nombre de flèches, les flèches bleues et rouge indiquent les relations parent/owner que je vais expliquer tout de suite (car différentes par rapport à Flex 3).

Voici les points importants à comprendre:

  • Les enfants (Children) du Panel sont button, label et checkbox
  • Le parent, d'un point de vue "composant" de button, label et checkbox est le Panel. On y accède sur ces 3 composants par la propriété "owner"
  • Le parent, d'un point de vue "layout", des 3 composants est le "contentGroup" de l'objet PanelSkin. On y accède sur ces 3 composants par la propriété "parent"

Ce qui signifie que même si on dirait que le Button est un enfant du Panel, le seul enfant du Panel est l'instance de sa skin. Les composants Button, Label et CheckBox deviennent en fait des enfants de "contentGroup", une partie de la skin.

Il y a plusieurs manières d'accéder au bouton du Panel:

  • myPanel.getElementAt(0)
  • myPanel.contentGroup.getElementAt(0)
  • myPanel.skin.contentGroup.getElementAt(0)

Tous les composants de type SkinnableComponent ont une propriété "skin". Dans un SkinnableContainer, les enfants du composant sont en fait ajoutés vers le "contentGroup" de la skin.

L'interface IVisualElementContainer est l'interface qui définit les APIs d'accès au contenu. Les composants Spark Skin, Group et SkinnableContainer sont les composants qui implémentes cette interface et peuvent donc contenir d'autres composants graphiques. Pour rester cohérent, les conteneurs MX (Flex 3) vont aussi implémenter cette interface, juste pour être une facade vers les méthodes addChild, numChildren, …

Voici les méthodes définies par l'interface IVisualElementContainer:

package mx.core
{
public interface IVisualElementContainer
{
public function get numElements():int;
public function getElementAt(index:int):IVisualElement
public function addElement(element:IVisualElement):IVisualElement;
public function addElementAt(element:IVisualElement, index:int):IVisualElement;
public function removeElement(element:IVisualElement):IVisualElement;
public function removeElementAt(index:int):IVisualElement;
public function getElementIndex(element:IVisualElement):int;
public function setElementIndex(element:IVisualElement, index:int):void;
public function swapElements(element1 :IVisualElement, element2 :IVisualElement):void;
public function swapElementAt(index1:int, index2:int):void;
}
}

Cette interface facilite le parcours de l'arbre d'affichage. Principalement, cette interface est un moyen pour les conteneurs d'indiquer quels sont ses enfants. Il sera par exemple utilisé par le FocusManager. Le FocusManager sera dépendant de cette interface, et pas d'une classe implémentant cette interface.

Un IVisualElementContainer va donc contenir des IVisualElement.

Les différences avec les composants Flex 3

Les composants Flex 3 utilisaient les mêmes concepts exposés plus haut, mais la plupart étaient appliqués sans que vous puissiez le voir ou y avoir accès. Les composants Sparks sont plus "transparents" sur ce qu'il se passe, car le skinning est plus exposé qu'auparavant.

Un bouton MX a un enfant, l'objet TextField. Cet enfant était ajouté directement au Button via addChild() (il n'y avait pas de skin). Dans cet exemple, le TextField était l'enfant direct du Button. Si vous demandiez ses enfants au Button, il vous aurait donné le TextField. Si vous aviez demandé le "parent" du TextField, il vous aurait donnée le Button.

Dans Flex 4, un Button a un enfant, sa skin. L'objet Skin contient un Label. Si vous demandez au Button ses enfants, ils vous en donnera un, sa skin. Si vos voulez déterminer les enfants de la skin du Button, vous devez récupérer une référence vers la skin, et lui demander ses enfants.

Les conteneurs Flex 3 vous mentaient

Le comportement des conteneurs est un peu plus difficile à comprendre, cas ils ont à la fois des composants enfant et des composants enfant de la skin. Dans Flex 3, la liste d'affichage, alias Display List, contient les enfants de la skin et un enfant spécial nommé "contentPane". Tous les composants enfant du Panel sont poussés vers le "contentPane".

Au fond, c'est assez similaire à ce qui se passe dans Flex 4. Cependant, beaucoup vous était caché en Flex 3. Si vous demandiez au Panel d'afficher ses enfants, il va vous mentir et vous donner les enfants du "contentPane". Pour accéder aux enfants de la skin, il fallait récupérer la liste des enfants disponible à travers la propriété "rawChildren". Si vous demandiez le parent d'un des enfants, il vous aurait donné le Panel, même si le vrai parent de la liste d'affichage est "contentPane".

Avec les composants Flex 3, la propriété "parent" et la propriété "owner" pointaient toujours sur le même objet. Comme vous l'avez compris plus haut, en Flex4, "owner" et "parent" pointent sur deux objets différents.

Articles similaires

Commentaires (4) Trackbacks (1)
  1. Bonjour,

    je cherche à créer une fonction récursive me permettant de supprimer complétement un élément.

    A ce que j'ai cru comprendre, pour supprimer un élément du flux visuel et l'envoyer au garbage collector, il faut plusieurs conditions :

    1) lui supprimer tous ses enfants
    2) lui appliquer un removeElement()
    3) lui supprimer tous ses événements
    4) écrire monElement = null; ?

    Concernant le point 4, est ce indispensable ?

    Concernant le point 3, comment mettre en œuvre une boucle permettant de lister/supprimer dynamiquement tous les événements d'un éléments ? je connais bien le hasEventListener() mais comment récupérer l'événement à proprement dit pour le supprimer ?

    Une âme charitable pourrait-elle m'aider un peu ? :)

    Merci

  2. J'ai trouvé cet article : http://saturnboy.com/2010/03/event-listeners-in-flex-4/

    unique alternative d'étendre EventDispatcher ?

  3. Salut,
    Honnetement, il vaut mieux garder le contrôle dans son code et supprimer les listeners ajoutés lorsque l'on en a plus besoin. Cela est plus clean et permet de ne pas se planter. La solution de l'article que vous donnez est assez brutale mais fonctionnera si vous voulez vraiment être sur que le GC va bien passer

    Fabien

  4. Ok, merci du feed back ;)

    je cherchais à supprimer une série de 3 dropDownList liée et les recréer dynamiquement mais finalement et après aiguillage sur un forum, je vais surement m'orienter vers les states :)


Leave a comment

(required)