Pratique d'Action Script 3


Pratique d'Action Script 3

 

trace("Pratique d'ActionScript 3"); // Thibault IMBERT Remerciements // "Je voudrais remercier tout particulièrement Mathieu Anthoine (Mama) pour sa relecture soignée et ses conseils avisés durant toute la rédaction de l?ouvrage. Son expérience et sa vision du Flash est une source d?inspiration. Jérôme Decoster pour sa relecture attentive, son soutien et ses conseils toujours pertinents. Pour nos soirées pizza à discuter ActionScript 3 et te faire rentrer chez toi en vélib. Chris Georgenes (www.mudbubble.com) et Aurelien Marcheguay de Carton Blanc (www.cartonblanc.com) et Stéphane Mortka pour leur soutien et leurs illustrations qui m?ont permis d?enrichir graphiquement l?ouvrage. Je remercie tous mes compagnons flasheurs qui m?ont forcément soutenu et aidé à un moment donné : Joa Ebert, Didier Brun, Jérôme Cordiez, Vincent Maitray, David Deraedt, Fréderic Saunier, Benjamin Jung. Le centre Regart.net pour m?avoir soutenu. A Nicolas pour ses opinions, à Eric pour ses bonnes blagues, et à Guylaine pour ces années chez Regart. Justin Everett-Church d?Adobe et Henri Torgemane de Yahoo pour leur aide et leur soutien. Ségolène (www.chevaldetroie.net) pour avoir relu tous ces chapitres à la recherche de fautes d?orthographes. Mes amis que je n'ai pas vus pendant quasiment 8 mois : Sébastien, Bessam, Laurent, Paul, Juan, Pascal, Bob Groove et bien sûr Stevie Wonder. Sonia pour ce dimanche 20 août 2006 où ma vie a changée. Mes parents et à ma s?ur qui m?ont soutenu pendant l?écriture. Bien entendu, je ne peux pas terminer les remerciements sans remercier tous mes stagiaires que j?ai pu rencontrer depuis plus de trois ans de formations. J?ai appris énormément chaque jour à vos côtés, vous avez participé à cet ouvrage d?une façon ou d?une autre." Préface // "Je pratique Flash depuis maintenant 10 ans et j?ai pu suivre de près toutes ses évolutions. Flash 3 a accompagné mes premiers pas dans l'univers du web. J'ai découvert un logiciel incroyablement accessible et dont l'ergonomie astucieuse, les outils d'animation et d'interactivité répondaient parfaitement aux besoins de l'époque. Quelques années plus tard, un bond de géant est franchi avec Flash MX et l'apparition de la première version robuste du langage ActionScript. Flash est alors un outil mûr et équilibré. Au même moment, sortent les premières versions de Flash Media Server et Flash Remoting qui deviennent des compléments indispensables à tout développement ambitieux. Suivant l'évolution des créations web de plus en plus dynamiques et complexes, Flash bascule, non sans mal, vers un outil de développement plus sophistiqué (Flash MX 2004), nécessitant la mise en place d'équipes spécialisées. L'époque du flasheur maitrisant tout de A à Z est révolue. Flash s'adresse désormais à deux publics distincts : graphistes d'un coté, développeurs de l'autre. L'équilibre devient alors précaire et la tendance à favoriser le code au détriment de l'accessibilité de l?outil se développe. Les réalisations du graphiste ou de l'animateur sont maintenant souvent subordonnées au travail du développeur, ce qui ne favorise pas toujours la création. Si Flash 8, grâce aux filtres et mélanges, ouvre de nouvelles voies à la création et redonne un peu de place au graphiste, c'est néanmoins, le plus souvent, le développeur qui contrôle le projet. Flash 9 et l'ActionScript 3 représentent une nouvelle étape vers la technicisation de l?outil. Connaître « l'environnement auteur » ne suffit plus, il est indispensable de comprendre et de maitriser les rouages du lecteur pour éviter la réalisation d?applications totalement instables. Etant au c?ur de la production, j'ai toujours déploré que les ouvrages Actionscript prennent si peu en compte les réalités que peuvent rencontrer une agence, un studio, une équipe ou tout professionnel dans son travail quotidien. J?apprécie particulièrement l?ouvrage de Thibault pour les exemples concrets, directement utilisables en production, qu?il nous livre. Il nous dispense justement d?explications trop virtuelles ou d?un esthétisme superflu du code. Fort de son expérience de formateur, il reste simple et clair au moyen d'un vocabulaire précis, sans jamais tomber, ni dans le perfectionnisme lexical, ni dans la vulgarisation imprécise. Cet ouvrage va bien au delà de l'apprentissage du langage ActionScript 3 en posant les bases de sa bonne utilisation dans Flash et en fournissant un éclairage neuf et pertinent sur cet outil fabuleux." Mathieu Anthoine Game Designer et co-fondateur du studio Yamago www.yamago.net trace("Pratique d'ActionScript 3"); // Pratique d?ActionScript 3 s?adresse à tous les flasheurs. Cet ouvrage dresse un panorama de l?utilisation d?ActionScript 3 et de ses nouveautés, ainsi que du nouveau lecteur Flash 9. L?auteur explique au travers de nombreux exemples et cas concrets comment traiter désormais les objets, le texte, le son et la vidéo, le chargement et l?envoi de données externes (variables, XML, etc.), les sockets, etc. Certains sujets avancés et peu abordés comme les classes bas niveau ou encore Flash Remoting sont également traités. Enfin, l?auteur livre une application complète qui reprend les différents points évoqués. // Thibault IMBERT est aujourd'hui ingénieur système chez Adobe France. Il a commencé en tant que développeur Flash pour différentes agences, avant de rejoindre Regart.net comme formateur et responsable pédagogique pour toutes les formations conçernant la plate-forme Flash. Pendant son temps libre, Thibault expérimente ActionScript 3, comme en témoigne son site www.bytearray.org. Il travaille également sur différents projets tels que WiiFlash www.wiiflash.org ou AlivePDF www.alivepdf.org Avril 2008 ? Environ 1200 pages. Pour un livre téléchargé, un platane planté. Graphisme couverture : Guillaume DURAND ? dgezeo Chapitre 1 ? Qu?est ce que l?ActionScript 3 ? - version 0.1.1 1 / 5 Thibault Imbert pratiqueactionscript3.bytearray.org 1 Qu?est ce que l?ActionScript 3 HISTORIQUE.......................................................................................................... 1 10 RAISONS DE CODER EN ACTIONSCRIPT 3 .............................................. 3 OUTILS..................................................................................................................... 4 LA PLATEFORME FLASH................................................................................... 4 Historique Expliquer comment créer un projet AS dans Flex Builder C?est en 1996 que l?aventure Flash commence lorsque la firme Macromedia rachète la petite société FutureWave auteur d?un logiciel d?animation vectoriel nommé Future Splash Animator. Chapitre 1 ? Qu?est ce que l?ActionScript 3 ? - version 0.1.1 2 / 5 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 1-1. Future Splash Animator sorti en avril 1996. Développé à l?origine pour pouvoir animer du contenu vectoriel sur Internet, Future Splash Animator intégrait déjà les bases de Flash en matière d?animation mais ne possédait à cette époque aucun langage de programmation associé. Macromedia renomma alors le logiciel sous le nom de Flash 1.0 en 1996, mais il faut attendre 1999 afin que la notion de programmation fasse officiellement son apparition avec Flash 4. Les documentations de l?époque ne parlent pas encore de langage ActionScript mais plus simplement d?actions. Grâce à celles-ci, il devient possible d?ajouter des comportements avancés aux boutons et autres objets graphiques. De nombreux graphistes à l?aise avec la programmation commencèrent à développer des comportements avancés et se mirent à échanger des scripts par le biais de forums et autres plateformes communautaires. Flash connu alors un engouement fulgurant et devint rapidement un outil d?animation avancé, capable de produire différents types de contenus interactifs comme des sites internet, des jeux, ou des applications multimédia. C?est en 2001 que le langage ActionScript fait officiellement apparition au sein de Flash 5. La notion de syntaxe pointée est intégrée au langage qui suit pour la première fois les spécifications ECMAScript. Nous reviendrons sur cette notion au cours du prochain chapitre intitulé Langage et API. Chapitre 1 ? Qu?est ce que l?ActionScript 3 ? - version 0.1.1 3 / 5 Thibault Imbert pratiqueactionscript3.bytearray.org Afin de répondre à une demande de la communauté pour un langage ActionScript plus structuré, Macromedia développe alors une nouvelle version d?ActionScript et l?intègre en 2003 au sein de Flash MX 2004 sous le nom d?ActionScript 2.0. Cette nouvelle version répond aux besoins des développeurs en offrant un langage orienté objet et non procédural comme c?était le cas en ActionScript 1.0. A l?époque, Macromedia fait le choix d?une migration en douceur, et propose un langage ActionScript 2.0 souple permettant aux développeurs et graphistes de coder au sein d?un même projet en ActionScript 1.0 et 2.0. Si cette nouvelle version du langage ne satisfait pas les développeurs puristes, elle permet néanmoins aux développeurs débutant de migrer doucement vers un développement orienté objet. Macromedia développe alors une nouvelle version 3.0 du langage ActionScript afin d?offrir des performances optimales à leur nouvelle création nommée Flex. Deux ans plus tard, la société Macromedia se voit rachetée par le géant Adobe et l?ActionScript 3 voit le jour au sein de Flash en 2007, lors de la sortie de Flash CS3. 10 raisons de coder en ActionScript 3 Si vous n?êtes pas encore convaincu de migrer vers ActionScript 3, voici 10 raisons pour ne plus hésiter : ? En décembre 2007, le lecteur Flash 9 possède un taux de pénétration de plus de 95%. Il demeure le lecteur multimédia le plus présent sur Internet. ? La vitesse d?exécution du code est environ 10 fois supérieure aux précédentes versions d?ActionScript. ? ActionScript 3 offre des possibilités incomparables aux précédentes versions d?ActionScript. ? Le code ActionScript 3 s?avère plus logique que les précédentes versions du langage. ? Le langage ActionScript 3 permet de développer du contenu en Flash, Flex, ou AIR. Nous reviendrons très vite sur ces différents frameworks. ? Il n?est pas nécessaire de connaître un autre langage orienté objet au préalable. ? ActionScript 3 est un langage orienté objet. Sa connaissance vous permettra d?aborder plus facilement d?autres langages objets tels Java, C#, ou C++. ? Le langage ActionScript 3 et l?API du lecteur Flash ont été entièrement repensés. Les développeurs ActionScript 1 et 2 seront ravis de découvrir les nouveautés du langage et la nouvelle organisation de l?API du lecteur. Chapitre 1 ? Qu?est ce que l?ActionScript 3 ? - version 0.1.1 4 / 5 Thibault Imbert pratiqueactionscript3.bytearray.org ? La technologie Flash ne cesse d?évoluer, vous ne risquez pas de vous ennuyer. ? ActionScript 3 est un langage souple, qui demeure ludique et accessible. Bien entendu, vous pouvez mémoriser ces différents points afin de convaincre vos amis au cours d?une soirée geek. ActionScript 3 ne vous sera pas d?une grande utilité sans un environnement de développement dédié. Nous allons nous attarder quelques instants sur les différents outils disponibles permettant de produire du contenu ActionScript 3. Outils Afin qu?il n?y ait pas de confusions, voici les différents outils vous permettant de coder en ActionScript 3 : ? Flash CS3 : permet le développement d?animations et d?applications en ActionScript 3. Pour plus d?informations : http://www.adobe.com/fr/products/flash/ ? Flex Builder : il s?agit d?un environnement auteur permettant le développement d?applications riches (RIA). Flex repose sur deux langages, le MXML afin de décrire les interfaces, et ActionScript pour la logique. Pour plus d?informations : http://www.adobe.com/fr/products/flex/ ? Eclipse (SDK Flex et AIR) : les SDK de Flex et AIR permettent de produire du contenu Flex et AIR gratuitement. Il s?agit de plugins pour l?environnement de développement Eclipse. Vous avez dit plateforme ? La plateforme Flash Par le terme de plateforme Flash nous entendons l?ensemble des technologies à travers lesquelles nous retrouvons le lecteur Flash. Nous pouvons diviser la plateforme Flash en trois catégories : ? Les lecteurs Flash : parmi les différents lecteurs, nous comptons le lecteur Flash, le lecteur Flash intégré à AIR, ainsi que Flash Lite (notons que Flash lite n?est pas à ce jour compatible avec ActionScript 3). ? Les outils de développement : Flex Builder et Flash CS3 sont les deux environnements auteurs permettant de produire du contenu Flash. Notons qu?il est aussi possible d?utiliser l?environnement Eclipse et les SDK de Flex et AIR. Chapitre 1 ? Qu?est ce que l?ActionScript 3 ? - version 0.1.1 5 / 5 Thibault Imbert pratiqueactionscript3.bytearray.org ? Les frameworks : Flex est un framework conçu pour faciliter le développement d?applications riches. Grâce à AIR, ces applications peuvent sortir du navigateur et être déployées sur le bureau. Chaque outil de développement ou framework répond à une attente spécifique et correspond à un profil type. Un graphiste-développeur sera intéressé par le développement d?interfaces animées et de sites Internet au sein de Flash CS3. La connaissance d?un environnement comme Flash CS3 lui permettra aussi de développer des composants réutilisables au sein de Flex et AIR. Un développeur préférera peut être un environnement de développement comme celui offert par Flex Builder. Les applications produites grâce au framework Flex seront de nature différente et pourront facilement être déployées sur le bureau grâce à AIR. Notons que les exemples présents dans cet ouvrage peuvent être utilisés au sein de Flash CS3, Flex et AIR. Afin d?entamer cette aventure au c?ur d?ActionScript 3, nous allons nous intéresser tout d?abord aux différentes nouveautés du langage. En route pour le prochain chapitre intitulé Langage et API ! Chapitre 2 ? Langage et API - version 0.1.2 1 / 35 Thibault Imbert pratiqueactionscript3.bytearray.org 2 Langage et API LE LANGAGE ACTIONSCRIPT 3.........................................................................1 MACHINES VIRTUELLES ............................................................................................4 TRADUCTION DYNAMIQUE ........................................................................................5 GESTION DES TYPES A L?EXECUTION.........................................................................6 ERREURS A L?EXECUTION........................................................................................11 NOUVEAUX TYPES PRIMITIFS ..................................................................................13 VALEURS PAR DEFAUT ............................................................................................18 NOUVEAUX TYPES COMPOSITES..............................................................................20 NOUVEAUX MOTS-CLES ..........................................................................................20 FONCTIONS .............................................................................................................21 CONTEXTE D?EXECUTION........................................................................................24 BOUCLES.................................................................................................................25 ENRICHISSEMENT DE LA CLASSE ARRAY.................................................................26 RAMASSE-MIETTES .................................................................................................29 BONNES PRATIQUES ................................................................................................32 AUTRES SUBTILITES ................................................................................................33 Le langage ActionScript 3 Le langage ActionScript 3 intègre de nombreuses nouveautés que nous allons traiter tout au long de cet ouvrage. Ce chapitre va nous permettre de découvrir les nouvelles fonctionnalités et comportements essentiels à tout développeur ActionScript 3. Avant de détailler les nouveautés liées au langage ActionScript 3, il convient de définir tout d?abord ce que nous entendons par le terme ActionScript. De manière générale, le terme ActionScript englobe deux composantes importantes : Chapitre 2 ? Langage et API - version 0.1.2 2 / 35 Thibault Imbert pratiqueactionscript3.bytearray.org ? Le c?ur du langage : il s?agit du langage ActionScript basé sur la spécification ECMAScript (ECMA-262) et intègre partiellement certaines fonctionnalités issues de la spécification ECMAScript 4. ? L?API du lecteur Flash : il s?agit des fonctionnalités du lecteur Flash. Toutes les classes nécessitant d?être importées font partie de l?API du lecteur et non du c?ur du langage ActionScript. Ainsi, l?interface de programmation du lecteur ou le langage peuvent être mise à jour indépendamment. Le lecteur Flash 10 devrait normalement intégrer une gestion de la 3D native ainsi qu?une implémentation plus complète de la spécification ECMAScript 4. La gestion de la 3D concerne ici uniquement l?interface de programmation du lecteur Flash, à l?inverse les nouveaux objets définis par la spécification ECMAScript 4 sont directement liés au c?ur du langage ActionScript. D?un côté réside le langage ActionScript 3, de l?autre l?API du lecteur appelée généralement interface de programmation. La figure 2-1 illustre les deux entités : Figure 2-1. Langage ActionScript 3. Contrairement aux précédentes versions d?ActionScript, nous remarquons qu?en ActionScript 3, les différentes fonctionnalités du lecteur Flash sont désormais stockées dans des paquetages spécifiques. Chapitre 2 ? Langage et API - version 0.1.2 3 / 35 Thibault Imbert pratiqueactionscript3.bytearray.org Afin d?afficher une vidéo nous utiliserons les objets issus du paquetage flash.media. A l?inverse, pour nous connecter à un serveur, nous utiliserons les objets issus du paquetage flash.net. Flash CS3 est configuré afin d?importer automatiquement toutes les classes issues de l?API du lecteur Flash. Il n?est donc pas nécessaire d?importer manuellement les classes lorsque nous codons au sein de l?environnement auteur. Un fichier implicitImports.xml situé au sein du répertoire d?installation de Flash CS3 (C:\Program Files\Adobe\Adobe Flash CS3\fr\Configuration\ActionScript 3.0) contient toutes les définitions de classe à importer : <implicitImportsList> <implicitImport name = "flash.accessibility.*"/> <implicitImport name = "flash.display.*"/> <implicitImport name = "flash.errors.*"/> <implicitImport name = "flash.events.*"/> <implicitImport name = "flash.external.*"/> <implicitImport name = "flash.filters.*"/> <implicitImport name = "flash.geom.*"/> <implicitImport name = "flash.media.*"/> <implicitImport name = "flash.net.*"/> <implicitImport name = "flash.printing.*"/> <implicitImport name = "flash.system.*"/> <implicitImport name = "flash.text.*"/> <implicitImport name = "flash.ui.*"/> <implicitImport name = "flash.utils.*"/> <implicitImport name = "flash.xml.*"/> </implicitImportsList> Afin de créer un clip dynamiquement nous pouvons écrire directement sur une image du scénario : var monClip:MovieClip = new MovieClip(); Si nous plaçons notre code à l?extérieur de Flash au sein de classes, nous devons explicitement importer les classes nécessaires : import flash.display.MovieClip; var monClip:MovieClip = new MovieClip(); Dans cet ouvrage nous n?importerons pas les classes du lecteur lorsque nous programmerons dans l?environnement auteur de Flash. A l?inverse dès l?introduction des classes au sein du chapitre 8 intitulé Programmation orientée objet, nous importerons explicitement les classes utilisées. A retenir Chapitre 2 ? Langage et API - version 0.1.2 4 / 35 Thibault Imbert pratiqueactionscript3.bytearray.org ? Le langage ActionScript 3 englobe deux composantes : le c?ur du langage ActionScript et l?interface de programmation du lecteur Flash. ? Le c?ur du langage est défini par la spécification ECMAScript. Machines virtuelles Le code ActionScript est interprété par une partie du lecteur Flash appelée machine virtuelle. C?est cette dernière qui se charge de retranscrire en langage machine le code binaire (ActionScript byte code) généré par le compilateur. Les précédentes versions du lecteur Flash intégraient une seule machine virtuelle appelée AVM1 afin d?interpréter le code ActionScript 1 et 2. En réalité, le code binaire généré par le compilateur en ActionScript 1 et 2 était le même, c?est la raison pour laquelle nous pouvions faire cohabiter au sein d?un même projet ces deux versions du langage ActionScript. La figure 2-2 illustre la machine virtuelle 1 (AVM1) présente dans le lecteur Flash 8 : Figure 2-2. AVM1 au sein du lecteur Flash 8. Le langage ActionScript 3 n?est pas compatible avec cette première machine virtuelle, pour des raisons évidentes de rétrocompatibilité, le lecteur Flash 9 embarque donc deux machines virtuelles. Lors de la lecture d?un SWF, le lecteur sélectionne automatiquement la machine virtuelle appropriée afin d?interpréter le code ActionScript présent au sein du SWF. Ainsi, une application ActionScript 1 et 2 sera interprétée au sein du lecteur Flash 9 par la machine virtuelle 1 (AVM1) et ne bénéficiera d?aucune optimisation des performances. Chapitre 2 ? Langage et API - version 0.1.2 5 / 35 Thibault Imbert pratiqueactionscript3.bytearray.org La figure 2-3 présente les deux machines virtuelles au sein du lecteur Flash 9 : Figure 2-3. Le lecteur Flash 9 et les deux machines virtuelles AVM1 et AVM2. Seules les animations compilées en ActionScript 3 pourront bénéficier des optimisations réalisées par la nouvelle machine virtuelle (AVM2). A retenir ? Les précédentes versions du lecteur Flash intégraient une seule machine virtuelle afin d?interpréter le code ActionScript 1 et 2. ? La machine virtuelle 1 (AVM1) interprète le code ActionScript 1 et 2. ? La machine virtuelle 2 (AVM2) interprète seulement le code ActionScript 3. ? Le lecteur Flash intègre les deux machines virtuelles (AVM1 et AVM2). ? Le langage ActionScript 3 ne peut pas cohabiter avec les précédentes versions d?ActionScript. Traduction dynamique Afin d?optimiser les performances, la machine virtuelle 2 (AVM2) du lecteur Flash 9 intègre un mécanisme innovant de compilation du code à la volée. Bien que le terme puisse paraître étonnant, ce principe appelé généralement traduction dynamique permet d?obtenir de meilleures performances d?exécution du code en compilant ce dernier à l?exécution. Dans les précédentes versions du lecteur Flash, le code présent au sein du SWF était directement retranscrit par la machine virtuelle en Chapitre 2 ? Langage et API - version 0.1.2 6 / 35 Thibault Imbert pratiqueactionscript3.bytearray.org langage machine sans aucune optimisation liée à la plateforme en cours. En ActionScript 3 la machine virtuelle retranscrit le code binaire (ActionScript byte code) en langage machine à l?aide d?un compilateur à la volée appelé couramment compilateur à la volée. (Just-in-time compiler). Ce dernier permet de compiler uniquement le code utilisé et de manière optimisée selon la plateforme en cours. La machine virtuelle peut donc optimiser les instructions pour un processeur spécifique tout en prenant en considération les différentes contraintes de la plateforme. Pour plus d?informations liées à la compilation à l?exécution, rendez vous à l?adresse suivante : http://en.wikipedia.org/wiki/Just-in-time_compilation http://fr.wikipedia.org/wiki/Compilation_%C3%A0_la_vol%C3%A9e Gestion des types à l?exécution ActionScript 2 introduit au sein de Flash MX 2004 la notion de typage fort. Cela consistait à associer un type de données à une variable à l?aide de la syntaxe suivante : variable:Type Dans le code suivant, nous tentions d?affecter une chaîne à une variable de type Number : var distance:Number = "150"; L?erreur suivante était générée à la compilation : Incompatibilité de types dans l'instruction d'affectation : String détecté au lieu de Number. En ActionScript 3, nous bénéficions du même mécanisme de vérification des types à la compilation. En compilant le même code en ActionScript 3, l?erreur suivante est générée : 1067: Contrainte implicite d'une valeur du type String vers un type sans rapport Number. Ce comportement est appelé Mode précis dans Flash CS3 et peut être désactivé par l?intermédiaire du panneau Paramètres d?ActionScript 3.0. A travers le panneau Paramètres de publication, puis de l?onglet Flash, nous cliquons sur le bouton Paramètres. Nous obtenons un panneau Paramètres d?ActionScript 3 contenant deux options liées aux erreurs comme l?illustre la figure 2-4 : Chapitre 2 ? Langage et API - version 0.1.2 7 / 35 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 2-4. Options du compilateur ActionScript 3. Nous remarquons que par défaut, le Mode précis est activé, nous reviendrons très vite sur le Mode avertissements. En décochant la case Mode précis, nous désactivons la vérification des types à la compilation afin de découvrir un comportement extrêmement important apporté par ActionScript 3. En testant le code suivant en mode non précis, nous remarquons qu?aucune erreur de compilation n?est générée : var distance:Number = "150"; A l?exécution, la machine virtuelle 2 (AVM2) convertit automatiquement la chaîne de caractères 150 en un nombre entier de type int. Afin de vérifier cette conversion automatique, nous pouvons utiliser la fonction describeType du paquetage flash.utils : var distance:Number = "150"; /* affiche : <type name="int" base="Object" isDynamic="false" isFinal="true" isStatic="false"> <extendsClass type="Object"/> <constructor> <parameter index="1" type="*" optional="true"/> </constructor> </type> */ trace( describeType ( distance ) ); La fonction describeType renvoie un objet XML décrivant le type de la variable. Nous pouvons remarquer que l?attribut name du n?ud type renvoie int. En modifiant la chaîne de caractères nous obtenons une conversion automatique vers le type Number : var distance:Number = "150.5"; /* affiche : <type name="Number" base="Object" isDynamic="false" isFinal="true" isStatic="false"> <extendsClass type="Object"/> <constructor> <parameter index="1" type="*" optional="true"/> </constructor> </type> */ Chapitre 2 ? Langage et API - version 0.1.2 8 / 35 Thibault Imbert pratiqueactionscript3.bytearray.org trace( describeType ( distance ) ); Si nous tentons d?affecter un autre type de données à celle-ci, la machine virtuelle 2 (AVM2) conserve le type Number et convertit implicitement les données à l?exécution. Contrairement au mode précis, ce comportement de vérification des types à l?exécution ne peut pas être désactivé. Nous pourrions ainsi en conclure de toujours conserver le mode précis afin de ne pas être surpris par ce comportement, mais certaines erreurs de types ne peuvent être détectées par le compilateur car celles-ci n?interviennent qu?à l?exécution. Dans le code suivant, le compilateur ne détecte aucune erreur : var tableauDonnees:Array = [ "150", "250" ]; // l'entrée du tableau est automatiquement convertie en int var distance:Number = tableauDonnees[0]; A l?exécution, la chaîne de caractères présente à l?index 0 est automatiquement convertie en int. Cette conversion reste silencieuse tant que celle-ci réussit, le cas échéant une erreur à l?exécution est levée. Dans le code suivant, nous tentons de stocker une chaîne de caractères au sein d?une variable de type MovieClip : var tableauDonnees:Array = [ "clip", "250" ]; // lève une erreur à l'exécution var clip:MovieClip = tableauDonnees[0]; A l?exécution, la machine virtuelle 2 (AVM2) tente de convertir la chaîne en MovieClip et échoue, l?erreur à l?exécution suivante est levée : TypeError: Error #1034: Echec de la contrainte de type : conversion de "clip" en flash.display.MovieClip impossible. Nous pouvons alors nous interroger sur l?intérêt d?un tel comportement, pourquoi la machine virtuelle s?évertue-t-elle à conserver les types à l?exécution et convertit automatiquement les données ? Afin de garantir des performances optimales, la machine virtuelle 2 (AVM2) s?appuie sur les types définis par le développeur. Ainsi, lorsque nous typons une variable, l?occupation mémoire est optimisée spécifiquement pour ce type, ainsi que les instructions processeur. Chapitre 2 ? Langage et API - version 0.1.2 9 / 35 Thibault Imbert pratiqueactionscript3.bytearray.org Il ne faut donc pas considérer ce comportement comme un désagrément mais comme un avantage contribuant à de meilleures performances. Ce comportement diffère d?ActionScript 2, où la machine virtuelle 1 (AVM1) évaluait dynamiquement tous les types à l?exécution, aucune optimisation n?était réalisée. Le typage des variables n?était qu?une aide à la compilation. La grande nouveauté liée à ActionScript 3 réside donc dans l?intérêt du typage à la compilation comme à l?exécution. En associant un type à une variable en ActionScript 3 nous bénéficions d?une vérification des types à la compilation et d?une optimisation des calculs réalisés par le processeur et d?une meilleure optimisation mémoire. Il est donc primordial de toujours typer nos variables en ActionScript 3, les performances en dépendent très nettement. Nous typerons systématiquement nos variables durant l?ensemble de l?ouvrage. Voici un exemple permettant de justifier cette décision : Une simple boucle utilise une variable d?incrémentation i de type int : var debut:Number = getTimer(); for ( var i:int = 0; i< 500000; i++ ) { } // affiche : 5 trace( getTimer() - debut ); La boucle nécessite 5 millisecondes pour effectuer 500 000 itérations. Sans typage de la variable i, la boucle suivante nécessite 14 fois plus de temps à s?exécuter : var debut:Number = getTimer(); for ( var i = 0; i< 500000; i++ ) { } // affiche : 72 trace( getTimer() - debut ); Chapitre 2 ? Langage et API - version 0.1.2 10 / 35 Thibault Imbert pratiqueactionscript3.bytearray.org Dans le code suivant, la variable prenom ne possède pas de type spécifique, la machine virtuelle doit évaluer elle-même le type ce qui ralentit le temps d?exécution : var debut:Number = getTimer(); var prenom = "Bobby"; var prenomRaccourci:String; for ( var i:int = 0; i< 500000; i++ ) { prenomRaccourci = prenom.substr ( 0, 3 ); } // affiche : 430 trace( getTimer() - debut ); // affiche : Bob trace ( prenomRaccourci ); En typant simplement la variable prenom nous divisons le temps d?exécution de presque deux fois : var debut:Number = getTimer(); var prenom:String = "Bobby"; var prenomRaccourci:String; for ( var i:int = 0; i< 500000; i++ ) { prenomRaccourci = prenom.substr ( 0, 3 ); } // affiche : 232 trace( getTimer() - debut ); // affiche : Bob trace ( prenomRaccourci ); Au sein du panneau Paramètres ActionScript 3, nous pouvons apercevoir un deuxième mode de compilation appelé Mode avertissements. Ce dernier permet d?indiquer plusieurs types d?erreurs comme par exemple les erreurs liées à la migration de code. Supposons que nous tentions d?utiliser la méthode attachMovie dans un projet ActionScript 3 : var ref:MovieClip = this.attachMovie ("clip", "monClip", 0); Au lieu d?indiquer un simple message d?erreur, le compilateur nous renseigne que notre code n?est pas compatible avec ActionScript 3 et nous propose son équivalent. Chapitre 2 ? Langage et API - version 0.1.2 11 / 35 Thibault Imbert pratiqueactionscript3.bytearray.org Le code précédent génère donc le message d?erreur suivant à la compilation : Warning: 1060: Problème de migration : la méthode 'attachMovie' n'est plus prise en charge. Si le nom de la sous-classe de MovieClip est A, utilisez var mc= new A(); addChild(mc). Pour plus d'informations, consultez la classe DisplayObjectContainer. Le Mode avertissements permet d?avertir le développeur de certains comportements à l?exécution risquant de le prendre au dépourvu. Attention, les avertissements n?empêchent ni la compilation du code ni son exécution, mais avertissent simplement que le résultat de l?exécution risque de ne pas être celui attendu. Dans le code suivant, un développeur tente de stocker une chaîne de caractère au sein d?une variable de type Boolean : var prenom:Boolean = "Bobby"; A la compilation, l?avertissement suivant est affiché : Warning: 3590: String utilisée alors qu'une valeur booléenne est attendue. L'expression va être transtypée comme booléenne. Il est donc fortement conseillé de conserver le Mode précis ainsi que le mode avertissements afin d?intercepter un maximum d?erreurs à la compilation. Comme nous l?avons vu précédemment, le lecteur Flash 9 n?échoue plus en silence et lève des erreurs à l?exécution. Nous allons nous intéresser à ce nouveau comportement dans la partie suivante. A retenir ? Il est possible de désactiver la vérification de type à la compilation. ? Il n?est pas possible de désactiver la vérification de type à l?exécution. ? Le typage en ActionScript 2 se limitait à une aide à la compilation. ? Le typage en ActionScript 3 aide à la compilation et à l?exécution. ? Dans un souci d?optimisation des performances, il est fortement recommandé de typer les variables en ActionScript 3. ? Il est fortement conseillé de conserver le mode précis ainsi que le mode avertissements afin d?intercepter un maximum d?erreurs à la compilation. Erreurs à l?exécution Nous avons traité précédemment des erreurs de compilation à l?aide du mode précis et abordé la notion d?erreurs à l?exécution. Chapitre 2 ? Langage et API - version 0.1.2 12 / 35 Thibault Imbert pratiqueactionscript3.bytearray.org Une des grandes nouveautés du lecteur Flash 9 réside dans la gestion des erreurs. Souvenez-vous, les précédentes versions du lecteur Flash ne levaient aucune erreur à l?exécution et échouaient en silence. En ActionScript 3 lorsque l?exécution du programme est interrompue de manière anormale, on dit qu?une erreur d?exécution est levée. Afin de générer une erreur à l?exécution nous pouvons tester le code suivant : // définition d'une variable de type MovieClip // sa valeur par défaut est null var monClip:MovieClip; // nous tentons de récupérer le nombre d'images du scénario du clip // il vaut null, une erreur d' exécution est levée var nbImages:int = monClip.totalFrames; La fenêtre de sortie affiche l?erreur suivante : TypeError: Error #1009: Il est impossible d'accéder à la propriété ou à la méthode d'une référence d'objet nul. Afin de gérer cette erreur, nous pouvons utiliser un bloc try catch : // définition d'une variable de type MovieClip // sa valeur par défaut est null var monClip:MovieClip; var nbImages:int; // grâce au bloc try catch nous pouvons gérer l'erreur try { nbImages = monClip.totalFrames; } catch ( pErreur:Error ) { trace ( "une erreur d'exécution a été levée !"); } Bien entendu, nous n?utiliserons pas systématiquement les blocs try catch afin d?éviter d?afficher les erreurs à l?exécution. Certains tests simples, que nous découvrirons au cours de l?ouvrage, nous permettrons quelque fois d?éviter d?avoir recours à ces blocs. Dans un contexte d?erreurs à l?exécution, il convient de définir les deux déclinaisons du lecteur Flash existantes : ? Version de débogage (Player Debug) : cette version du lecteur est destinée au développement et affiche les erreurs à l?exécution en ouvrant une fenêtre spécifique indiquant l?erreur en cours. Ce lecteur est installé Chapitre 2 ? Langage et API - version 0.1.2 13 / 35 Thibault Imbert pratiqueactionscript3.bytearray.org automatiquement lors de l?installation de l?environnement de développement Flash CS3 ou Flex Builder 2 et 3. ? Version production (Player Release) : cette version du lecteur est disponible depuis le site d?Adobe. Les personnes n?ayant pas d?environnement de développement installé utilisent cette version du lecteur. Ce lecteur n?affiche pas les erreurs à l?exécution afin de ne pas interrompre l?expérience de l?utilisateur. Avec le lecteur de débogage, les erreurs non gérées par un bloc try catch ouvrent un panneau d?erreur au sein du navigateur comme l?illustre la figure 2-5 : Figure 2-5. Exemple d?erreur à l?exécution. Lorsqu?une erreur est levée, l?exécution du code est alors mise en pause. Nous pouvons alors décider de continuer l?exécution du code bien qu?une erreur vienne d?être levée ou bien de stopper totalement l?exécution de l?application. A retenir ? Le lecteur Flash 9 lève des erreurs à l?exécution. ? Ces erreurs ouvrent avec le lecteur de débogage une fenêtre indiquant l?erreur au sein du navigateur. ? Toutes les méthodes de l?API du lecteur en ActionScript 3 peuvent lever des erreurs à l?exécution. ? Le lecteur Flash 9 n?échoue plus en silence, le débogage est donc facilité. Nouveaux types primitifs En ActionScript 2, seul le type Number existait afin de définir un nombre, ainsi pour stocker un nombre entier ou décimal le type Number était utilisé : var age:Number = 20; var vitesse:Number = 12.8; Chapitre 2 ? Langage et API - version 0.1.2 14 / 35 Thibault Imbert pratiqueactionscript3.bytearray.org Aucune distinction n?était faite entre les nombres entiers, décimaux et non négatifs. ActionScript 3 intègre désormais trois types afin de représenter les nombres : ? int : représente un nombre entier 32 bit (32 bit signed integer) ? uint : représente un nombre entier non signé 32 bit. (32 bit unsigned integer) ? Number : représente un nombre décimal 64 bit (64-bit IEEE 754 double-precision floating-point number) Notons que les deux nouveaux types int et uint ne prennent pas de majuscule, contrairement au type Number déjà présent au sein d?ActionScript 2. Une variable de type int peut contenir un nombre oscillant entre -2147483648 et 2147483648 : // affiche : -2147483648 trace( int.MIN_VALUE ); // affiche : 2147483648 trace( int.MAX_VALUE ); Une variable de type uint peut contenir un nombre entier oscillant entre 0 et 4294967295 : // affiche : 0 trace( uint.MIN_VALUE ); // affiche : 4294967295 trace( uint.MAX_VALUE ); Attention, la machine virtuelle ActionScript 3 conserve les types à l?exécution, si nous tentons de stocker un nombre à virgule flottante au sein d?une variable de type int ou uint, le nombre est automatiquement converti en entier par la machine virtuelle : var age:int = 22.2; // affiche : 22 trace ( age ); Notons, que la machine virtuelle arrondi à l?entier inférieur : var age:int = 22.8; // affiche : 22 trace ( age ); Cette conversion automatique assurée par la machine virtuelle s?avère beaucoup plus rapide que la méthode floor de la classe Math. Chapitre 2 ? Langage et API - version 0.1.2 15 / 35 Thibault Imbert pratiqueactionscript3.bytearray.org Dans le code suivant, nous arrondissons l?entier au sein d?une boucle à l?aide de la méthode floor, la boucle nécessite 111 millisecondes : var distance:Number = 45.2; var arrondi:Number; var debut:Number = getTimer(); for ( var i:int = 0; i< 500000; i++ ) { arrondi = Math.floor ( distance ); } // affiche : 111 trace( getTimer() - debut ); A présent, nous laissons la machine virtuelle gérer pour nous l?arrondi du nombre : var distance:Number = 45.2; var arrondi:int; var debut:Number = getTimer(); for ( var i:int = 0; i< 500000; i++ ) { arrondi = distance; } // affiche : 8 trace( getTimer() - debut ); // affiche : 45 trace( arrondi ); Nous obtenons le même résultat en 8 millisecondes, soit un temps d?exécution presque 14 fois plus rapide. Attention, cette astuce n?est valable uniquement dans le cas de nombres positifs. Dans le code suivant, nous remarquons que la méthode floor de la classe Math ne renvoie pas la même valeur que la conversion en int par la machine virtuelle : var distance:int = -3.2; // affiche : -3 trace(distance); var profondeur:Number = Math.floor (-3.2); // affiche : -4 Chapitre 2 ? Langage et API - version 0.1.2 16 / 35 Thibault Imbert pratiqueactionscript3.bytearray.org trace( profondeur ); Partant du principe qu?une distance est toujours positive, nous pouvons utiliser le type uint qui offre dans ce cas précis des performances similaires au type int : var arrondi:uint; Malheureusement, le type uint s?avère généralement beaucoup plus lent, dès lors qu?une opération mathématique est effectuée. En revanche, le type Number s?avère plus rapide que le type int lors de division. Lors de la définition d?une boucle, il convient de toujours préférer l?utilisation d?une variable d?incrémentation de type int : var debut:Number = getTimer(); for ( var i:int = 0; i< 5000000; i++ ) { } // affiche : 61 trace( getTimer() - debut ); A l?inverse, si nous utilisons un type uint, les performances chutent de presque 400% : var debut:Number = getTimer(); for ( var i:uint = 0; i< 5000000; i++ ) { } // affiche : 238 trace( getTimer() - debut ); Gardez à l?esprit, qu?en cas d?hésitation, il est préférable d?utiliser le type Number : var debut:Number = getTimer(); for ( var i:Number = 0; i< 5000000; i++ ) { } // affiche : 102 trace( getTimer() - debut ); Chapitre 2 ? Langage et API - version 0.1.2 17 / 35 Thibault Imbert pratiqueactionscript3.bytearray.org Nous obtenons ainsi un compromis en termes de performances entre le type int et uint. De manière générale, il est préférable d?éviter le type uint. La même optimisation peut être obtenue pour calculer l?arrondi supérieur. Nous préférons laisser la machine virtuelle convertir à l?entier inférieur puis nous ajoutons 1 : var distance:Number = 45.2; var arrondi:int; var debut:Number = getTimer(); for ( var i:int = 0; i< 500000; i++ ) { arrondi = distance + 1; } // affiche : 12 trace( getTimer() - debut ); // affiche : 46 trace( arrondi ); En utilisant la méthode ceil de la classe Math, nous ralentissons les performances d?environ 300% : var distance:Number = 45.2; var arrondi:Number; var debut:Number = getTimer(); for ( var i:int = 0; i< 500000; i++ ) { arrondi = Math.ceil ( distance ); } // affiche : 264 trace( getTimer() - debut ); // affiche : 46 trace( arrondi ); Pour plus d?astuces liées à l?optimisation, rendez-vous à l?adresse suivante : http://lab.polygonal.de/2007/05/10/bitwise-gems-fast-integer-math/ A retenir Chapitre 2 ? Langage et API - version 0.1.2 18 / 35 Thibault Imbert pratiqueactionscript3.bytearray.org ? Le type int permet de représenter un nombre entier 32 bit. ? Le type uint permet de représenter un nombre entier 32 bit non négatif. ? Le type Number permet de représenter un nombre décimal 64 bit. ? Il est conseillé d?utiliser le type int pour les nombres entiers, son utilisation permet d?optimiser les performances. ? Il est déconseillé d?utiliser le type uint. ? En cas d?hésitation, il convient d?utiliser le type Number. Valeurs par défaut Il est important de noter que les valeurs undefined et null ont un comportement différent en ActionScript 3. Désormais, une variable renvoie undefined uniquement lorsque celle-ci n?existe pas où lorsque nous ne la typons pas : var prenom; // affiche : undefined trace( prenom ); Lorsqu?une variable est typée mais ne possède aucune valeur, une valeur par défaut lui est attribuée : var condition:Boolean; var total:int; var couleur:uint; var resultat:Number; var personnage:Object; var prenom:String; var donnees:*; // affiche : false trace( condition ); // affiche : 0 trace( total ); // affiche : 0 trace( couleur ); // affiche : NaN trace( resultat ); // affiche : null trace( personnage ); // affiche : null trace( prenom ); // affiche : undefined trace( donnees ); Le tableau suivant illustre les différentes valeurs attribuées par défaut aux types de données : Chapitre 2 ? Langage et API - version 0.1.2 19 / 35 Thibault Imbert pratiqueactionscript3.bytearray.org Type de données Valeur par défaut Boolean false int 0 Number NaN Object null String null uint 0 Non typée (équivalent au type *) undefined Autres types null Tableau 1. Valeurs par défaut associées aux types de données. De la même manière, si nous tentons d?accéder à une propriété inexistante au sein d?une instance de classe non dynamique telle String, nous obtenons une erreur à la compilation : var prenom:String = "Bob"; // génère une erreur à la compilation trace( prenom.proprieteInexistante ); Si nous tentons d?accéder à une propriété inexistante, au sein d?une instance de classe dynamique, le compilateur ne procède à aucune vérification et nous obtenons la valeur undefined pour la propriété ciblée : var objet:Object = new Object(); // affiche : undefined trace( objet.proprieteInexistante ); Attention, une exception demeure pour les nombres, qui ne peuvent être null ou undefined. Si nous typons une variable avec le type Number, la valeur par défaut est NaN : var distance:Number; // affiche : NaN trace( distance ); Chapitre 2 ? Langage et API - version 0.1.2 20 / 35 Thibault Imbert pratiqueactionscript3.bytearray.org En utilisant le type int ou uint, les variables sont automatiquement initialisées à 0 : var distance:int; var autreDistance:uint; // affiche : 0 trace( distance ); // affiche : 0 trace( autreDistance ); A retenir ? Une variable renvoie undefined uniquement lorsque celle-ci n?existe pas ou n?est pas typée. ? Lorsqu?une variable est typée mais ne possède aucune valeur, la machine virtuelle attribue automatiquement une valeur par défaut. Nouveaux types composites Deux nouveaux types composites sont intégrés en ActionScript 3 : ? Les expressions régulières (RegExp) : elles permettent d?effectuer des recherches complexes sur des chaînes de caractères. Nous reviendrons sur les expressions régulières au cours de certains exercices. ? E4X (ECMAScript 4 XML) : la spécification ECMAScript 4 intègre un objet XML en tant qu?objet natif. Nous reviendrons sur le format XML et E4X au cours de certains exercices. Nouveaux mots-clés Le mot clé is introduit par ActionScript 3 remplace l?ancien mot-clé instanceof des précédentes versions d?ActionScript. Ainsi pour tester si une variable est d?un type spécifique nous utilisons le mot-clé is : var tableauDonnees:Array = [5654, 95, 54, 687968, 97851]; // affiche : true trace( tableauDonnees is Array ); // affiche : true trace( tableauDonnees is Object ); // affiche : false trace( tableauDonnees is MovieClip ); Un autre mot-clé nommé as fait aussi son apparition. Ce dernier permet de transtyper un objet vers un type spécifique. Chapitre 2 ? Langage et API - version 0.1.2 21 / 35 Thibault Imbert pratiqueactionscript3.bytearray.org Dans le code suivant, nous définissons une variable de type DisplayObject, mais celle-ci contient en réalité une instance de MovieClip : var monClip:DisplayObject = new MovieClip(); Si nous tentons d?appeler une méthode de la classe MovieClip sur la variable monClip, une erreur à la compilation est générée. Afin de pouvoir appeler la méthode sans que le compilateur ne nous bloque, nous pouvons transtyper vers le type MovieClip : // transtypage en MovieClip (monClip as MovieClip).gotoAndStop(2); En cas d?échec du transtypage, le résultat du transtypage renvoie null, nous pouvons donc tester si le transtypage réussit de la manière suivante : var monClip:DisplayObject = new MovieClip(); // affiche : true trace( MovieClip(monClip) != null ); Nous aurions pu transtyper avec l?écriture traditionnelle suivante : var monClip:DisplayObject = new MovieClip(); // transtypage en MovieClip MovieClip(monClip).gotoAndStop(2); En termes de performances, le mot clé as s?avère presque deux fois plus rapide. En cas d?échec lors du transtypage l?écriture précédente ne renvoie pas null mais lève une erreur à l?exécution. Fonctions ActionScript 3 intègre de nouvelles fonctionnalités liées à la définition de fonctions. Nous pouvons désormais définir des paramètres par défaut pour les fonctions. Prenons le cas d?une fonction affichant un message personnalisé : function alerte ( pMessage:String ):void { trace( pMessage ); } Cette fonction alerte accepte un paramètre accueillant le message à afficher. Si nous souhaitons l?exécuter nous devons obligatoirement passer un message : alerte ("voici un message d'alerte !"); Chapitre 2 ? Langage et API - version 0.1.2 22 / 35 Thibault Imbert pratiqueactionscript3.bytearray.org Si nous omettons le paramètre : // génère une erreur à la compilation alerte (); L?erreur à la compilation suivante est générée : 1136: Nombre d'arguments incorrect. 1 attendus. ActionScript 3 permet de définir une valeur par défaut pour le paramètre : function alerte ( pMessage:String="message par défaut" ):void { trace( pMessage ); } Une fois définie, nous pouvons appeler la fonction alerte sans passer de paramètres : function alerte ( pMessage:String="message par défaut" ):void { trace( pMessage ); } // affiche : message par défaut alerte (); Lorsque nous passons un paramètre spécifique, celui-ci écrase la valeur par défaut : function alerte ( pMessage:String="message par défaut" ):void { trace( pMessage ); } // affiche : un message personnalisé ! alerte ( "un message personnalisé !" ); En plus de cela, ActionScript 3 intègre un nouveau mécanisme lié aux paramètres aléatoires. Imaginons que nous devions créer une fonction pouvant accueillir un nombre aléatoire de paramètres. En ActionScript 1 et 2, nous ne pouvions l?indiquer au sein de la signature de la fonction. Nous définissions donc une fonction sans paramètre, puis nous utilisions le tableau arguments regroupant l?ensemble des paramètres : Chapitre 2 ? Langage et API - version 0.1.2 23 / 35 Thibault Imbert pratiqueactionscript3.bytearray.org function calculMoyenne ():Number { var lng:Number = arguments.length; var total:Number = 0; for (var i:Number = 0; i< lng; i++) { total += arguments[i]; } return total / lng; } var moyenne:Number = calculMoyenne ( 50, 48, 78, 20, 90 ); // affiche : 57.2 trace( moyenne ); Bien que cette écriture puisse paraître très souple, elle posait néanmoins un problème de relecture du code. En relisant la signature de la fonction, un développeur pouvait penser que la fonction calculMoyenne n?acceptait aucun paramètre, alors que ce n?était pas le cas. Afin de résoudre cette ambiguïté, ActionScript 3 introduit un mot-clé permettant de spécifier dans les paramètres que la fonction en cours reçoit un nombre variable de paramètres. Pour cela nous ajoutons trois points de suspensions en tant que paramètre de la fonction, suivi d?un nom de variable de notre choix. Le même code s?écrit donc de la manière suivante en ActionScript 3 : function calculMoyenne ( ...parametres ):Number { var lng:int = parametres.length; var total:Number = 0; for (var i:Number = 0; i< lng; i++) { total += parametres[i]; } return total / lng; } var moyenne:Number = calculMoyenne ( 50, 48, 78, 20, 90 ); Chapitre 2 ? Langage et API - version 0.1.2 24 / 35 Thibault Imbert pratiqueactionscript3.bytearray.org // affiche : 57.2 trace( moyenne ); En relisant le code précédent, le développeur ActionScript 3 peut facilement détecter les fonctions accueillant un nombre de paramètres aléatoires. Contexte d?exécution Afin que vous ne soyez pas surpris, il convient de s?attarder quelques instants sur le nouveau comportement des fonctions passées en référence. Souvenez-vous, en ActionScript 1 et 2, nous pouvions passer en référence une fonction, celle-ci perdait alors son contexte d?origine et épousait comme contexte le nouvel objet : var personnage:Object = { age : 25, nom : "Bobby" }; // la fonction parler est passée en référence personnage.parler = parler; function parler ( ) { trace("bonjour, je m'appelle " + this.nom + ", j'ai " + this.age + " ans"); } // appel de la méthode // affiche : bonjour, je m'appelle Bobby, j'ai 25 ans personnage.parler(); En ActionScript 3, la fonction parler conserve son contexte d?origine et ne s?exécute donc plus dans le contexte de l?objet personnage : var personnage:Object = { age : 25, nom : "Bobby" }; // la fonction parler est passée en référence personnage.parler = parler; function parler ( ) { trace("bonjour, je m'appelle " + this.nom + ", j'ai " + this.age + " ans"); } // appel de la méthode // affiche : bonjour, je m'appelle undefined, j'ai undefined ans personnage.parler(); De nombreux développeurs ActionScript se basaient sur ce changement de contexte afin de réutiliser des fonctions. Chapitre 2 ? Langage et API - version 0.1.2 25 / 35 Thibault Imbert pratiqueactionscript3.bytearray.org Nous devrons donc garder à l?esprit ce nouveau comportement apporté par ActionScript 3 durant l?ensemble de l?ouvrage. Boucles ActionScript 3 introduit une nouvelle boucle permettant d?itérer au sein des propriétés d?un objet et d?accéder directement au contenu de chacune d?entre elles. Dans le code suivant, nous affichons les propriétés de l?objet personnage à l?aide d?une boucle for in : var personnage:Object = { prenom : "Bobby", age : 50 }; for (var p:String in personnage) { /* affiche : age prenom */ trace( p ); } Attention, l?ordre d?énumération des propriétés peut changer selon les machines. Il est donc essentiel de ne pas se baser sur l?ordre d?énumération des propriétés. Notons que la boucle for in en ActionScript 3 ne boucle plus de la dernière entrée à la première comme c?était le cas en ActionScript 1 et 2, mais de la première à la dernière. Nous pouvons donc désormais utiliser la boucle for in afin d?itérer au sein d?un tableau sans se soucier du fait que la boucle parte de la fin du tableau : var tableauDonnees:Array = [ 5654, 95, 54, 687968, 97851]; for ( var p:String in tableauDonnees ) { /* affiche : 5654 95 54 687968 97851 */ trace( tableauDonnees[p] ); } Chapitre 2 ? Langage et API - version 0.1.2 26 / 35 Thibault Imbert pratiqueactionscript3.bytearray.org La boucle for each accède elle, directement au contenu de chaque propriété : var personnage:Object = { prenom : "Bobby", age : 50 }; for each ( var p:* in personnage ) { /* affiche : 50 Bobby */ trace( p ); } Nous pouvons donc plus simplement itérer au sein du tableau à l?aide de la nouvelle boucle for each : var tableauDonnees:Array = [ 5654, 95, 54, 687968, 97851 ]; for each ( var p:* in tableauDonnees ) { /* affiche : 5654 95 54 687968 97851 */ trace( p ); } Nous allons nous intéresser dans la prochaine partie au concept de ramasse-miettes qui s?avère très important en ActionScript 3. Enrichissement de la classe Array La classe Array bénéficie de nouvelles méthodes en ActionScript 3 facilitant la manipulation de données. La méthode forEach permet d?itérer simplement au sein du tableau : // Array.forEach procède à une navigation simple // sur chaque élement à l'aide d'une fonction spécifique var prenoms:Array = [ "bobby", "willy", "ritchie" ]; function navigue ( element:*, index:int, tableau:Array ):void { trace ( element + " : " + index + " : " + tableau); } Chapitre 2 ? Langage et API - version 0.1.2 27 / 35 Thibault Imbert pratiqueactionscript3.bytearray.org /* affiche : bobby : 0 : bobby,willy,ritchie willy : 1 : bobby,willy,ritchie ritchie : 2 : bobby,willy,ritchie */ prenoms.forEach( navigue ); Toutes ces nouvelles méthodes fonctionnent sur le même principe. Une fonction de navigation est passée en paramètre afin d?itérer et de traiter les données au sein du tableau. La méthode every exécute la fonction de navigation jusqu'à ce que celle ci ou l?élément parcouru renvoient false. Il est donc très simple de déterminer si un tableau contient des valeurs attendues. Dans le code suivant, nous testons si le tableau donnees contient uniquement des nombres : var donnees:Array = [ 12, "bobby", "willy", 58, "ritchie" ]; function navigue ( element:*, index:int, tableau:Array ):Boolean { return ( element is Number ); } var tableauNombres:Boolean = donnees.every ( navigue ); // affiche : false trace( tableauNombres ); La méthode map permet la création d?un tableau relatif au retour de la fonction de navigation. Dans le code suivant, nous appliquons un formatage aux données du tableau prenoms. Un nouveau tableau de prénoms formatés est créé : var prenoms:Array = ["bobby", "willy", "ritchie"]; function navigue ( element:*, index:int, tableau:Array ):String { return element.charAt(0).toUpperCase()+element.substr(1).toLowerCase(); } // on crée un tableau à partir du retour de la fonction navigue var prenomsFormates:Array = prenoms.map ( navigue ); // affiche : Bobby,Willy,Ritchie trace( prenomsFormates ); La méthode map ne permet pas de filtrer les données. Toutes les données du tableau source sont ainsi placées au sein du tableau généré. Chapitre 2 ? Langage et API - version 0.1.2 28 / 35 Thibault Imbert pratiqueactionscript3.bytearray.org Si nous souhaitons filtrer les données, nous pouvons appeler la méthode filter. Dans le code suivant nous filtrons les utilisateurs mineurs et obtenons un tableau d?utilisateurs majeurs : var utilisateurs:Array = [ { prenom : "Bobby", age : 18 }, { prenom : "Willy", age : 20 }, { prenom : "Ritchie", age : 16 }, { prenom : "Stevie", age : 15 } ]; function navigue ( element:*, index:int, tableau:Array ):Boolean { return ( element.age >= 18 ); } var utilisateursMajeurs:Array = utilisateurs.filter ( navigue ); function parcourir ( element:*, index:int, tableau:Array ):void { trace ( element.prenom, element.age ); } /* affiche : Bobby 18 Willy 20 */ utilisateursMajeurs.forEach( parcourir ); La méthode some permet de savoir si un élément existe au moins une fois au sein du tableau. La fonction de navigation est exécutée jusqu?à ce que celle ci ou un élément du tableau renvoient true : var utilisateurs:Array = [ { prenom : "Bobby", age : 18, sexe : "H" }, { prenom : "Linda", age : 18, sexe : "F" }, { prenom : "Ritchie", age : 16, sexe : "H"}, { prenom : "Stevie", age : 15, sexe : "H" } ] function navigue ( element:*, index:int, tableau:Array ):Boolean { return ( element.sexe == "F" ); } // y'a t'il une femme au sein du tableau d'utilisateurs ? var resultat:Boolean = utilisateurs.some ( navigue ); // affiche : true trace( resultat ); Chapitre 2 ? Langage et API - version 0.1.2 29 / 35 Thibault Imbert pratiqueactionscript3.bytearray.org Les méthodes indexOf et lastIndexOf font elles aussi leur apparition au sein de la classe Array, celles-ci permettent de rechercher si un élément existe et d?obtenir sa position : var utilisateurs:Array = [ "Bobby", "Linda", "Ritchie", "Stevie", "Linda" ]; var position:int = utilisateurs.indexOf ("Linda"); var positionFin:int = utilisateurs.lastIndexOf ("Linda"); // affiche : 1 trace( position ); // affiche : 4 trace( positionFin ); Les méthodes indexOf et lastIndexOf permettent de rechercher un élément de type primitif mais aussi de type composite. Nous pouvons ainsi rechercher la présence de références au sein d?un tableau : var monClip:DisplayObject = new MovieClip(); var monAutreClip:DisplayObject = new MovieClip(); // une référence au clip monClip est placée au sein du tableau var tableauReferences:Array = [ monClip ]; var position:int = tableauReferences.indexOf (monClip); var autrePosition:int = tableauReferences.lastIndexOf (monAutreClip); // affiche : 0 trace( position ); // affiche : -1 trace( autrePosition ); Nous reviendrons sur certaines de ces méthodes au sein de l?ouvrage. A retenir ? Pensez à utiliser les nouvelles méthodes de la classe Array afin de traiter plus facilement les données au sein d?un tableau. Ramasse-miettes Tout au long de l?ouvrage nous reviendrons sur le concept de ramasse- miettes. Bien que le terme puisse paraitre fantaisiste, ce mécanisme va s?avérer extrêmement important durant nos développements ActionScript 3. Pendant la durée de vie d?un programme, certains objets peuvent devenir inaccessibles. Afin, de ne pas saturer la mémoire, un mécanisme de suppression des objets inutilisés est intégré au sein du Chapitre 2 ? Langage et API - version 0.1.2 30 / 35 Thibault Imbert pratiqueactionscript3.bytearray.org lecteur Flash. Ce mécanisme est appelé ramasse-miettes ou plus couramment Garbage collector en Anglais. Afin de bien comprendre ce mécanisme, nous définissons un simple objet référencé par une variable personnage : var personnage:Object = { prenom : "Bobby", age : 50 }; Pour rendre cet objet inaccessible et donc éligible à la suppression par le ramasse-miettes, nous pourrions être tentés d?utiliser le mot clé delete : var personnage:Object = { prenom : "Bobby", age : 50 }; // génère une erreur à la compilation delete personnage; Le code précédent, génère l?erreur de compilation suivante : 1189: Tentative de suppression de la propriété fixe personnage. Seules les propriétés définies dynamiquement peuvent être supprimées. Cette erreur traduit une modification du comportement du mot clé delete en ActionScript 3, qui ne peut être utilisé que sur des propriétés dynamiques d?objets dynamiques. Ainsi, le mot clé delete pourrait être utilisé pour supprimer la propriété prenom au sein de l?objet personnage : var personnage:Object = { prenom : "Bobby", age : 50 }; // affiche : Bobby trace( personnage.prenom ); // supprime la propriété prenom delete ( personnage.prenom ); // affiche : undefined trace( personnage.prenom ); Afin de supprimer correctement une référence, nous devons affecter la valeur null à celle-ci : var personnage:Object = { prenom : "Bobby", age : 50 }; // supprime la référence vers l?objet personnage personnage = null; Lorsque l?objet ne possède plus aucune référence, nous pouvons estimer que l?objet est éligible à la suppression. Nous devrons donc toujours veiller à ce qu?aucune référence ne subsiste vers notre objet, au risque de le voir conservé en mémoire. Attention, l?affectation de la valeur null à une référence ne déclenche en aucun cas le passage du Chapitre 2 ? Langage et API - version 0.1.2 31 / 35 Thibault Imbert pratiqueactionscript3.bytearray.org ramasse-miettes. Nous rendons simplement l?objet éligible à la suppression. Il est important de garder à l?esprit que le passage du ramasse-miettes reste potentiel. L?algorithme de nettoyage effectué par ce dernier étant relativement gourmand en termes de ressources, son déclenchement reste limité au cas où le lecteur utiliserait trop de mémoire. Dans le code suivant, nous supprimons une des deux références seulement : var personnage:Object = { prenom : "Bobby", age : 50 }; // copie d'une référence au sein du tableau var tableauPersonnages:Array = [ personnage ]; // suppression d?une seule référence personnage = null; Une autre référence vers l?objet personnage subsiste au sein du tableau, l?objet ne sera donc jamais supprimé par le ramasse-miettes : var personnage:Object = { prenom : "Bobby", age : 50 }; // copie d'une référence au sein du tableau var tableauPersonnages:Array = [ personnage ]; // supprime une des deux références seulement personnage = null; var personnageOrigine:Object = tableauPersonnages[0]; // affiche : Bobby trace( personnageOrigine.prenom ); // affiche : 50 trace( personnageOrigine.age ); Nous devons donc aussi supprimer la référence présente au sein du tableau afin de rendre l?objet personnage éligible à la suppression : var personnage:Object = { prenom : "Bobby", age : 50 }; // copie d'une référence au sein du tableau var tableauPersonnages:Array = [ personnage ]; // supprime la première référence personnage = null; // supprime la seconde référence tableauPersonnages[0] = null; Si la situation nous le permet, un moyen plus radical consiste à écraser le tableau contenant la référence : var personnage:Object = { prenom : "Bobby", age : 50 }; Chapitre 2 ? Langage et API - version 0.1.2 32 / 35 Thibault Imbert pratiqueactionscript3.bytearray.org // copie d'une référence au sein du tableau var tableauPersonnages:Array = [ personnage ]; // supprime la première référence personnage = null; // écrase le tableau stockant la référence tableauPersonnages = new Array(); Depuis la version 9.0.115 du lecteur Flash 9, il est possible de déclencher le ramasse-miettes manuellement à l?aide de la méthode gc de la classe System. Notons que cette fonctionnalité n?est accessible qu?au sein du lecteur de débogage. Nous reviendrons sur cette méthode au cours du prochain chapitre intitulé Le modèle événementiel. A retenir ? Il est possible de déclencher manuellement le ramasse-miettes au sein du lecteur de débogage 9.0.115. ? Afin qu?un objet soit éligible à la suppression par le ramasse-miettes nous devons passer toutes ses références à null. ? Le ramasse-miettes intervient lorsque la machine virtuelle juge cela nécessaire. Bonnes pratiques Durant l?ensemble de l?ouvrage nous ferons usage de certaines bonnes pratiques, dont voici le détail : Nous typerons les variables systématiquement afin d?optimiser les performances de nos applications et garantir une meilleure gestion des erreurs à la compilation. Lors de la définition de boucles, nous utiliserons toujours une variable de référence afin d?éviter que la machine virtuelle ne réévalue la longueur du tableau à chaque itération. Nous préférerons donc le code suivant : var tableauDonnees:Array = [ 5654, 95, 54, 687968, 97851]; // stockage de la longueur du tableau var lng:int = tableauDonnees.length; for ( var i:int = 0; i< lng; i++ ) { } A cette écriture non optimisée : Chapitre 2 ? Langage et API - version 0.1.2 33 / 35 Thibault Imbert pratiqueactionscript3.bytearray.org var tableauDonnees:Array = [ 5654, 95, 54, 687968, 97851]; for ( var i:int = 0; i< tableauDonnees.length; i++ ) { } De la même manière, nous éviterons de redéfinir nos variables au sein des boucles. Nous préférerons l?écriture suivante : var tableauDonnees:Array = [5654, 95, 54, 687968, 97851]; var lng:int = tableauDonnees.length; // déclaration de la variable elementEnCours une seule et unique fois var elementEnCours:int; for ( var i:int = 0; i< lng; i++ ) { elementEnCours = tableauDonnees[i]; } A l?écriture suivante non optimisée : var tableauDonnees:Array = [5654, 95, 54, 687968, 97851]; for ( var i:int = 0; i< tableauDonnees.length; i++ ) { // déclaration de la variable elementEnCours à chaque itération var elementEnCours:int = tableauDonnees[i]; } Découvrons à présent quelques dernières subtilités du langage ActionScript 3. Autres subtilités Attention, le type Void existant en ActionScript 2, utilisé au sein des signatures de fonctions prend désormais un v minuscule en ActionScript 3. Une fonction ActionScript 2 ne renvoyant aucune valeur s?écrivait de la manière suivante : function initilisation ( ):Void { } Chapitre 2 ? Langage et API - version 0.1.2 34 / 35 Thibault Imbert pratiqueactionscript3.bytearray.org En ActionScript 3, le type void ne prend plus de majuscule : function initilisation ( ):void { } ActionScript 3 intègre un nouveau type de données permettant d?indiquer qu?une variable peut accueillir n?importe quel type de données. En ActionScript 2 nous nous contentions de ne pas définir de type, en ActionScript 3 nous utilisons le type * : var condition:* = true; var total:* = 5; var couleur:* = 0x990000; var resultat:* = 45.4; var personnage:* = new Object(); var prenom:* = "Bobby"; Nous utiliserons donc systématiquement le type * au sein d?une boucle for each, car la variable p doit pouvoir accueillir n?importe quel type de données : var personnage:Object = { prenom : "Bobby", age : 50 }; for each ( var p:* in personnage ) { /* affiche : 50 Bobby */ trace( p ); } En utilisant un type spécifique pour la variable d?itération p, nous risquons de convertir implicitement les données itérées. Dans le code suivant, nous utilisons le type Boolean pour la variable p : var personnage:Object = { prenom : "Bobby", age : 50 }; for each ( var p:Boolean in personnage ) { /* affiche : true true */ trace( p ); Chapitre 2 ? Langage et API - version 0.1.2 35 / 35 Thibault Imbert pratiqueactionscript3.bytearray.org } Le contenu des propriétés est automatiquement converti en booléen. Au cas où une donnée ne pourrait être convertie en booléen, le code précédent pourrait lever une erreur à l?exécution. Passons à présent aux nouveautés liées à l?interface de programmation du lecteur Flash. Les deux grandes nouveautés du lecteur Flash 9 concernent la gestion de l?affichage ainsi que le modèle événementiel. Au cours du prochain chapitre intitulé Le modèle événementiel nous allons nous intéresser à ce nouveau modèle à travers différents exercices d?applications. Nous apprendrons à maîtriser ce dernier et découvrirons comment en tirer profit tout au long de l?ouvrage. Puis nous nous intéresserons au nouveau mécanisme de gestion de l?affichage appelée Liste d?affichage au cours du chapitre 4, nous verrons que ce dernier offre beaucoup plus de souplesse en termes de manipulation des objets graphiques et d?optimisation de l?affichage. Vous êtes prêt pour la grande aventure ActionScript 3 ? Chapitre 3 ? Le modèle événementiel - version 0.1.3 1 / 35 Thibault Imbert pratiqueactionscript3.bytearray.org 3 Le modèle événementiel L?HISTOIRE............................................................................................................ 1 UN NOUVEAU MODELE EVENEMENTIEL..................................................... 6 TOUT EST ÉVÉNEMENTIEL ....................................................................................... 8 ECOUTER UN ÉVÉNEMENT..................................................................................... 10 L?OBJET ÉVÉNEMENTIEL ....................................................................................... 13 LA CLASSE EVENT................................................................................................. 16 LES SOUS-CLASSES D?EVENT......................................................................... 17 ARRÊTER L?ÉCOUTE D?UN ÉVÉNEMENT................................................................. 19 MISE EN APPLICATION........................................................................................... 20 LA PUISSANCE DU COUPLAGE FAIBLE....................................................... 25 SOUPLESSE DE CODE ............................................................................................. 28 ORDRE DE NOTIFICATION.............................................................................. 30 REFERENCES FAIBLES..................................................................................... 32 SUBTILITES.......................................................................................................... 34 L?histoire ActionScript 3 intègre un nouveau modèle événementiel que nous allons étudier ensemble tout au long de ce chapitre. Avant de l?aborder, rappelons-nous de l?ancien modèle apparu pour la première fois avec le lecteur Flash 6. Jusqu?à maintenant nous définissions des fonctions que nous passions en référence à l?événement écouté. Prenons un exemple simple, pour écouter l?événement onRelease d?un bouton nous devions écrire le code suivant : monBouton.onRelease = function () Chapitre 3 ? Le modèle événementiel - version 0.1.3 2 / 35 Thibault Imbert pratiqueactionscript3.bytearray.org { // affiche : _level0.monBouton trace ( this ); this._alpha = 50; } Une fonction anonyme était définie sur la propriété onRelease du bouton. Lorsqu?un clic souris intervenait sur le bouton, le lecteur Flash exécutait la fonction que nous avions définie et réduisait l?opacité du bouton de 50%. Cette écriture posait plusieurs problèmes. Dans un premier temps, la fonction définie sur le bouton ne pouvait pas être réutilisée pour un autre événement, sur un objet différent. Pour pallier ce problème de réutilisation, certains développeurs faisaient appels à des références de fonctions afin de gérer l?événement : function clicBouton () { // affiche : _level0.monBouton trace (this); this._alpha = 50; } monBouton.onRelease = clicBouton; Cette écriture permettait de réutiliser la fonction clicBouton pour d?autres événements liés à différents objets, rendant le code plus aéré en particulier lors de l?écoute d?événements au sein de boucles. Point important, le mot-clé this faisait ici référence au bouton et non au scénario sur lequel est définie la fonction clicBouton. Le contexte d?exécution d?origine de la fonction clicBouton était donc perdu lorsque celle-ci était passée en référence. La fonction épousait le contexte de l?objet auteur de l?événement. Ensuite l?auto référence traduite par l?utilisation du mot-clé this était le seul moyen de faire référence à ce dernier. Ce comportement était quelque peu déroutant pour une personne découvrant ActionScript. Voici un exemple mettant en évidence l?ambiguïté possible : function clicBouton () { // affiche : _level0.monBouton Chapitre 3 ? Le modèle événementiel - version 0.1.3 3 / 35 Thibault Imbert pratiqueactionscript3.bytearray.org trace ( this ); this._alpha = 50; } monBouton.onRelease = clicBouton; clicBouton(); En exécutant la fonction clicBouton de manière traditionnelle le mot-clé this faisait alors référence au scénario sur lequel elle était définie, c?est à dire son contexte d?origine. Le code suivant était peu digeste et rigide : var ref:MovieClip; for ( var i:Number = 0; i< 50; i++ ) { ref = this.attachMovie ( "monClip", "monClip"+i, i ); ref.onRelease = function () { trace("cliqué"); } } Les développeurs préféraient donc l?utilisation d?une fonction séparée réutilisable : var ref:MovieClip; for ( var i:Number = 0; i< 50; i++ ) { ref = this.attachMovie ( "monClip", "monClip"+i, i ); ref.onRelease = clicBouton; } function clicBouton ( ) { trace("cliqué"); } Le mécanisme était le même pour la plupart des événements. Macromedia, à l?époque de Flash MX (Flash 6) s?était rendu compte du manque de souplesse de ce système de fonctions définies sur les Chapitre 3 ? Le modèle événementiel - version 0.1.3 4 / 35 Thibault Imbert pratiqueactionscript3.bytearray.org événements, et intégra la notion d?écouteurs et de diffuseurs au sein du lecteur Flash 6. Les classes Key, Selection, TextField furent les premières à utiliser cette notion de diffuseurs écouteurs. Pour écouter la saisie dans un champ texte, nous pouvions alors utiliser la syntaxe suivante : var monEcouteur:Object = new Object(); monEcouteur.onChanged = function(pChamp:TextField) { trace ( pChamp._name + " a diffusé l?événement onChanged" ); }; monChampTexte.addListener ( monEcouteur ); En appelant la méthode addListener sur notre champ texte nous souscrivions un objet appelé ici monEcouteur en tant qu?écouteur de l?événement onChanged. Une méthode du même nom devait être définie sur l?objet écouteur afin que celle-ci soit exécutée lorsque l?événement était diffusé. Pour déterminer quelle touche du clavier était enfoncée, nous pouvions écrire le code suivant : var monEcouteur:Object = new Object(); monEcouteur.onKeyDown = function() { trace ( "La touche " + String.fromCharCode( Key.getCode() ) + " est enfoncée" ); }; Key.addListener ( monEcouteur ); Cette notion de méthode portant le nom de l?événement diffusé n?était pas évidente à comprendre et souffrait de la faiblesse suivante. Aucun contrôle du nom de l?événement n?était effectué à la compilation ou à l?exécution. Le code suivant ne générait donc aucune erreur bien qu?une faute de frappe s?y soit glissée : var monEcouteur:Object = new Object(); monEcouteur.onKeydown = function() { trace ( "La touche " + String.fromCharCode( Key.getCode() ) + " est enfoncée" ); Chapitre 3 ? Le modèle événementiel - version 0.1.3 5 / 35 Thibault Imbert pratiqueactionscript3.bytearray.org }; Key.addListener ( monEcouteur ); Bien que critiquée, cette approche permettait néanmoins de souscrire un nombre illimité d?écouteurs auprès d?un événement, mais aussi de renvoyer des paramètres à la méthode écouteur. Ces paramètres étaient généralement des informations liées à l?événement, par exemple l?auteur de l?événement diffusé. En 2004, lors de la sortie du lecteur Flash 7 (Flash MX 2004), ActionScript 2 fit son apparition accompagné d?un lot de nouveaux composants appelés composants V2. Ces derniers étendaient le concept de diffuseurs écouteurs en intégrant une méthode addEventListener pour souscrire un écouteur auprès d?un événement. Contrairement à la méthode addListener, cette méthode stockait chaque écouteur dans des tableaux internes différents pour chaque événement. En regardant l?évolution d?ActionScript au cours des dernières années, nous pouvons nous rendre compte du travail des ingénieurs visant à intégrer la notion d?écouteurs auprès de tous nouveaux objets créés. ActionScript 3 a été développé dans le but d?offrir un langage puissant et standard et prolonge ce concept en intégrant un nouveau modèle événementiel appelé « Document Object Model » (DOM3 Event Model) valable pour tous les objets liés à l?API du lecteur Flash 9. Le W3C définit la spécification de ce modèle événementiel. Vous pouvez lire les dernières révisions à l?adresse suivante : http://www.w3.org/TR/DOM-Level-3-Events/ Les développeurs ActionScript 2 ayant déjà utilisé la classe EventDispatcher seront ravis de savoir que toutes les classes de l?API du lecteur Flash héritent directement de celle-ci. Nous allons donc découvrir en profondeur le concept de diffuseurs écouteurs et voir comment cela améliore la clarté et la souplesse de notre code. A retenir Chapitre 3 ? Le modèle événementiel - version 0.1.3 6 / 35 Thibault Imbert pratiqueactionscript3.bytearray.org ? L?ancien modèle événementiel a été entièrement revu en ActionScript 3. ? La grande puissance d?ActionScript 3 réside dans l?implémentation native d?EventDispatcher. Un nouveau modèle événementiel En ActionScript 3, le modèle événementiel a clairement évolué. La manière dont les objets interagissent entre eux a été entièrement revue. Le principe même de ce modèle événementiel est tiré d?un modèle de conception ou « Design Pattern » appelé Observateur qui définit l?interaction entre plusieurs objets d?après un découpage bien spécifique. Habituellement ce modèle de conception peut être défini de la manière suivante : « Le modèle de conception observateur définit une relation entre objets de type un à plusieurs, de façon que, lorsque un objet change d?état, tous ceux qui en dépendent en soient notifiés et soient mis à jour automatiquement ». Le modèle de conception Observateur met en scène trois acteurs : Le sujet Il est la source de l?événement, nous l?appellerons sujet car c?est lui qui diffuse les événements qui peuvent être écoutés ou non par les observateurs. Ce sujet peut être un bouton, ou un clip par exemple. Ne vous inquiétez pas, tous ces événements sont documentés, la documentation présente pour chaque objet les événements diffusés. L?événement Nous pouvons conceptualiser la notion d?événement comme un changement d?état au sein du sujet. Un clic sur un bouton, la fin d?un chargement de données provoquera la diffusion d?un événement par l?objet sujet. Tous les événements existants en ActionScript 3 sont stockés dans des propriétés constantes de classes liées à l?événement. L?écouteur L?écouteur a pour mission d?écouter un événement spécifique auprès d?un ou plusieurs sujets. Il peut être une fonction ou une méthode. Lorsque le sujet change d?état, ce dernier diffuse un événement approprié, si l?écouteur est souscrit auprès de l?événement diffusé, il en est notifié et exécute une action. Il est Chapitre 3 ? Le modèle événementiel - version 0.1.3 7 / 35 Thibault Imbert pratiqueactionscript3.bytearray.org important de souligner qu?il peut y avoir de multiples écouteurs pour un même événement. C?est justement l?un des points forts existant au sein de l?ancien modèle qui fut conservé dans ce nouveau représenté par la figure 3-1 : Figure 3-1. Schéma du modèle de conception Observateur Nos écouteurs sont dépendants du sujet, ils y ont souscrit. Le sujet ne connaît rien des écouteurs, il sait simplement s?il est observé ou non à travers sa liste interne d?écouteurs. Lorsque celui-ci change d?état un événement est diffusé, nos écouteurs en sont notifiés et peuvent alors réagir. Tout événement diffusé envoie des informations aux écouteurs leur permettant d?être tenus au courant des modifications et donc de rester à jour en permanence avec le sujet. Nous découvrirons au fil de ces pages, la souplesse apportée par ce nouveau modèle événementiel, et vous apprécierez son fonctionnement très rapidement. Cela reste encore relativement abstrait, pour y remédier voyons ensemble comment ce modèle événementiel s?établit au sein d?une application Flash traditionnelle. Prenons un exemple simple constitué d?un bouton et d?une fonction écouteur réagissant lorsque l?utilisateur clique sur ce bouton. Nous avons dans ce cas nos trois acteurs mettant en scène le nouveau modèle événementiel ActionScript 3 : ? Le sujet : le bouton ? L?événement : le clic bouton Chapitre 3 ? Le modèle événementiel - version 0.1.3 8 / 35 Thibault Imbert pratiqueactionscript3.bytearray.org ? L?écouteur : la fonction Transposé dans une application ActionScript 3 traditionnelle, nous pouvons établir le schéma suivant : Figure 3-2. Modèle événementiel dans une application ActionScript 3 Notre bouton est un sujet pouvant diffuser un événement. Celui-ci est écouté par une fonction écouteur. A retenir ? Il existe un seul et unique modèle événementiel en ActionScript 3. ? Ce modèle événementiel est basé sur le modèle de conception Observateur. ? Les trois acteurs de ce nouveau modèle événementiel sont : le sujet, l?événement, et l?écouteur. ? Nous pouvons avoir autant d?écouteurs que nous le souhaitons. ? Plusieurs écouteurs peuvent écouter le même événement. ? Un seul écouteur peut être souscrit à différents événements. Tout est événementiel L?essentiel du modèle événementiel réside au sein d?une seule et unique classe appelée EventDispatcher. C?est elle qui offre la possibilité à un objet de diffuser des événements. Tous les objets issus de l?API du lecteur 9 et 10 héritent de cette classe, et possèdent de ce fait la capacité de diffuser des événements. Chapitre 3 ? Le modèle événementiel - version 0.1.3 9 / 35 Thibault Imbert pratiqueactionscript3.bytearray.org Souvenez-vous que toutes les classes issues du paquetage flash sont relatives à l?API du lecteur Flash. Avant le lecteur 9, ces objets héritaient simplement de la classe Object, si nous souhaitions pouvoir diffuser des événements nous devions nous même implémenter la classe EventDispatcher au sein de ces objets. Voici la chaîne d?héritage de la classe MovieClip avant le lecteur Flash 9 : Object | +-MovieClip Dans les précédentes versions du lecteur Flash, la classe MovieClip étendait directement la classe Object, si nous souhaitions pouvoir diffuser des événements à partir d?un MovieClip nous devions implémenter nous-mêmes un nouveau modèle événementiel. Voyons comme cela a évolué avec l?ActionScript 3 au sein du lecteur 9. Depuis le lecteur 9 et ActionScript 3 : Object | +-EventDispatcher La classe EventDispatcher hérite désormais de la classe Object, tous les autres objets héritent ensuite d?EventDispatcher et bénéficient donc de la diffusion d?événements en natif. C?est une grande évolution pour nous autres développeurs ActionScript, car la plupart des objets que nous manipulerons auront la possibilité de diffuser des événements par défaut. Ainsi la classe MovieClip en ActionScript 3 hérite d?abord de la classe Object puis d?EventDispatcher pour ensuite hériter des différentes classes graphiques. Toutes les classes résidant dans le paquetage flash ne dérogent pas à la règle et suivent ce même principe. Voici la chaîne d?héritage complète de la classe MovieClip en ActionScript 3 : Object | +-EventDispatcher | +-DisplayObject | +-InteractiveObject | Chapitre 3 ? Le modèle événementiel - version 0.1.3 10 / 35 Thibault Imbert pratiqueactionscript3.bytearray.org +-DisplayObjectContainer | +-Sprite | +-MovieClip Voyons ensemble comment utiliser ce nouveau modèle événementiel à travers différents objets courants. Ecouter un événement Lorsque vous souhaitez être tenu au courant des dernières nouveautés de votre vidéoclub, vous avez plusieurs possibilités. La première consiste à vous déplacer jusqu?au vidéoclub afin de découvrir les dernières sorties. Une approche classique qui ne s?avère pas vraiment optimisée. Nous pouvons déjà imaginer le nombre de visites que vous effectuerez dans le magasin pour vous rendre compte qu?aucun nouveau DVD ne vous intéresse. Une deuxième approche consiste à laisser une liste de films que vous attendez au gérant du vidéoclub et attendre que celui-ci vous appelle lorsqu?un des films est arrivé. Une approche beaucoup plus efficace pour vous et pour le gérant qui en a assez de vous voir repartir déçu. Le gérant incarne alors le diffuseur, et vous-même devenez l?écouteur. Le gérant du vidéoclub ne sait rien sur vous si ce n?est votre nom et votre numéro de téléphone. Il est donc le sujet, vous êtes alors l?écouteur car vous êtes souscrit à un événement précis : « la disponibilité d?un film que vous recherchez ». En ActionScript 3 les objets fonctionnent de la même manière. Pour obtenir une notification lorsque l?utilisateur clique sur un bouton, nous souscrivons un écouteur auprès d?un événement diffusé par le sujet, ici notre bouton. Afin d?écouter un événement spécifique, nous utilisons la méthode addEventListener dont voici la signature : addEventListener(type:String, listener:Function, useCapture:Boolean = false, priority:int = 0, useWeakReference:Boolean = false):void Le premier attend l?événement à écouter sous la forme d?une chaîne de caractères. Le deuxième paramètre prend en référence l?écouteur qui sera souscrit auprès de l?événement. Les trois derniers paramètres seront expliqués plus tard. Souvenez-vous que seuls les objets résidant dans le paquetage flash utilisent ce modèle événementiel. Le schéma à mémoriser est donc le suivant : Chapitre 3 ? Le modèle événementiel - version 0.1.3 11 / 35 Thibault Imbert pratiqueactionscript3.bytearray.org sujet.addEventListener("evenement", ecouteur); Voyons à l?aide d'un exemple simple, comment écouter un événement. Dans un nouveau document Flash, créez un symbole bouton, et placez une occurrence de ce dernier sur la scène et nommez la monBouton. Sur un calque AS tapez le code suivant : monBouton.addEventListener ( "click", clicBouton ); function clicBouton ( pEvt:MouseEvent ):void { // affiche : événement diffusé trace("événement diffusé"); } Nous écoutons ici l?événement click sur le bouton monBouton et nous passons la fonction clicBouton comme écouteur. Lorsque l?utilisateur clique sur le bouton, il diffuse un événement click qui est écouté par la fonction clicBouton. Attention à ne pas mettre de doubles parenthèses lorsque nous passons une référence à la fonction écouteur. Nous devons passer sa référence et non le résultat de son exécution. Revenons un instant sur notre code précédent : monBouton.addEventListener ( "click", clicBouton ); function clicBouton ( pEvt:MouseEvent ):void { // affiche : événement diffusé trace("événement diffusé"); } Même si cette syntaxe fonctionne, et ne pose aucun problème pour le moment, que se passe t-il si nous spécifions un nom d?événement incorrect ? Imaginons le cas suivant, nous faisons une erreur et écoutons l?événement clicck : monBouton.addEventListener( "clicck", clicBouton ); Si nous testons le code ci-dessus, la fonction clicBouton ne sera jamais déclenchée car le nom de l?événement click possède une erreur de saisie et de par sa non-existence n?est jamais diffusé. Chapitre 3 ? Le modèle événementiel - version 0.1.3 12 / 35 Thibault Imbert pratiqueactionscript3.bytearray.org Afin d?éviter ces problèmes de saisie, tous les noms des événements sont désormais stockés dans des classes liées à chaque événement. Lorsque vous devez écouter un événement spécifique pensez immédiatement au type d?événement qui sera diffusé. Les classes résidant dans le paquetage flash.events constituent l?ensemble des classes événementielles en ActionScript 3. Elles peuvent être divisées en deux catégories. On peut ainsi distinguer les classes événementielles liées aux événements autonomes : ? flash.events.Event ? flash.events.FullScreenEvent ? flash.events.HTTPStatusEvent ? flash.events.IOErrorEvent ? flash.events.NetStatusEvent ? flash.events.ProgressEvent ? flash.events.SecurityErrorEvent ? flash.events.StatusEvent ? flash.events.SyncEvent ? flash.events.TimerEvent Puis, celles liées aux événements interactifs : ? flash.events.FocusEvent ? flash.events.IMEEvent ? flash.events.KeyboardEvent ? flash.events.MouseEvent ? flash.events.TextEvent Dans notre cas, nous souhaitons écouter un événement qui relève d?une interactivité utilisateur provenant de la souris. Nous nous dirigerons logiquement au sein de la classe MouseEvent. Pour récupérer le nom d?un événement, nous allons cibler ces propriétés constantes statiques, la syntaxe repose sur le système suivant : ClasseEvenement.MON_EVENEMENT Pour bien comprendre ce concept, créez un nouveau document Flash et sur un calque AS tapez la ligne suivante : // affiche : click trace ( MouseEvent.CLICK ); Chapitre 3 ? Le modèle événementiel - version 0.1.3 13 / 35 Thibault Imbert pratiqueactionscript3.bytearray.org La classe MouseEvent contient tous les événements relatifs à la souris, en ciblant la propriété constante statique CLICK nous récupérons la chaîne de caractères click que nous passerons en premier paramètre de la méthode addEventListener. En ciblant un nom d?événement par l?intermédiaire d?une propriété de classe nous bénéficions de deux avantages. Si jamais une erreur de saisie survenait de par la vérification du code effectuée par le compilateur notre code ne pourrait pas être compilé. Ainsi le code suivant échouerait à la compilation : monBouton.addEventListener ( MouseEvent.CLICCK, clicBouton ); Il afficherait dans la fenêtre de sortie le message d?erreur suivant : 1119: Accès à la propriété CLICCK peut-être non définie, via la référence de type static Class. Au sein de Flash CS3, Flash CS4 ou Flex Builder le simple fait de cibler une classe événementielle comme MouseEvent vous affiche aussitôt tout les événements liés à cette classe. Vous n?aurez plus aucune raison de vous tromper dans le ciblage de vos événements. A plus long terme, si le nom de l?événement click venait à changer ou disparaître dans une future version du lecteur Flash, notre ancien code pointant vers la constante continuerait de fonctionner car ces propriétés seront mises à jour dans les futures versions du lecteur Flash. Pour écouter le clic souris sur notre bouton, nous récupérons le nom de l?événement et nous le passons à la méthode addEventListener : monBouton.addEventListener( MouseEvent.CLICK, clicBouton ); Cette nouvelle manière de stocker le nom des événements apporte donc une garantie à la compilation mais règle aussi un souci de compatibilité future. Voyons ensemble les différentes classes liées aux événements existant en ActionScript 3. L?objet événementiel Lorsqu?un événement est diffusé, un objet est obligatoirement envoyé en paramètre à la fonction écouteur. Cet objet est appelé objet événementiel. Pour cette raison, la fonction écouteur doit impérativement contenir dans sa signature un paramètre du type de l?événement diffusé. Afin de ne jamais vous tromper, souvenez-vous de la règle suivante. Chapitre 3 ? Le modèle événementiel - version 0.1.3 14 / 35 Thibault Imbert pratiqueactionscript3.bytearray.org Le type de l?objet événementiel correspond toujours au type de la classe contenant le nom de l?événement. Dans notre exemple il s?agit de MouseEvent. L?objet événementiel contient des informations liées à l?événement en cours de diffusion. Ces informations sont disponibles à travers des propriétés de l?objet événementiel. Nous nous attarderons pour l?instant sur trois de ces propriétés, target, currentTarget et type. ? La propriété target fait référence à l?objet cible de l?événement. De n?importe où nous pouvons savoir qui est à l?origine de la propagation de l?événement. ? La propriété currentTarget fait référence à l?objet diffuseur de l?événement (le sujet). Il s?agit toujours de l?objet sur lequel nous avons appelé la méthode addEventListener. ? La propriété type contient, elle, le nom de l?événement diffusé, ici click. Le code suivant récupère une référence vers l?objet cible, le sujet, ainsi que le nom de l?événement : monBouton.addEventListener ( MouseEvent.CLICK, clicBouton ); function clicBouton ( pEvt:MouseEvent ):void { // affiche : [object SimpleButton] trace( pEvt.target ); // affiche : [object SimpleButton] trace( pEvt.currentTarget ); // affiche : click trace( pEvt.type ); } Nous reviendrons au cours du chapitre 6 intitulé Propagation événementielle sur les différences subtiles entre les propriétés target et currentTarget. Le paramètre pEvt définit un paramètre de type MouseEvent car l?événement écouté est stocké au sein de la classe MouseEvent. Les propriétés target et currentTarget, présentent dans tout événement diffusé, permettent en réalité un couplage faible entre les objets. En passant par la propriété currentTarget pour cibler le sujet, nous évitons de le référencer directement par son nom, ce qui en cas de modification rendrait notre code rigide et pénible à modifier. Chapitre 3 ? Le modèle événementiel - version 0.1.3 15 / 35 Thibault Imbert pratiqueactionscript3.bytearray.org Attention, Si la fonction écouteur ne possède pas dans sa signature un paramètre censé accueillir l?objet événementiel, une exception est levée à l?exécution. Si nous testons le code suivant : monBouton.addEventListener ( MouseEvent.CLICK, clicBouton ); function clicBouton ():void { } L?erreur d?exécution suivante est levée : ArgumentError: Error #1063: Non-correspondance du nombre d'arguments sur bouton_fla::MainTimeline/onMouseClick(). 0 prévu(s), 1 détecté(s). Lorsque l?événement est diffusé, aucun paramètre n?accueille l?objet événementiel, une exception est donc levée. Souvenez-vous que la nouvelle machine virtuelle (AVM2) conserve les types à l?exécution, si le type de l?objet événementiel dans la signature de la fonction écouteur ne correspond pas au type de l?objet événementiel diffusé, le lecteur tentera de convertir implicitement l?objet événementiel. Une erreur sera aussitôt levée si la conversion est impossible. Mettons en évidence ce comportement en spécifiant au sein de la signature de la fonction écouteur un objet événementiel de type différent. Ici nous spécifions au sein de la signature de la fonction écouteur, un paramètre de type flash.events.TextEvent : monBouton.addEventListener ( MouseEvent.CLICK, clicBouton ); function clicBouton ( pEvt:TextEvent ):void { } Ce code lève l?erreur d?exécution suivante : TypeError: Error #1034: Echec de la contrainte de type : conversion de flash.events::MouseEvent@2ee7601 en flash.events.TextEvent impossible. A l?inverse, nous pouvons utiliser le type commun flash.events.Event compatible avec toutes les classes événementielles : monBouton.addEventListener ( MouseEvent.CLICK, clicBouton ); function clicBouton ( pEvt:Event ):void { Chapitre 3 ? Le modèle événementiel - version 0.1.3 16 / 35 Thibault Imbert pratiqueactionscript3.bytearray.org } Nous pouvons nous demander l?intérêt d?une telle écriture. Pourquoi utiliser le type Event alors que nous attendons le type MouseEvent ? Imaginons que nous souhaitions utiliser une seule et unique fonction écouteur pour de multiples événements. Prenons l?exemple d?une fonction ecouteurClicGlobal qui se charge d?écouter deux événements de types différents : monBouton.addEventListener ( MouseEvent.CLICK, ecouteurClicGlobal ); monChampTexte.addEventListener ( TextEvent.LINK, ecouteurClicGlobal ); function ecouteurClicGlobal ( pEvt:Event ):void { } L?utilisation du type commun Event résout ici tout problème de contrainte de type. Nous reviendrons sur ce mécanisme au cours du chapitre 13 intitulé Chargement de contenu. Si le code précédent vous pose un problème de compréhension, lisez avec attention les deux prochaines parties dédiées à la classe Event. La classe Event Comme nous l?avons vu précédemment, au sein du paquetage flash.events réside un ensemble de classes contenant tous les types d?objets d?événementiels pouvant être diffusés. Il est important de retenir que la classe Event est la classe parente de toutes les classes événementielles. Cette classe définit la majorité des événements diffusés en ActionScript 3, le classique événement Event.ENTER_FRAME est contenu dans la classe Event, tout comme l?événement Event.COMPLETE indiquant la fin de chargement de données externe. Dans un nouveau document Flash, créez un symbole MovieClip et placez une occurrence de ce clip nommé monClip sur la scène. Le code suivant permet l?écoute de l?événement Event.ENTER_FRAME auprès de ce dernier : monClip.addEventListener ( Event.ENTER_FRAME, execute ); function execute ( pEvt:Event ):void { Chapitre 3 ? Le modèle événementiel - version 0.1.3 17 / 35 Thibault Imbert pratiqueactionscript3.bytearray.org // affiche : [object MovieClip] : enterFrame trace( pEvt.currentTarget + " : " + pEvt.type ); } L?événement Event.ENTER_FRAME a la particularité d?être automatiquement diffusé dès lors que le lecteur entre sur une nouvelle image. Comme nous l?avons abordé précédemment, certains événements diffusent en revanche des objets événementiels de type étendus à la classe Event, c?est le cas notamment des événements provenant de la souris ou du clavier. Lorsqu?un événement lié à la souris est diffusé, nous ne recevons pas un objet événementiel de type Event mais un objet de type flash.events.MouseEvent. Pourquoi recevons-nous un objet événementiel de type différent pour les événements souris ? C?est ce que nous allons découvrir ensemble dans cette partie intitulée Les sous classes d?Event. Les sous-classes d?Event Lorsque l?événement Event.ENTER_FRAME est diffusé, l?objet événementiel de type Event n?a aucune information supplémentaire à renseigner à l?écouteur. Les propriétés target et type commune à ce type sont suffisantes. A l?inverse, si vous souhaitez écouter un événement lié au clavier, il y a de fortes chances que notre écouteur ait besoin d?informations supplémentaires, comme par exemple la touche du clavier qui vient d?être enfoncée et qui a provoqué la diffusion de cet événement. De la même manière un événement diffusé par la souris pourra nous renseigner sur sa position ou bien sur quel élément notre curseur se situe. Si nous regardons la définition de la classe MouseEvent nous découvrons de nouvelles propriétés contenant les informations dont nous pourrions avoir besoin. Le code suivant récupère la position de la souris ainsi que l?état de la touche ALT du clavier : monBouton.addEventListener ( MouseEvent.CLICK, clicBouton ); function clicBouton ( pEvt:MouseEvent ):void { // affiche : x : 40.05 y : 124.45 Chapitre 3 ? Le modèle événementiel - version 0.1.3 18 / 35 Thibault Imbert pratiqueactionscript3.bytearray.org trace ( "x : " + pEvt.stageX + " y : " + pEvt.stageY ); // affiche : x : 3 y : 4 trace ( "x : " + pEvt.localX + " y : " + pEvt.localY ); // affiche : ALT enfonçé : false trace ( "ALT enfonçé : " + pEvt.altKey ); } Lorsque l?événement MouseEvent.CLICK est diffusé, la fonction écouteur clicBouton en est notifiée et récupère les informations relatives à l?événement au sein de l?objet événementiel, ici de type MouseEvent. Nous récupérons ici la position de la souris par rapport à la scène en ciblant les propriétés stageX et stageY, la propriété altKey est un booléen renseignant sur l?état de la touche ALT du clavier. Les propriétés localX et localY de l?objet événementiel pEvt nous renseignent sur la position de la souris par rapport au repère local du bouton, nous disposons ici d?une très grande finesse en matière de gestion des coordonnées de la souris. En examinant les autres sous-classes de la classe Event vous découvrirez que chacune des sous-classes en héritant intègre de nombreuses propriétés supplémentaires riches en informations. A retenir Chapitre 3 ? Le modèle événementiel - version 0.1.3 19 / 35 Thibault Imbert pratiqueactionscript3.bytearray.org ? Tous les objets issus du paquetage flash peuvent diffuser des événements. ? La méthode addEventListener est le seul et unique moyen d?écouter un événement. ? Le nom de chaque événement est stocké dans une propriété statique de classe correspondant à l?événement en question. ? Les classes contenant le nom des événements sont appelées classes événementielles. ? La classe Event est la classe parente de toutes les classes événementielles. ? Lorsqu?un événement est diffusé, il envoie en paramètre à la fonction écouteur un objet appelé objet événementiel. ? Le type de cet objet événementiel est le même que la classe contenant le nom de l?événement. ? Un objet événementiel possède au minimum une propriété target et currentTarget référençant l?objet cible et l?objet auteur de l?événement. La propriété type permet de connaître le nom de l?événement en cours de diffusion. Arrêter l?écoute d?un événement Imaginez que vous ne souhaitiez plus être mis au courant des dernières nouveautés DVD de votre vidéoclub. En informant le gérant que vous ne souhaitez plus en être notifié, vous ne serez plus écouteur de l?événement « nouveautés DVD ». En ActionScript 3, lorsque nous souhaitons ne plus écouter un événement, nous utilisons la méthode removeEventListener dont voici la signature : removeEventListener(type:String, listener:Function, useCapture:Boolean = false):void Le premier paramètre appelé type attend le nom de l?événement auquel nous souhaitons nous désinscrire, le deuxième attend une référence à la fonction écouteur, le dernier paramètre sera traité au cours du chapitre 6 intitulé Propagation événementielle. Reprenons ensemble notre exemple précédent. Lorsqu?un clic sur le bouton se produit, l?événement MouseEvent.CLICK est diffusé, la fonction écouteur clicBouton en est notifiée et nous supprimons l?écoute de l?événement : monBouton.addEventListener( MouseEvent.CLICK, clicBouton ); function clicBouton ( pEvt:MouseEvent ):void { Chapitre 3 ? Le modèle événementiel - version 0.1.3 20 / 35 Thibault Imbert pratiqueactionscript3.bytearray.org // affiche : x : 40.05 y : 124.45 trace( "x : " + pEvt.stageX + " y : " + pEvt.stageY ); // affiche : ALT enfonçé : false trace( "ALT enfonçé : " + pEvt.altKey ); // on supprime l'écoute de l'événement MouseEvent.CLICK pEvt.target.removeEventListener ( MouseEvent.CLICK, clicBouton ); } L?appel de la méthode removeEventListener sur l?objet monBouton au sein de la fonction écouteur clicBouton, supprime l?écoute de l?événement MouseEvent.CLICK. La fonction écouteur sera donc déclenchée une seule et unique fois. Lorsque vous n?avez plus besoin d?un écouteur pensez à le supprimer de la liste des écouteurs à l?aide de la méthode removeEventListener. Le ramasse-miettes ne supprimera pas de la mémoire un objet ayant une de ses méthodes enregistrées comme écouteur. Pour optimiser votre application, pensez à supprimer les écouteurs non utilisés, l?occupation mémoire sera moindre et vous serez à l?abri de comportements difficiles à diagnostiquer. Mise en application Afin de reprendre les notions abordées précédemment nous allons ensemble créer un projet dans lequel une fenêtre s?agrandit avec un effet d?inertie selon des dimensions évaluées aléatoirement. Une fois le mouvement terminé, nous supprimerons l?événement nécessaire afin de libérer les ressources et d?optimiser l?exécution de notre projet. Dans un document Flash, nous créons une occurrence de clip appelée myWindow représentant un rectangle de couleur unie. Nous écoutons l?événement MouseEvent.CLICK sur un bouton nommé monBouton placé lui aussi sur la scène : monBouton.addEventListener ( MouseEvent.CLICK, clicBouton ); Puis nous définissons la fonction écouteur : function clicBouton ( pEvt:MouseEvent ):void { trace("fonction écouteur déclenchée"); } Lors du clic souris sur le bouton monBouton, la fonction écouteur clicBouton est déclenchée, nous affichons un message validant Chapitre 3 ? Le modèle événementiel - version 0.1.3 21 / 35 Thibault Imbert pratiqueactionscript3.bytearray.org l?exécution de celle-ci. Il nous faut désormais écouter l?événement Event.ENTER_FRAME auprès de notre clip myWindow. La fonction clicBouton est modifiée de la manière suivante : function clicBouton ( pEvt:MouseEvent ):void { trace("fonction écouteur déclenchée"); trace("souscription de l'événement Event.ENTER_FRAME"); myWindow.addEventListener ( Event.ENTER_FRAME, redimensionne ); } En appelant la méthode addEventListener sur notre clip myWindow, nous souscrivons la fonction écouteur redimensionne, celle-ci s?occupera du redimensionnement de notre clip. A la suite, définissons la fonction redimensionne : function redimensionne ( pEvt:Event ):void { trace("exécution de la fonction redimensionne"); } Nous obtenons le code complet suivant : monBouton.addEventListener ( MouseEvent.CLICK, clicBouton ); function clicBouton ( pEvt:MouseEvent ):void { trace("fonction écouteur déclenchée"); trace("souscription de l'événement Event.ENTER_FRAME"); myWindow.addEventListener ( Event.ENTER_FRAME, redimensionne ); } function redimensionne ( pEvt:Event ):void { trace("exécution de la fonction redimensionne"); } Au clic souris, le message "exécution de la fonction redimensionne" s?affiche. Ajoutons à présent un effet d?inertie pour gérer le redimensionnement de notre fenêtre. Chapitre 3 ? Le modèle événementiel - version 0.1.3 22 / 35 Thibault Imbert pratiqueactionscript3.bytearray.org Il nous faut dans un premier temps générer une dimension aléatoire. Pour cela nous définissons deux variables sur notre scénario, afin de stocker la largeur et la hauteur générées aléatoirement. Juste après l?écoute de l?événement MouseEvent.CLICK nous définissons deux variables largeur et hauteur : var largeur:int; var hauteur:int; Ces deux variables de scénario serviront à stocker les tailles en largeur et hauteur que nous allons générer aléatoirement à chaque clic souris. Notons que l?utilisation du type int pour les variables hauteur et largeur nous permet d?obtenir par défaut des valeurs arrondies à l?entier inférieur. Cela nous évite de faire la conversion manuellement à l?aide de la méthode floor de la classe Math. Nous évaluons ces dimensions au sein de la fonction clicBouton en affectant les deux variables : function clicBouton ( pEvt:MouseEvent ):void { largeur = Math.random()*400; hauteur = Math.random()*400; trace("fonction écouteur déclenchée"); trace("souscription de l'événement Event.ENTER_FRAME"); myWindow.addEventListener ( Event.ENTER_FRAME, redimensionne ); } Nous utilisons la méthode random de la classe Math afin d?évaluer une dimension aléatoire en largeur et hauteur dans une amplitude de 400 pixels. Nous choisissons ici une amplitude inférieure à la taille totale de la scène. Nous modifions la fonction redimensionne afin d?ajouter l?effet voulu : function redimensionne ( pEvt:Event ):void { trace("exécution de la fonction redimensionne"); myWindow.width -= ( myWindow.width ? largeur ) * .3; myWindow.height -= ( myWindow.height - hauteur ) * .3; Chapitre 3 ? Le modèle événementiel - version 0.1.3 23 / 35 Thibault Imbert pratiqueactionscript3.bytearray.org } Rafraîchissons-nous la mémoire et lisons le code complet : monBouton.addEventListener ( MouseEvent.CLICK, clicBouton ); var largeur:int; var hauteur:int; function clicBouton ( pEvt:MouseEvent ):void { largeur = Math.random()*400; hauteur = Math.random()*400; trace("fonction écouteur déclenchée"); trace("souscription de l'événement Event.ENTER_FRAME"); myWindow.addEventListener ( Event.ENTER_FRAME, redimensionne ); } function redimensionne ( pEvt:Event ):void { trace("exécution de la fonction redimensionne"); myWindow.width -= ( myWindow.width - largeur ) *.3; myWindow.height -= ( myWindow.height - hauteur ) *.3; } Lorsque nous cliquons sur le bouton monBouton, notre fenêtre se redimensionne à une taille aléatoire, l?effet est visuellement très sympathique mais n?oublions pas que nous n?arrêtons jamais l?écoute de l?événement Event.ENTER_FRAME ! Il est important de noter qu?une fois un écouteur enregistré auprès d?un événement spécifique, toute nouvelle souscription est ignorée. Ceci évite qu?un écouteur ne soit enregistré plusieurs fois auprès d?un même événement. Ainsi dans notre code, quelque soit le nombre de clic, la fonction redimensionne n?est souscrite qu?une seule fois à l?événement Event.ENTER_FRAME. En laissant le code ainsi, même lorsque le mouvement de la fenêtre est terminé notre fonction écouteur redimensionne continue de s?exécuter et ralentit grandement notre application. La question que nous devons nous poser est la suivante : Chapitre 3 ? Le modèle événementiel - version 0.1.3 24 / 35 Thibault Imbert pratiqueactionscript3.bytearray.org Quand devons-nous supprimer l?écoute de l?événement Event.ENTER_FRAME ? L?astuce consiste à tester la différence entre la largeur et hauteur actuelle et la largeur et hauteur finale. Si la différence est inférieure à un demi nous pouvons en déduire que nous sommes quasiment arrivés à destination, et donc pouvons supprimer l?événement Event.ENTER_FRAME. Afin d?exprimer cela dans notre code, rajoutez cette condition au sein de la fonction redimensionne : function redimensionne ( pEvt:Event ):void { trace("exécution de la fonction redimensionne"); myWindow.width -= ( myWindow.width - largeur ) * .3; myWindow.height -= ( myWindow.height - hauteur ) * .3; if ( Math.abs ( myWindow.width - largeur ) < .5 && Math.abs ( myWindow.height - hauteur ) < .5 ) { myWindow.removeEventListener ( Event.ENTER_FRAME, redimensionne ); trace("redimensionnement terminé"); } } Nous supprimons l?écoute de l?événement Event.ENTER_FRAME à l?aide de la méthode removeEventListener. La fonction redimensionne n?est plus exécutée, de cette manière nous optimisons la mémoire, et donc les performances d?exécution de notre application. Notre application fonctionne sans problème, pourtant un point essentiel a été négligé ici, nous n?avons absolument pas tiré parti d?une fonctionnalité du nouveau modèle événementiel. Notre couplage entre nos objets est dit fort, cela signifie qu?un changement de nom sur un objet entraînera de lourdes modifications en chaîne au sein de notre code. Voyons comment arranger cela en optimisant nos relations inter-objets. A retenir Chapitre 3 ? Le modèle événementiel - version 0.1.3 25 / 35 Thibault Imbert pratiqueactionscript3.bytearray.org ? Lorsqu?un événement n?est plus utilisé, pensez à arrêter son écoute à l?aide de la méthode removeEventListener. La puissance du couplage faible Notre code précédent fonctionne pour le moment sans problème. Si nous devions modifier certains éléments de notre application, comme par exemple le nom de certains objets, une modification en cascade du code serait inévitable. Pour illustrer les faiblesses de notre code précédent, imaginons le scénario suivant : Marc, un nouveau développeur ActionScript 3 intègre votre équipe et reprend le projet de redimensionnement de fenêtre que nous avons développé ensemble. Marc décide alors de renommer le clip myWindow par maFenetre pour des raisons pratiques. Comme tout bon développeur, celui-ci s?attend à mettre à jour une partie minime du code. Marc remplace donc la ligne qui permet la souscription de l?événement Event.ENTER_FRAME au clip fenêtre. Voici sa nouvelle version de la fonction clicBouton : function clicBouton ( pEvt:MouseEvent ):void { largeur = Math.random()*400; hauteur = Math.random()*400; trace("fonction écouteur déclenchée"); trace("souscription de l'événement Event.ENTER_FRAME"); maFenetre.addEventListener ( Event.ENTER_FRAME, redimensionne ); } A la compilation, Marc se rend compte des multiples erreurs affichées dans la fenêtre de sortie, à plusieurs reprises l?erreur suivante est affichée : 1120: Accès à la propriété non définie myWindow. Cette erreur signifie qu?une partie de notre code fait appel à cet objet myWindow qui n?est pourtant plus présent dans notre application. En effet, au sein de notre fonction redimensionne nous ciblons toujours l?occurrence myWindow qui n?existe plus car Marc l?a renommé. Chapitre 3 ? Le modèle événementiel - version 0.1.3 26 / 35 Thibault Imbert pratiqueactionscript3.bytearray.org Marc décide alors de mettre à jour la totalité du code de la fonction redimensionne et obtient le code suivant : function redimensionne ( pEvt:Event ):void { trace("exécution de la fonction redimensionne"); maFenetre.width -= ( maFenetre.width ? largeur ) * .3; maFenetre.height -= ( maFenetre.height ? hauteur ) * .3; if ( Math.abs ( maFenetre.width ? largeur ) < .5 && Math.abs ( maFenetre.height ? hauteur ) < .5 ) { maFenetre.removeEventListener ( Event.ENTER_FRAME, redimensionne ); trace("arrivé"); } } A la compilation, tout fonctionne à nouveau ! Malheureusement pour chaque modification du nom d?occurrence du clip maFenetre, nous devrons mettre à jour la totalité du code de la fonction redimensionne. Cela est dû au fait que notre couplage inter-objets est fort. Eric qui vient de lire un article sur le couplage faible propose alors d?utiliser au sein de la fonction redimensionne une information essentielle apportée par tout objet événementiel diffusé. Souvenez-vous, lorsqu?un événement est diffusé, les fonctions écouteurs sont notifiées de l?événement, puis un objet événementiel est passé en paramètre à chaque fonction écouteur. Au sein de tout objet événementiel résident les propriétés target et currentTarget renseignant la fonction écouteur sur l?objet cible ainsi que l?auteur de l?événement. Grâce à la propriété currentTarget ou target, nous rendons le couplage faible entre nos objets. Modifions le code de la fonction redimensionne de manière à cibler la propriété currentTarget de l?objet événementiel : function redimensionne ( pEvt:Event ):void { trace("exécution de la fonction redimensionne"); var objetDiffuseur:DisplayObject = pEvt.currentTarget as DisplayObject; Chapitre 3 ? Le modèle événementiel - version 0.1.3 27 / 35 Thibault Imbert pratiqueactionscript3.bytearray.org objetDiffuseur.width -= ( objetDiffuseur.width - nLargeur ) * .3; objetDiffuseur.height -= ( objetDiffuseur.height - nHauteur ) * .3; if ( Math.abs ( objetDiffuseur.width - nLargeur ) < .5 && Math.abs ( objetDiffuseur.height - nHauteur ) < .5 ) { objetDiffuseur.removeEventListener ( Event.ENTER_FRAME, redimensionne ); trace("arrivé"); } } Si nous compilons, le code fonctionne. Désormais la fonction redimensionne fait référence au clip maFenetre par l?intermédiaire de la propriété currentTarget de l?objet événementiel. Souvenez-vous, la propriété currentTarget fait toujours référence à l?objet sujet sur lequel nous avons appelé la méthode addEventListener. Ainsi, lorsque le nom d?occurrence du clip maFenetre est modifié la propriété currentTarget nous permet de cibler l?objet auteur de l?événement sans même connaître son nom ni son emplacement. Pour toute modification future du nom d?occurrence du clip maFenetre, aucune modification ne devra être apportée à la fonction écouteur redimensionne. Nous gagnons ainsi en souplesse, notre code est plus simple à maintenir. Quelques jours plus tard, Marc décide d?imbriquer le clip maFenetre dans un autre clip appelé conteneur. Heureusement, notre couplage inter-objets est faible, une seule ligne seulement devra être modifiée : function clicBouton ( pEvt:MouseEvent ):void { largeur = Math.random()*400; hauteur = Math.random()*400; trace("fonction écouteur déclenchée"); trace("souscription de l'événement Event.ENTER_FRAME"); conteneur.maFenetre.addEventListener ( Event.ENTER_FRAME, redimensionne ); } Marc est tranquille, grâce au couplage faible son travail de mise à jour du code est quasi nul. Chapitre 3 ? Le modèle événementiel - version 0.1.3 28 / 35 Thibault Imbert pratiqueactionscript3.bytearray.org A retenir ? Lorsqu?un événement n?a plus besoin d?être observé, nous supprimons l?écoute avec la méthode removeEventListener. ? La méthode removeEventListener est le seul et unique moyen pour supprimer l?écoute d?un événement. ? Le nom de chaque événement est stocké dans une propriété statique de classe correspondant à l?événement en question. ? Utilisez la propriété target ou currentTarget des objets événementiels pour garantir un couplage faible entre les objets. ? Nous découvrirons au cours du chapitre 6 intitulé Propagation événementielle, les différences subtiles entre les propriétés target et currentTarget. Souplesse de code L?intérêt du nouveau modèle événementiel ActionScript 3 ne s?arrête pas là. Auparavant il était impossible d?affecter plusieurs fonctions écouteurs à un même événement. Le code ActionScript suivant met en évidence cette limitation de l?ancien modèle événementiel : function clicBouton1 ( ) { trace("click 1 sur le bouton"); } function clicBouton2 ( ) { trace("click 2 sur le bouton"); } monBouton.onRelease = clicBouton1; monBouton.onRelease = clicBouton2; Nous définissions sur le bouton monBouton deux fonctions pour gérer l?événement onRelease. En utilisant ce modèle événementiel il nous était impossible de définir plusieurs fonctions pour gérer un même événement. En ActionScript 1 et 2, une seule et unique fonction pouvait être définie pour gérer un événement spécifique. Chapitre 3 ? Le modèle événementiel - version 0.1.3 29 / 35 Thibault Imbert pratiqueactionscript3.bytearray.org Dans notre code, seule la fonction clicBouton2 était affectée comme gestionnaire de l?événement onRelease, car la dernière affectation effectuée sur l?événement onRelease est la fonction clicBouton2. La deuxième affectation écrasait la précédente. Le principe même du nouveau modèle événementiel apporté par ActionScript 3 repose sur le principe d?une relation un à plusieurs, c?est la définition même du modèle de conception Observateur. De ce fait nous pouvons souscrire plusieurs écouteurs auprès du même événement. Prenons un exemple simple constitué d?un bouton et de deux fonctions écouteurs. Dans un nouveau document Flash, créez un symbole bouton et placez une occurrence de bouton sur la scène et nommez la monBouton. Sur un calque AS tapez le code suivant : monBouton.addEventListener ( MouseEvent.CLICK, clicBouton1 ); monBouton.addEventListener ( MouseEvent.CLICK, clicBouton2 ); function clicBouton1 ( pEvt:MouseEvent ):void { // affiche : clicBouton1 : [object MovieClip] : monBouton trace( "clicBouton1 : " + pEvt.currentTarget + " : " + pEvt.currentTarget.name ); } function clicBouton2 ( pEvt:MouseEvent ):void { // affiche : clicBouton2 : [object MovieClip] : monBouton trace( "clicBouton2 : " + pEvt.currentTarget + " : " + pEvt.currentTarget.name ); } Nous souscrivons deux fonctions écouteurs auprès de l?événement MouseEvent.CLICK, au clic bouton les deux fonctions écouteurs sont notifiées de l?événement. Sachez que l?ordre de notification dépend de l?ordre dans lequel les écouteurs ont été souscris. Ainsi dans notre exemple la fonction clicBouton2 sera exécutée après la fonction clicBouton1. De la même manière, une seule fonction écouteur peut être réutilisée pour différents objets. Dans le code suivant la fonction écouteur tourner est utilisée pour les trois boutons. Un premier réflexe pourrait nous laisser écrire le code suivant : Chapitre 3 ? Le modèle événementiel - version 0.1.3 30 / 35 Thibault Imbert pratiqueactionscript3.bytearray.org monBouton1.addEventListener ( MouseEvent.MOUSE_DOWN, tourner ); monBouton2.addEventListener ( MouseEvent.MOUSE_DOWN, tourner ); monBouton3.addEventListener ( MouseEvent.MOUSE_DOWN, tourner ); function tourner ( pEvt:MouseEvent ) { if ( pEvt.currentTarget == monBouton1 ) monBouton1.rotation += 5; else if ( pEvt.currentTarget == monBouton2 ) monBouton2.rotation += 5; else if ( pEvt.currentTarget == monBouton3 ) monBouton3.rotation += 5; } Souvenez-vous que la propriété currentTarget vous permet de référencer dynamiquement l?objet auteur de l?événement : monBouton1.addEventListener ( MouseEvent.MOUSE_DOWN, tourner ); monBouton2.addEventListener ( MouseEvent.MOUSE_DOWN, tourner ); monBouton3.addEventListener ( MouseEvent.MOUSE_DOWN, tourner ); function tourner ( pEvt:MouseEvent ) { pEvt.currentTarget.rotation += 5; } En modifiant le nom des boutons, la fonction tourner ne subit, elle, aucune modification : boutonA.addEventListener ( MouseEvent.MOUSE_DOWN, tourner ); boutonB.addEventListener ( MouseEvent.MOUSE_DOWN, tourner ); boutonC.addEventListener ( MouseEvent.MOUSE_DOWN, tourner ); La fonction tourner est ainsi réutilisable pour n?importe quel objet pouvant subir une rotation. Ordre de notification Imaginez que vous soyez abonné à un magazine, vous souhaitez alors recevoir le magazine en premier lors de sa sortie, puis une fois reçu, le magazine est alors distribué aux autres abonnés. Certes ce scénario est peu probable dans la vie mais il illustre bien le concept de priorité de notifications du modèle événementiel ActionScript 3. Afin de spécifier un ordre de notification précis nous pouvons utiliser le paramètre priority de la méthode addEventListener dont nous revoyons la signature : addEventListener(type:String, listener:Function, useCapture:Boolean = false, priority:int = 0, useWeakReference:Boolean = false):void Chapitre 3 ? Le modèle événementiel - version 0.1.3 31 / 35 Thibault Imbert pratiqueactionscript3.bytearray.org En reprenant notre exemple précédent, nous spécifions le paramètre priority : monBouton.addEventListener ( MouseEvent.CLICK, clicBouton1, false, 0); monBouton.addEventListener ( MouseEvent.CLICK, clicBouton2, false, 1); function clicBouton1 ( pEvt:MouseEvent ):void { // affiche : A [object SimpleButton] : monBouton trace( "A " + pEvt.currentTarget + " : " + pEvt.currentTarget.name ); } function clicBouton2 ( pEvt:MouseEvent ):void { // affiche : B [object SimpleButton] : monBouton trace( "B " + pEvt.currentTarget + " : " + pEvt.currentTarget.name ); } Lors du clic sur le bouton monBouton, la fonction clicBouton1 est déclenchée après la fonction clicBouton2, le message suivant est affiché : B [object SimpleButton] : monBouton A [object SimpleButton] : monBouton Une fonction écouteur enregistrée avec une priorité de 0 sera exécutée après une fonction écouteur enregistrée avec une priorité de 1. Il est impossible de modifier la priorité d?un écouteur une fois enregistré, nous serions obligés de supprimer l?écouteur à l?aide de la méthode removeEventListener, puis d?enregistrer à nouveau l?écouteur. Cette notion de priorité n?existe pas dans l?implémentation officielle DOM3 et a été rajoutée uniquement en ActionScript 3 pour offrir plus de flexibilité. Même si cette fonctionnalité peut s?avérer pratique dans certains cas précis, s?appuyer sur l?ordre de notification dans vos développements est considéré comme une mauvaise pratique, la mise à jour de votre code peut s?avérer difficile, et rendre le déroulement de l?application complexe. Chapitre 3 ? Le modèle événementiel - version 0.1.3 32 / 35 Thibault Imbert pratiqueactionscript3.bytearray.org Références faibles En ActionScript 3 la gestion de la mémoire est assurée par une partie du lecteur appelée ramasse-miettes ou Garbage Collector en anglais. Nous avons rapidement abordé cette partie du lecteur au cours du chapitre 2 intitulé Langage et API. Le ramasse-miettes est chargé de libérer les ressources en supprimant de la mémoire les objets qui ne sont plus utilisés. Un objet est considéré comme non utilisé lorsqu?aucune référence ne pointe vers lui, autrement dit lorsque l?objet est devenu inaccessible. Quand nous écoutons un événement spécifique, l?écouteur est ajouté à la liste interne du diffuseur, il s?agit en réalité d?un tableau stockant les références de chaque écouteur. Gardez à l?esprit que le sujet référence l?écouteur et non l?inverse. De ce fait le ramasse-miettes ne supprimera jamais ces écouteurs tant que ces derniers seront référencés par les sujets et que ces derniers demeurent référencés. Lors de la souscription d?un écouteur auprès d?un événement vous avez la possibilité de modifier ce comportement grâce au dernier paramètre de la méthode addEventListener. Revenons sur la signature de cette méthode : addEventListener(type:String, listener:Function, useCapture:Boolean = false, priority:int = 0, useWeakReference:Boolean = false):void Le cinquième paramètre appelé useWeakReference permet de spécifier si la fonction écouteur sera stockée à l?aide d?une référence forte ou faible. En utilisant une référence faible, la fonction écouteur est référencée faiblement au sein du tableau d?écouteurs interne. De cette manière, si la seule ou dernière référence à la fonction écouteur est une référence faible lors du passage du ramasse-miettes, ce dernier fait exception, ignore cette référence et supprime tout de même l?écouteur de la mémoire. Attention, cela ne veut pas dire que la fonction écouteur va obligatoirement être supprimée de la mémoire. Elle le sera uniquement si le ramasse-miettes intervient et procède à un nettoyage. Notez bien que cette intervention pourrait ne jamais avoir lieu si le lecteur Flash n?en décidait pas ainsi. Chapitre 3 ? Le modèle événementiel - version 0.1.3 33 / 35 Thibault Imbert pratiqueactionscript3.bytearray.org C?est pour cette raison qu?il ne faut pas considérer cette technique comme une suppression automatique des fonctions écouteurs dès lors qu?elles ne sont plus utiles. Pensez à toujours appeler la méthode removeEventListener pour supprimer l?écoute de certains événements lorsque cela n?est plus nécessaire. Si ne nous spécifions pas ce paramètre, sa valeur par défaut est à false. Ce qui signifie que tous nos objets sujets conservent jusqu?à l?appel de la méthode removeEventListener une référence forte vers l?écouteur. Dans le code suivant, une méthode d?instance de classe personnalisée est enregistrée comme écouteur de l?événement Event.ENTER_FRAME d?un MovieClip : // instanciation d'un objet personnalisé var monObjetPerso:ClassePerso = new ClassePerso(); var monClip:MovieClip = new MovieClip(); // écoute de l'événement Event.ENTER_FRAME monClip.addEventListener( Event.ENTER_FRAME, monObjetPerso.ecouteur ); // supprime la référence vers l'instance de ClassePerso monObjetPerso = null; Bien que nous ayons supprimé la référence à l?instance de ClassePerso, celle-ci demeure référencée fortement par l?objet sujet monClip. En s?inscrivant comme écouteur à l?aide d?une référence faible, le ramasse-miettes ignorera la référence détenue par le sujet et supprimera l?instance de ClassePerso de la mémoire : // écoute de l'événement Event.ENTER_FRAME monClip.addEventListener( Event.ENTER_FRAME, monObjetPerso.ecouteur, false, 0, true ); Les références faibles ont un intérêt majeur lorsque l?écouteur ne connaît pas l?objet sujet auquel il est souscrit. N?ayant aucun moyen de se désinscrire à l?événement, il est recommandé d?utiliser une référence faible afin de garantir que l?objet puisse tout de même être libéré dé la mémoire. Nous reviendrons sur ces subtilités tout au long de l?ouvrage afin de ne pas être surpris par le ramasse-miettes. A retenir ? En ajoutant un écouteur à l?aide d?une référence faible, ce dernier sera tout de même supprimé de la mémoire, même si ce dernier n?a Chapitre 3 ? Le modèle événementiel - version 0.1.3 34 / 35 Thibault Imbert pratiqueactionscript3.bytearray.org pas été désinscrit à l?aide de la méthode removeEventListener. ? Il ne faut surtout pas considérer l?utilisation de références faibles comme remplacement de la méthode removeEventListener. ? Veillez à bien désinscrire tous vos écouteurs lorsqu?ils ne sont plus utiles à l?aide de la méthode removeEventListener. Subtilités Comme nous l?avons vu au début de ce chapitre, en ActionScript 1 et 2 nous avions l?habitude d?ajouter des écouteurs qui n?étaient pas des fonctions mais des objets. En réalité, une méthode portant le nom de l?événement était déclenchée lorsque ce dernier était diffusé. Pour écouter un événement lié au clavier nous pouvions écrire le code suivant : var monEcouteur = new Object(); monEcouteur.onKeyDown = function ( ) { trace ( Key.getCode() ); } Key.addListener ( monEcouteur ); En passant un objet comme écouteur, nous définissions une méthode portant le nom de l?événement diffusé, le lecteur déclenchait alors la méthode lorsque l?événement était diffusé. Désormais seule une fonction ou une méthode peut être passée en tant qu?écouteur. Si nous testons le code suivant : var monEcouteur:Object = new Object(); stage.addEventListener ( KeyboardEvent.KEY_DOWN, monEcouteur ); L?erreur de compilation ci-dessous s?affiche dans la fenêtre de sortie : 1118: Contrainte implicite d'une valeur du type statique Object vers un type peut-être sans rapport Function. Le compilateur détecte que le type de l?objet passé en tant qu?écouteur n?est pas une fonction mais un objet et refuse la compilation. En revanche il est techniquement possible de passer en tant qu?écouteur non pas une fonction mais une méthode créée dynamiquement sur un objet. Le code suivant fonctionne sans problème : Chapitre 3 ? Le modèle événementiel - version 0.1 35 / 35 Thibault Imbert pratiqueactionscript3.bytearray.org var monEcouteur:Object = new Object(); monEcouteur.maMethode = function () { trace( "touche clavier enfoncée" ); } stage.addEventListener ( KeyboardEvent.KEY_DOWN, monEcouteur.maMethode ); En revanche, une subtilité est à noter dans ce cas précis le contexte d?exécution de la méthode maMethode n?est pas celui que nous attendons. En faisant appel au mot-clé this pour afficher le contexte en cours nous serons surpris de ne pas obtenir [object Object] mais [object global]. Lorsque nous passons une méthode stockée au sein d?une propriété dynamique d?un objet, le lecteur Flash n?a aucun moyen de rattacher cette méthode à son objet d?origine, la fonction s?exécute alors dans un contexte global, l?objet global. Le code suivant met en avant cette subtilité de contexte : var monEcouteur:Object = new Object(); monEcouteur.maMethode = function () { // affiche : [object global] trace( this ); } stage.addEventListener ( KeyboardEvent.KEY_DOWN, monEcouteur.maMethode ); Il est donc fortement déconseillé d?utiliser cette technique pour gérer les événements au sein de votre application. Nous avons découvert ensemble le nouveau modèle événementiel, afin de couvrir totalement les mécanismes liés à ce modèle attardons-nous à présent sur la gestion de l?affichage en ActionScript 3. A retenir ? En ActionScript 3, un objet ne peut pas être utilisé comme écouteur. ? Si la méthode passée comme écouteur est créée dynamiquement, celle-ci s?exécuté dans un contexte global. Nous allons nous intéresser dans le chapitre suivant à la notion de liste d?affichage. Découvrons ensemble comment manipuler avec souplesse les objets graphiques au sein du leteur Flash 9. Chapitre 4 ? La liste d?affichage ? version 0.1.2 1 / 41 Thibault Imbert pratiqueactionscript3.bytearray.org 4 La liste d?affichage CLASSES GRAPHIQUES ...................................................................................... 1 LISTE D?AFFICHAGE........................................................................................... 5 INSTANCIATION DES CLASSES GRAPHIQUES............................................................. 7 AJOUT D?OBJETS D?AFFICHAGE ............................................................................... 8 REATTRIBUTION DE CONTENEUR............................................................................. 9 LISTE INTERNE D?OBJETS ENFANTS ....................................................................... 11 ACCEDER AUX OBJETS D?AFFICHAGE .................................................................... 13 SUPPRESSION D?OBJETS D?AFFICHAGE .................................................................. 20 EFFONDREMENT DES PROFONDEURS ..................................................................... 25 GESTION DE L?EMPILEMENT DES OBJETS D?AFFICHAGE......................................... 27 ECHANGE DE PROFONDEURS ................................................................................. 30 DESACTIVATION DES OBJETS GRAPHIQUES............................................................ 32 FONCTIONNEMENT DE LA TETE DE LECTURE ......................................................... 35 SUBTILITES DE LA PROPRIETE STAGE..................................................................... 36 SUBTILITES DE LA PROPRIETE ROOT ...................................................................... 38 LES _LEVEL........................................................................................................... 40 Classes graphiques En ActionScript 3 comme nous l?avons vu précédemment, toutes les classes graphiques sont stockées dans des emplacements spécifiques et résident au sein des trois différents paquetages : flash.display, flash.media, et flash.text. Le schéma suivant regroupe l?ensemble d?entre elles : Chapitre 4 ? La liste d?affichage ? version 0.1.2 2 / 41 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 3-1. Classes graphiques de la liste d?affichage. Nous remarquons à travers ce schéma la multitude de classes graphiques disponibles pour un développeur ActionScript 3. La question que nous pouvons nous poser est la suivante. Pourquoi un tel éclatement de classes ? ActionScript 3 a été développé dans un souci d?optimisation. En offrant un large choix de classes graphiques nous avons la possibilité d?utiliser l?objet le plus optimisé pour chaque besoin. Plus la classe graphique est enrichie plus celle-ci occupe un poids en mémoire important. Du fait du petit nombre de classes graphiques en ActionScript 1 et 2 beaucoup de développeurs utilisaient la classe MovieClip pour tout réaliser, ce qui n?était pas optimisé. En ActionScript 1 et 2 seules quatre classes graphiques existaient : MovieClip, Button, TextField, et dernièrement BitmapData. Prenons l?exemple d?une application Flash traditionnelle comme une application de dessin. Avant ActionScript 3 le seul objet nous permettant de dessiner grâce à l?API de dessin était la classe MovieClip. Cette dernière intègre un scénario ainsi que des méthodes liées à la manipulation de la tête de lecture qui nous étaient inutiles dans ce cas précis. Un simple objet Shape plus léger en mémoire suffirait pour dessiner. Dans la même logique, nous préférerons utiliser un objet SimpleButton plutôt qu?un MovieClip pour la création de boutons. Nous devons mémoriser trois types d?objets graphiques primordiaux. ? Les objets de type flash.display.DisplayObject Tout élément devant être affiché doit être de type DisplayObject. Un champ texte, un bouton ou un clip sera forcément de type Chapitre 4 ? La liste d?affichage ? version 0.1.2 3 / 41 Thibault Imbert pratiqueactionscript3.bytearray.org DisplayObject. Durant vos développements ActionScript 3, vous qualifieriez généralement de DisplayObject tout objet pouvant être affiché. La classe DisplayObject définit toutes les propriétés de base liées à l?affichage, la position, la rotation, l?étirement et d?autres propriétés plus avancées. Attention : un objet héritant simplement de DisplayObject comme Shape par exemple n?a pas la capacité de contenir des objets graphiques. Parmi les sous-classes directes de DisplayObject nous pouvons citer les classes Shape, Video, Bitmap. ? Les objets de type flash.display.InteractiveObject La classe InteractiveObject définit les comportements liés à l?interactivité. Lorsqu?un objet hérite de la classe InteractiveObject, ce dernier peut réagir aux entrées utilisateur liées à la souris ou au clavier. Parmi les objets graphiques descendant directement de la classe InteractiveObject nous pouvons citer les classes SimpleButton et TextField. ? Les objets de type flash.display.DisplayObjectContainer A la différence des DisplayObject les DisplayObjectContainer peuvent contenir des objets graphiques. Dorénavant nous parlerons d?objet enfant pour qualifier un objet graphique contenu dans un autre. Les objets héritant de cette classe sont donc appelés conteneurs d?objets d?affichage. La classe DisplayObjectContainer est une sous classe de la classe DisplayObject et définit toutes les propriétés et méthodes relatives à la manipulation des objets enfants. Loader, Sprite et Stage héritent directement de DisplayObjectContainer. Durant nos développements ActionScript 3, certains objets graphiques ne pourront être instanciés tandis que d?autres le pourront. Nous pouvons séparer en deux catégories l?ensemble des classes graphiques : Les objets instanciables : ? flash.display.Bitmap : cette classe sert d?enveloppe à l?objet flash.display.BitmapData qui ne peut être ajouté à la liste d?affichage sans enveloppe Bitmap. ? flash.display.Shape : il s?agit de la classe de base pour tout contenu vectoriel, elle est fortement liée à l?API de dessin grâce à sa propriété graphics. Nous reviendrons sur l?API de dessin très vite. Chapitre 4 ? La liste d?affichage ? version 0.1.2 4 / 41 Thibault Imbert pratiqueactionscript3.bytearray.org ? flash.media.Video : la classe Video sert à afficher les flux vidéo, provenant d?une webcam, d?un serveur de streaming, ou d?un simple fichier vidéo (FLV, MP4, MOV, etc.). ? flash.text.TextField : la gestion du texte est assurée par la classe TextField. ? flash.display.SimpleButton : la classe SimpleButton permet de créer des boutons dynamiquement ce qui était impossible dans les précédentes versions d?ActionScript. ? flash.display.Loader : la classe Loader gère le chargement de tout contenu graphique externe, chargement de SWF et images (PNG, GIF, JPG) ? flash.display.Sprite : la classe Sprite est un MovieClip allégé car il ne dispose pas de scénario. ? flash.display.MovieClip : la classe MovieClip est la classe graphique la plus enrichie, elle est liée à l?animation traditionnelle. Les objets non instanciables: ? flash.display.AVM1Movie : lorsqu?une animation ActionScript 1 ou 2 est chargée au sein d?une animation ActionScript 3, le lecteur Flash 9 enveloppe l?animation d?ancienne génération dans un objet de type AVM1Movie il est donc impossible d?instancier un objet de ce type par programmation. ? flash.display.InteractiveObject : la classe InteractiveObject définit les comportements liés à l?interactivité comme les événements souris ou clavier par exemple. ? flash.display.MorphShape : les formes interpolées sont représentées par le type MorphShape. Seul l?environnement auteur de Flash CS3 peut créer des objets de ce type. ? flash.display.StaticText : la classe StaticText représente les champs texte statique créés dans l?environnement auteur Flash CS3 ? flash.display.Stage : il est le conteneur principal de tout notre contenu graphique. A retenir Chapitre 4 ? La liste d?affichage ? version 0.1.2 5 / 41 Thibault Imbert pratiqueactionscript3.bytearray.org ? Il faut utiliser la classe la plus optimisée pour chaque cas. ? Les classes graphiques résident dans trois paquetages différents : flash.display, flash.media, et flash.text. ? Les classes DisplayObject, InteractiveObject, et DisplayObjectContainer sont abstraites et ne peuvent être instanciées ni héritées en ActionScript. Liste d?affichage Lorsqu?une animation est chargée au sein du lecteur Flash, celui-ci ajoute automatiquement la scène principale du SWF en tant que premier enfant du conteneur principal, l?objet Stage. Cette hiérarchie définit ce qu?on appelle la liste d?affichage de l?application. La figure 3-2 illustre le mécanisme : Figure 3-2. Schéma du système d?affichage du lecteur Flash. Afin de bien comprendre comment la liste d?affichage s?organise, créez un nouveau document Flash CS3 et testez le code suivant sur le scénario principal : // affiche : [object MainTimeline] trace( this ); // affiche : [object Stage] trace( this.parent ); En faisant appel au mot-clé this, nous faisons référence à notre scénario principal l?objet MainTimeline contenu par l?objet Stage. Chapitre 4 ? La liste d?affichage ? version 0.1.2 6 / 41 Thibault Imbert pratiqueactionscript3.bytearray.org Attention, l?objet Stage n?est plus accessible de manière globale comme c?était le cas en ActionScript 1 et 2. Le code suivant génère une erreur à la compilation : trace( Stage ); Pour faire référence à l?objet Stage nous devons obligatoirement passer par la propriété stage d?un DisplayObject présent au sein de la liste d?affichage : monDisplayObject.stage Pour récupérer le nombre d?objets d?affichage contenu dans un objet de type DisplayObjectContainer nous utilisons la propriété numChildren. Ainsi pour récupérer le nombre d?objets d?affichage contenus par l?objet Stage nous écrivons : // affiche : 1 trace( this.stage.numChildren ); Ou bien de manière implicite : // affiche : 1 trace( stage.numChildren ); Nous reviendrons très bientôt sur l?accès à cette propriété qui mérite une attention toute particulière. Notre objet Stage contient un seul enfant, notre scénario principal, l?objet MainTimeline. Ainsi lorsqu?un SWF vide est lu, deux DisplayObject sont présents par défaut dans la liste d?affichage : ? L?objet Stage ? Le scénario du SWF, l?objet MainTimeline Comme l?illustre la figure 3-2, chaque application ActionScript 3 possède un seul objet Stage et donc une seule et unique liste d?affichage. Il faut considérer l?objet Stage comme le n?ud principal de notre liste d?affichage. En réalité celle-ci peut être représentée comme une arborescence XML, un n?ud principal duquel découlent d?autres n?uds enfants, nos objets d?affichages. Lorsque nous posons une occurrence de bouton sur la scène principale de notre animation, nous obtenons la liste d?affichage suivante : Chapitre 4 ? La liste d?affichage ? version 0.1.2 7 / 41 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 3-3. Liste d?affichage avec un bouton. Nous verrons au cours du chapitre 11 intitulé Classe du document comment remplacer la classe MainTimeline par une sous classe graphique personnalisée. Jusqu'à maintenant nous n?avons pas créé d?objet graphique par programmation. ActionScript 3 intègre un nouveau procédé d?instanciation des classes graphiques bien plus efficace que nous allons aborder à présent. A retenir ? L?objet Stage n?est plus accessible de manière globale. ? Nous accédons à l?objet Stage à travers la propriété stage de tout DisplayObject. Instanciation des classes graphiques Une des grandes nouveautés d?ActionScript 3 concerne le procédé d?instanciation des classes graphiques. Avant ActionScript 3, nous devions utiliser diverses méthodes telles createEmptyMovieClip pour la création de flash.display.MovieClip, ou encore createTextField pour la classe flash.text.TextField. Il nous fallait mémoriser plusieurs méthodes ce qui n?était pas forcément évident pour une personne découvrant ActionScript. Désormais, nous utilisons le mot clé new pour instancier tout objet graphique. Le code suivant crée une nouvelle instance de MovieClip. var monClip:MovieClip = new MovieClip(); Chapitre 4 ? La liste d?affichage ? version 0.1.2 8 / 41 Thibault Imbert pratiqueactionscript3.bytearray.org De la même manière, si nous souhaitons créer un champ texte dynamiquement nous écrivons : var monChampTexte:TextField = new TextField(); Pour affecter du texte à notre champ nous utilisons la propriété text de la classe TextField : var monChampTexte:TextField = new TextField(); monChampTexte.text = "Hello World"; L?utilisation des méthodes createTextField ou createEmptyMovieClip nous obligeaient à conserver une référence à un MovieClip afin de pouvoir instancier nos objets graphiques, avec l?utilisation du mot-clé new tout cela n?est qu?un mauvais souvenir. Nous n?avons plus besoin de spécifier de nom d?occurrence ni de profondeur lors de la phase d?instanciation. Bien que notre objet graphique soit créé et réside en mémoire, celui-ci n?a pas encore été ajouté à la liste d?affichage. Si nous testons le code précédent, nous remarquons que le texte n?est pas affiché. Un des comportements les plus troublants lors de la découverte d?ActionScript 3 concerne les objets d?affichage hors liste. A retenir ? Pour qu?un objet graphique soit visible, ce dernier doit obligatoirement être ajouté à la liste d?affichage. ? Tous les objets graphiques s?instancient avec le mot clé new. Ajout d?objets d?affichage En ActionScript 3, lorsqu?un objet graphique est créé, celui-ci n?est pas automatiquement ajouté à la liste d?affichage comme c?était le cas en ActionScript 1 et 2. L?objet existe en mémoire mais ne réside pas dans la liste d?affichage et n?est donc pas rendu. Dans ce cas l?objet est appelé objet d?affichage hors liste. Pour l?afficher nous utilisons une des deux méthodes définies par la classe DisplayObjectContainer appelées addChild et addChildAt. Voici la signature de la méthode addChild : public function addChild(child:DisplayObject):DisplayObject Chapitre 4 ? La liste d?affichage ? version 0.1.2 9 / 41 Thibault Imbert pratiqueactionscript3.bytearray.org La méthode addChild accepte comme seul paramètre une instance de DisplayObject. Pour voir notre objet graphique nous devons l?ajouter à la liste d?objets enfants d?un DisplayObjectContainer présent au sein de la liste d?affichage. Nous pouvons ajouter par exemple l?instance à notre scénario principal : var monChampTexte:TextField = new TextField(); monChampTexte.text = "Hello World"; addChild ( monChampTexte ); Nous obtenons la liste d?affichage illustrée par la figure suivante : Figure 3-4. Liste d?affichage simple avec un champ texte. Ce comportement nouveau est extrêmement puissant. Tout développeur ActionScript a déjà souhaité charger une image ou attacher un objet de la bibliothèque sans pour autant l?afficher. Avec le concept d?objets d?affichage hors liste, un objet graphique peut « vivre » sans pour autant être rendu à l?affichage. Ce comportement offre de nouvelles possibilités en matière d?optimisation de l?affichage. Nous reviendrons sur ce mécanisme lors du chapitre 12 intitulé Programmation bitmap. Réattribution de conteneur En ActionScript 1 et 2, aucune méthode n?existait pour déplacer un objet graphique au sein de la liste d?affichage. Il était impossible de changer son parent. Le seul moyen était de supprimer l?objet graphique puis le recréer au sein du conteneur voulu. Chapitre 4 ? La liste d?affichage ? version 0.1.2 10 / 41 Thibault Imbert pratiqueactionscript3.bytearray.org En ActionScript 3, si nous passons à la méthode addChild un DisplayObject déjà présent au sein de la liste d?affichage, ce dernier est supprimé de son conteneur d?origine et placé dans le nouveau DisplayObjectContainer sur lequel nous avons appelé la méthode addChild. Prenons un exemple simple, nous créons un clip que nous ajoutons à la liste d?affichage en l?ajoutant à notre scénario principal : var monClip:MovieClip = new MovieClip(); // le clip monClip est ajouté au scénario principal addChild ( monClip ); // affiche : 1 trace( numChildren ); La propriété numChildren du scénario principal renvoie 1 car le clip monClip est un enfant du scénario principal. Nous créons maintenant, un objet graphique de type Sprite et nous lui ajoutons en tant qu?enfant notre clip monClip déjà contenu par notre scénario principal : var monClip:MovieClip = new MovieClip(); // le clip monClip est ajouté au scénario principal addChild ( monClip ); // affiche : 1 trace( numChildren ); var monSprite:Sprite = new Sprite(); addChild ( monSprite ); // affiche : 2 trace( numChildren ); monSprite.addChild ( monClip ); // affiche : 1 trace( numChildren ); La propriété numChildren du scénario principal renvoie toujours 1 car le clip monClip a quitté le scénario principal afin d?être placé au sein de notre objet monSprite. Dans le code suivant, l?appel successif de la méthode addChild ne duplique donc pas l?objet graphique mais le déplace de son conteneur d?origine pour le replacer à nouveau : var monClip:MovieClip = new MovieClip(); // le clip monClip est ajouté au scénario principal addChild ( monClip ); Chapitre 4 ? La liste d?affichage ? version 0.1.2 11 / 41 Thibault Imbert pratiqueactionscript3.bytearray.org // le clip monClip quitte le scénario principal // puis est replaçé au sein de ce dernier addChild ( monClip ); // affiche : 1 trace( numChildren ); Il n?existe pas en ActionScript 3 d?équivalent à la méthode duplicateMovieClip existante dans les précédentes versions d?ActionScript. Pour dupliquer un objet graphique, nous utilisons le mot-clé new. La réattribution de conteneurs illustre la puissance apportée par le lecteur Flash 9 et ActionScript 3 en matière de gestion des objets graphiques. Liste interne d?objets enfants Chaque DisplayObjectContainer possède une liste d?objets enfants représentée par un tableau interne. A chaque index se trouve un objet enfant. Visuellement l?objet positionné à l?index 0 est en dessous de l?objet positionné à l?index 1. La figure 3-5 illustre le concept : Figure 3-5. Tableau interne d?accès aux objets enfants. Ce DisplayObjectContainer contient trois objets enfants. A chaque appel, la méthode addChild place l?objet graphique concerné à la fin de la liste d?objets enfants, ce qui se traduit par un empilement de ces derniers. En travaillant avec les différentes méthodes de gestion des objets enfants nous travaillerons de manière transparente avec ce tableau interne. La méthode addChild empile les objets enfants, mais si nous souhaitons gérer l?ordre d?empilement, nous utiliserons la méthode addChildAt dont voici la signature : public function addChildAt(child:DisplayObject, index:int):DisplayObject Chapitre 4 ? La liste d?affichage ? version 0.1.2 12 / 41 Thibault Imbert pratiqueactionscript3.bytearray.org La méthode addChildAt accepte un deuxième paramètre permettant de gérer l?index du DisplayObject au sein de la liste du DisplayObjectContainer. Prenons un exemple simple, nous créons une instance de MovieClip et l?ajoutons à la liste d?affichage : var monClipA:MovieClip = new MovieClip(); addChild ( monClipA ); L?objet graphique monClipA est aussitôt placé à l?index 0. Nous souhaitons alors placer un deuxième clip au même index. La méthode addChildAt place le clip monClipB à l?index 0 déplaçant monClipA à l?index 1 : var monClipA:MovieClip = new MovieClip(); addChild ( monClipA ); var monClipB:MovieClip = new MovieClip(); addChildAt ( monClipB, 0 ); Afin de faire place à l?objet graphique ajouté, les objets enfants déjà présents à l?index spécifié ne sont pas remplacés mais déplacés d?un index. Ce comportement évite d?écraser par erreur un objet graphique présent au sein de la liste d?affichage. Si nous souhaitons reproduire le mécanisme d'écrasement, nous devons supprimer l?objet graphique puis en placer un nouveau à l?index libéré. Si l?index passé à la méthode addChildAt est négatif ou ne correspond à aucun objet enfant : var monClipA:MovieClip = new MovieClip(); addChildAt ( monClipA, 10 ); Une erreur à l?exécution de type RangeError est levée : RangeError: Error #2006: L'index indiqué sort des limites. La méthode addChildAt ne peut donc être utilisée qu?avec un index allant de 0 à numChildren-1. Pour placer un DisplayObject devant tous les autres nous pouvons écrire : addChildAt ( monClipA, numChildren-1 ); Chapitre 4 ? La liste d?affichage ? version 0.1.2 13 / 41 Thibault Imbert pratiqueactionscript3.bytearray.org Aucune profondeur intermédiaire entre deux DisplayObject ne peut demeurer inoccupée. Ce comportement est lié la gestion de la profondeur automatique par le lecteur 9 en ActionScript 3. Nous reviendrons plus tard sur cette notion dans la partie intitulée Effondrement des profondeurs. A retenir ? Il existe une seule et unique liste d?affichage. ? En son sommet se trouve l?objet Stage. ? Tous les objets graphiques s?instancient avec le mot clé new ? Aucune profondeur ou nom d?occurrence n?est nécessaire durant la phase d?instanciation. ? Lorsqu?un objet graphique est créé, il n?est pas ajouté automatiquement à la liste d?affichage. ? Pour voir un objet graphique il faut l?ajouter à la liste d?affichage. ? Pour choisir la position du DisplayObject lors de l?ajout à la liste d?affichage, nous utilisons la méthode addChildAt. Accéder aux objets d?affichage Ouvrez un nouveau document Flash CS3 et créez une simple forme vectorielle à l?aide de l?outil Rectangle. En ciblant la propriété numChildren de notre scène principale, nous récupérons le nombre d?enfants correspondant à la longueur du tableau interne : // affiche : 1 trace( numChildren ); La propriété numChildren nous renvoie 1, car le seul objet contenu par notre scène principale est notre forme vectorielle. Celle-ci a été ajoutée automatiquement à la liste d?affichage. Bien que la forme vectorielle n?ait pas de nom d?occurrence nous pouvons y accéder grâce aux méthodes définies par la classe DisplayObjectContainer. Pour accéder à un objet enfant placé à index spécifique nous avons à disposition la méthode getChildAt : public function getChildAt(index:int):DisplayObject N?oubliez pas que la méthode getChildAt ne fait que pointer dans le tableau interne du DisplayObjectContainer selon l?index passé en paramètre : monDisplayObjectContainer.getChildAt ( index ); Chapitre 4 ? La liste d?affichage ? version 0.1.2 14 / 41 Thibault Imbert pratiqueactionscript3.bytearray.org Pour accéder à la forme vectorielle que nous venons de dessiner, nous ciblons l?index 0 à l?aide de la méthode getChildAt : // accède à l'objet graphique placé à l'index 0 var forme:Shape = getChildAt ( 0 ); En compilant le code précédent, une erreur est levée nous indiquant qu?il est impossible de placer un objet de type DisplayObject au sein d?une variable de type Shape : 1118: Contrainte implicite d'une valeur du type statique flash.display:DisplayObject vers un type peut-être sans rapport flash.display:Shape. Nous tentons de stocker la référence renvoyée par la méthode getChildAt au sein d?un conteneur de type Shape. En relisant la signature de la méthode getChildAt nous pouvons lire que le type retourné par celle-ci est DisplayObject. Flash refuse alors la compilation car nous tentons de stocker un objet de type DisplayObject dans un conteneur de type Shape. Nous pouvons donc indiquer au compilateur que l?objet sera bien un objet de type Shape en utilisant le mot clé as : // accède à l'objet graphique placé à l'index 0 var forme:Shape = getChildAt ( 0 ) as Shape; // affiche : [object Shape] trace( forme ); En réalité, ce code s?avère dangereux car si l?objet placé à l?index 0 est remplacé par un objet graphique non compatible avec le type Shape, le résultat du transtypage échoue et renvoie null. Nous devons donc nous assurer que quelque soit l?objet graphique placé à l?index 0, notre variable soit de type compatible. Dans cette situation, il convient donc de toujours utiliser le type générique DisplayObject pour stocker la référence à l?objet graphique : // accède à l'objet graphique placé à l'index 0 var forme:DisplayObject = getChildAt ( 0 ); // affiche : [object Shape] trace( forme ); De cette manière, nous ne prenons aucun risque, car de par l?héritage, l?objet graphique sera forcément de type DisplayObject. Si nous tentons d?accéder à un index inoccupé : // accède à l'objet graphique placé à l'index 0 var forme:DisplayObject = getChildAt ( 1 ); Chapitre 4 ? La liste d?affichage ? version 0.1.2 15 / 41 Thibault Imbert pratiqueactionscript3.bytearray.org Une erreur de type RangeError est levée, car l?index 1 spécifié ne contient aucun objet graphique : RangeError: Error #2006: L'index indiqué sort des limites. Avant ActionScript 3 lorsqu?un objet graphique était créé à une profondeur déjà occupée, l?objet résidant à cette profondeur était remplacé. Pour éviter tout conflit entre graphistes et développeurs les objets graphiques créés par programmation étaient placés à une profondeur positive et ceux créés dans l?environnement auteur à une profondeur négative. Ce n?est plus le cas en ActionScript 3, tout le monde travaille dans le même niveau de profondeurs. Lorsque nous posons un objet graphique depuis l?environnement auteur de Flash CS3 sur la scène, celui-ci est créé et automatiquement ajouté à la liste d?affichage. En ActionScript 1 et 2 il était impossible d?accéder à une simple forme contenue au sein d?un scénario. Une des forces d?ActionScript 3 est la possibilité de pouvoir accéder à n?importe quel objet de la liste d?affichage. Allons un peu plus loin, et créez sur le scénario principal une seconde forme vectorielle à l?aide de l?outil Ovale, le code suivant nous affiche toujours au total un seul objet enfant : // affiche : 1 trace( numChildren ); Nous aurions pu croire que chaque forme vectorielle créée correspond à un objet graphique différent, il n?en est rien. Toutes les formes vectorielles créées dans l?environnement auteur ne sont en réalité qu?un seul objet Shape. Même si les formes vectorielles sont placées sur plusieurs calques, un seul objet Shape est créé pour contenir la totalité des tracés. Le code suivant rend donc invisible nos deux formes vectorielles : var mesFormes:DisplayObject = getChildAt ( 0 ); mesFormes.visible = false; Rappelez-vous, ActionScript 3 intègre une gestion de la profondeur automatique. A chaque appel de la méthode addChild le DisplayObject passé en paramètre est ajouté au DisplayObjectContainer. Les objets enfants s?ajoutant les uns derrière les autres au sein du tableau interne. Chapitre 4 ? La liste d?affichage ? version 0.1.2 16 / 41 Thibault Imbert pratiqueactionscript3.bytearray.org Chaque objet graphique est placé graphiquement au dessus du précédent. Il n?est donc plus nécessaire de se soucier de la profondeur d?un MovieClip au sein d?une boucle for : var monClip:MovieClip; var i:int; for ( i = 0; i< 10; i++ ) { monClip = new MovieClip(); addChild ( monClip ); } En sortie de boucle, dix clips auront été ajoutés à la liste d?affichage : var monClip:MovieClip; var i:int; for ( i = 0; i< 10; i++ ) { monClip = new MovieClip(); addChild ( monClip ); } // affiche : 10 trace( numChildren ); Pour faire référence à chaque clip nous pouvons ajouter le code suivant : var lng:int = numChildren; for ( i = 0; i< lng; i++ ) { /* affiche : [object MovieClip] [object MovieClip] [object MovieClip] [object MovieClip] [object MovieClip] [object MovieClip] [object MovieClip] [object MovieClip] [object MovieClip] [object MovieClip] */ trace( getChildAt ( i ) ); } Chapitre 4 ? La liste d?affichage ? version 0.1.2 17 / 41 Thibault Imbert pratiqueactionscript3.bytearray.org Nous récupérons le nombre total d?objets enfants avec la propriété numChildren, puis nous passons l?indice i comme index à la méthode getChildAt pour cibler chaque occurrence. Pour récupérer l?index associé à un DisplayObject, nous le passons en référence à la méthode getChildIndex : public function getChildIndex(child:DisplayObject):int Dans le code suivant nous accédons aux clips précédemment créés et récupérons l?index de chacun : var lng:int = numChildren; var objetEnfant:DisplayObject; for ( i = 0; i< lng; i++ ) { objetEnfant = getChildAt( i ); /*affiche : [object MovieClip] à l'index : 0 [object MovieClip] à l'index : 1 [object MovieClip] à l'index : 2 [object MovieClip] à l'index : 3 [object MovieClip] à l'index : 4 [object MovieClip] à l'index : 5 [object MovieClip] à l'index : 6 [object MovieClip] à l'index : 7 [object MovieClip] à l'index : 8 [object MovieClip] à l'index : 9 */ trace ( objetEnfant + " à l'index : " + getChildIndex ( objetEnfant ) ); } Sachez qu?il est toujours possible de donner un nom à un objet graphique. En ActionScript 1 et 2 pour donner un nom à un clip, il fallait spécifier le nom de l?occurrence en tant que paramètre lors de l?appel de la méthode createEmptyMovieClip ou attachMovie. En ActionScript 3, une fois l?objet graphique instancié nous pouvons passer une chaîne de caractères à la propriété name définie par la classe DisplayObject. Le code suivant crée une instance de MovieClip et lui affecte un nom d?occurrence : var monClip:MovieClip = new MovieClip(); monClip.name = "monOcurrence"; addChild ( monClip ); // affiche : monOccurrence Chapitre 4 ? La liste d?affichage ? version 0.1.2 18 / 41 Thibault Imbert pratiqueactionscript3.bytearray.org trace(monClip.name); Pour accéder à notre MovieClip nous pouvons utiliser la méthode getChildByName permettant de cibler un DisplayObject par son nom et non par son index comme le permet la méthode getChildAt. La méthode getChildByName définie par la classe DisplayObjectContainer accepte comme seul paramètre une chaîne de caractères correspondant au nom d?occurrence du DisplayObject auquel nous souhaitons accéder. Voici sa signature : public function getChildByName(name:String):DisplayObject Le code suivant nous permet de cibler notre MovieClip : var monClip:MovieClip = new MovieClip(); monClip.name = "monOcurrence"; addChild ( monClip ); // affiche : [object MovieClip] trace( getChildByName ("monOcurrence") ); La méthode getChildByName traverse récursivement tous les objets de la liste d?objets enfants jusqu?à ce que le DisplayObject soit trouvé. Si l?objet graphique recherché n?est pas présent dans le DisplayObjectContainer concerné, la méthode getChildByName renvoie null : var monClip:MovieClip = new MovieClip(); monClip.name = "monOcurrence"; // affiche : null trace( getChildByName ("monOcurrence") ); Il n?est pas recommandé d?utiliser la méthode getChildByName, celle-ci s?avère beaucoup plus lente à l?exécution que la méthode getChildAt qui pointe directement dans le tableau interne du DisplayObjectContainer. Un simple test met en évidence la différence significative de performances. Au sein d?une boucle nous accédons à un clip présent au sein de la liste d?affichage à l?aide de la méthode getChildByName : var monClip:MovieClip = new MovieClip(); monClip.name = "monOcurrence"; addChild ( monClip ); Chapitre 4 ? La liste d?affichage ? version 0.1.2 19 / 41 Thibault Imbert pratiqueactionscript3.bytearray.org var depart:Number = getTimer(); for ( var i:int = 0; i< 5000000; i++ ) { getChildByName ("monOcurrence"); } var arrivee:Number = getTimer(); // affiche : 854 ms trace( (arrivee - depart) + " ms" ); Notre boucle met environ 854 ms à s?exécuter en utilisant la méthode d?accès getChildByName. Procédons au même test en utilisant cette fois la méthode d?accès getChildAt : var monClip:MovieClip = new MovieClip(); monClip.name = "monOcurrence"; addChild ( monClip ); var depart:Number = getTimer(); for ( var i:int = 0; i< 5000000; i++ ) { getChildAt (0); } var arrivee:Number = getTimer(); // affiche : 466 ms trace( (arrivee - depart) + " ms" ); Nous obtenons une différence d?environ 400 ms entre les deux boucles. Le bilan de ce test nous pousse à utiliser la méthode getChildAt plutôt que la méthode getChildByName. Une question que certains d?entre vous peuvent se poser est la suivante : Si je ne précise pas de nom d?occurrence, quel nom porte mon objet graphique ? Le lecteur Flash 9 procède de la même manière que les précédents lecteurs en affectant un nom d?occurrence par défaut. Prenons l?exemple d?un MovieClip créé dynamiquement : var monClip:MovieClip = new MovieClip(); // affiche : instance1 trace( monClip.name ); Un nom d?occurrence est automatiquement affecté. Chapitre 4 ? La liste d?affichage ? version 0.1.2 20 / 41 Thibault Imbert pratiqueactionscript3.bytearray.org Il est important de noter qu?il est impossible de modifier la propriété name d?un objet créé depuis l?environnement auteur. Dans le code suivant, nous tentons de modifier la propriété name d?un MovieClip créé depuis l?environnement auteur : monClip.name = "nouveauNom"; Ce qui génère l?erreur suivante à la compilation : Error: Error #2078: Impossible de modifier la propriété de nom d'un objet placé sur le scénario. En pratique, cela ne pose pas de réel problème car nous utiliserons très rarement la propriété name qui est fortement liée à la méthode getChildByName. Suppression d?objets d?affichage Pour supprimer un DisplayObject de l?affichage nous appelons la méthode removeChild sur son conteneur, un objet DisplayObjectContainer. La méthode removeChild prend comme paramètre le DisplayObject à supprimer de la liste d?affichage et renvoie sa référence : public function removeChild(child:DisplayObject):DisplayObject Il est essentiel de retenir que la suppression d?un objet graphique est toujours réalisée par l?objet parent. La méthode removeChild est donc toujours appelée sur l?objet conteneur : monConteneur.removeChild ( enfant ); De ce fait, un objet graphique n?est pas en mesure de se supprimer lui- même comme c?était le cas avec la méthode removeMovieClip existante en ActionScript 1 et 2. Il est en revanche capable de demander à son parent de le supprimer : parent.removeChild ( this ); Il est important de retenir que l?appel de la méthode removeChild procède à une simple suppression du DisplayObject au sein de la liste d?affichage mais ne le détruit pas. Un ancien réflexe lié à ActionScript 1 et 2 pourrait vous laisser penser que la méthode removeChild est l?équivalent de la méthode removeMovieClip, ce n?est pas le cas. Pour nous rendre compte de ce comportement, ouvrez un nouveau document Flash CS3 et créez un symbole de type clip. Posez une Chapitre 4 ? La liste d?affichage ? version 0.1.2 21 / 41 Thibault Imbert pratiqueactionscript3.bytearray.org occurrence de ce dernier sur le scénario principal et nommez la monRond. Sur un calque AS nous ajoutons un appel à la méthode removeChild pour supprimer le clip de la liste d?affichage. removeChild ( monRond ); Nous pourrions penser que notre clip monRond a aussi été supprimé de la mémoire. Mais si nous ajoutons la ligne suivante : removeChild ( monRond ); // affiche : [object MovieClip] trace( monRond ); Nous voyons que le clip monRond existe toujours et n?a pas été supprimé de la mémoire. En réalité nous avons supprimé de l?affichage notre clip monRond. Ce dernier n?est plus rendu mais continue d?exister en mémoire. Si le développeur ne prend pas en compte ce mécanisme, les performances d?une application peuvent être mises en péril. Prenons le cas classique suivant : un événement Event.ENTER_FRAME est écouté auprès d?une instance de MovieClip : var monClip:MovieClip = new MovieClip(); monClip.addEventListener( Event.ENTER_FRAME, ecouteur ); addChild ( monClip ); function ecouteur ( pEvt:Event ):void { trace("exécution"); } En supprimant l?instance de MovieClip de la liste d?affichage à l?aide de la méthode removeChild, nous remarquons que l?événement est toujours diffusé : var monClip:MovieClip = new MovieClip(); monClip.addEventListener( Event.ENTER_FRAME, ecouteur ); addChild ( monClip ); function ecouteur ( pEvt:Event ):void { Chapitre 4 ? La liste d?affichage ? version 0.1.2 22 / 41 Thibault Imbert pratiqueactionscript3.bytearray.org trace("exécution"); } removeChild ( monClip ); Pour libérer de la mémoire un DisplayObject supprimé de la liste d?affichage nous devons passer ses références à null et attendre le passage du ramasse-miettes. Rappelez-vous que ce dernier supprime les objets n?ayant plus aucune référence au sein de l?application. Le code suivant supprime notre clip de la liste d?affichage et passe sa référence à null. Ce dernier n?est donc plus référencé au sein de l?application le rendant donc éligible pour le ramasse miettes : var monClip:MovieClip = new MovieClip(); monClip.addEventListener( Event.ENTER_FRAME, ecouteur ); addChild ( monClip ); function ecouteur ( pEvt:Event ):void { trace("exécution"); } removeChild ( monClip ); monClip = null; // affiche : null trace( monClip ); Bien que nous ayons supprimé toutes les références vers notre MovieClip, celui-ci n?est pas supprimé de la mémoire immédiatement. Rappelez-vous que le passage du ramasse-miettes est différé ! Nous ne pouvons pas savoir quand ce dernier interviendra. Le lecteur gère cela de manière autonome selon les ressources système. Il est donc impératif de prévoir un mécanisme de désactivation des objets graphiques, afin que ces derniers consomment un minimum de ressources lorsqu?ils ne sont plus affichés et attendent d?être supprimés par le ramasse-miettes. Durant nos développements, nous pouvons néanmoins déclencher manuellement le passage du ramasse-miettes au sein du lecteur de débogage à l?aide de la méthode gc de la classe System. Chapitre 4 ? La liste d?affichage ? version 0.1.2 23 / 41 Thibault Imbert pratiqueactionscript3.bytearray.org Dans le code suivant, nous déclenchons le ramasse-miettes lors du clic souris sur la scène : var monClip:MovieClip = new MovieClip(); monClip.addEventListener( Event.ENTER_FRAME, ecouteur ); addChild ( monClip ); function ecouteur ( pEvt:Event ):void { trace("exécution"); } removeChild ( monClip ); monClip = null; stage.addEventListener ( MouseEvent.CLICK, clicSouris ); function clicSouris ( pEvt:MouseEvent ):void { // déclenchement du ramasse-miettes System.gc(); } En cliquant sur la scène, l?événement Event.ENTER_FRAME cesse d?être diffusé car le passage du ramasse-miettes a entraîné une suppression définitive du MovieClip. Nous reviendrons sur ce point dans une prochaine partie intitulée Désactivation des objets graphiques. Imaginons le cas suivant : nous devons supprimer un ensemble de clips que nous venons d?ajouter à la liste d?affichage. Ouvrez un nouveau document Flash CS3 et placez plusieurs occurrences de symbole MovieClip sur la scène principale. Il n?est pas nécessaire de leur donner des noms d?occurrences. En ActionScript 1 et 2, nous avions plusieurs possibilités. Une première technique consistait à stocker dans un tableau les références aux clips ajoutés, puis le parcourir pour appeler la méthode removeMovieClip sur chaque référence. Autre possibilité, parcourir le clip conteneur à l?aide d?une boucle for in pour récupérer chaque propriété faisant référence aux clips pour pouvoir les cibler puis les supprimer. Chapitre 4 ? La liste d?affichage ? version 0.1.2 24 / 41 Thibault Imbert pratiqueactionscript3.bytearray.org Lorsque vous souhaitez supprimer un objet graphique positionné à un index spécifique nous pouvons utiliser la méthode removeChildAt dont voici la signature : public function removeChildAt(index:int):DisplayObject En découvrant cette méthode nous pourrions être tentés d?écrire le code suivant : var lng:int = numChildren; for ( var i:int = 0; i< lng; i++ ) { removeChildAt ( i ); } A l?exécution le code précédent génère l?erreur suivante : RangeError: Error #2006: L'index indiqué sort des limites. Une erreur de type RangeError est levée car l?index que nous avons passé à la méthode getChildIndex est considéré comme hors- limite. Cette erreur est levée lorsque l?index passé ne correspond à aucun objet enfant contenu par le DisplayObjectContainer. Notre code ne peut pas fonctionner car nous n?avons pas pris en compte un comportement très important en ActionScript 3 appelé effondrement des profondeurs. A retenir Chapitre 4 ? La liste d?affichage ? version 0.1.2 25 / 41 Thibault Imbert pratiqueactionscript3.bytearray.org ? Chaque DisplayObjectContainer contient un tableau interne contenant une référence à chaque objet enfant. ? Toutes les méthodes de manipulation des objets d?affichage travaillent avec le tableau interne d?objets enfants de manière transparente. ? La profondeur est gérée automatiquement. ? La méthode la plus rapide pour accéder à un objet enfant est getChildAt ? La méthode addChild ne nécessite aucune profondeur et empile chaque objet enfant. ? La méthode removeChild supprime un DisplayObject de la liste d?affichage mais ne le détruit pas. ? Pour supprimer un DisplayObject de la mémoire, nous devons passer ses références à null. ? Le ramasse miettes interviendra quand il le souhaite et supprimera les objets non référencés de la mémoire. Effondrement des profondeurs En ActionScript 1 et 2, lorsqu?un objet graphique était supprimé, les objets placés au dessus de ce dernier conservaient leur profondeur. Ce comportement paraissait tout à fait logique mais soulevait un problème d?optimisation car des profondeurs demeuraient inoccupées entres les objets graphiques. Ainsi les profondeurs 6, 8 et 30 pouvaient êtres occupées alors que les profondeurs intermédiaires restaient inoccupées. En ActionScript 3 aucune profondeur intermédiaire ne peut rester vide au sein de la liste d?affichage. Pour comprendre le concept d?effondrement des objets d?affichage prenons un exemple de la vie courante, une pile d?assiettes. Si nous retirons une assiette située en bas de la pile, les assiettes situées au dessus de celle-ci descendront d?un niveau. Au sein de la liste d?affichage, les objets d?affichage fonctionnent de la même manière. Lorsque nous supprimons un objet d?affichage, les objets situés au dessus descendent alors d?un niveau. On dit que les objets d?affichage s?effondrent. De cette manière aucune profondeur entre deux objets d?affichage ne peut demeurer inoccupée. En prenant en considération cette nouvelle notion, relisons notre code censé supprimer la totalité des objets enfants contenus dans un DisplayObjectContainer : var lng:int = numChildren; Chapitre 4 ? La liste d?affichage ? version 0.1.2 26 / 41 Thibault Imbert pratiqueactionscript3.bytearray.org for ( var i:int = 0; i< lng; i++ ) { removeChildAt ( i ); } A chaque itération la méthode removeChildAt supprime un objet enfant, faisant descendre d?un index tous les objets enfants supérieurs. L?objet en début de pile étant supprimé, le suivant devient alors le premier de la liste d?objets enfants, le même processus se répète alors pour chaque itération. En milieu de boucle, notre index pointe donc vers un index inoccupé levant une erreur de type RangeError. En commençant par le haut de la pile nous n?entraînons aucun effondrement de pile. Nous pouvons supprimer chaque objet de la liste : var lng:int = numChildren-1; for ( var i:int = lng; i>= 0; i-- ) { removeChildAt ( i ); } // affiche : 0 trace( numChildren ); La variable lng stocke l?index du dernier objet enfant, puis nous supprimons chaque objet enfant en commençant par le haut de la pile puis en descendant pour chaque itération. Cette technique fonctionne sans problème, mais une approche plus concise et plus élégante existe. Ne considérons pas l?effondrement automatique comme un inconvénient mais plutôt comme un avantage, nous pouvons écrire : while ( numChildren > 0 ) { removeChildAt ( 0 ); } // affiche : 0 trace( numChildren ); Chapitre 4 ? La liste d?affichage ? version 0.1.2 27 / 41 Thibault Imbert pratiqueactionscript3.bytearray.org Nous supprimons à chaque itération l?objet graphique positionné à l?index 0. Du fait de l?effondrement, chaque objet enfant passera forcément par cet index. A retenir ? La suppression d?un DisplayObject au sein de la liste d?affichage provoque un effondrement des profondeurs. ? L?effondrement des profondeurs permet de ne pas laisser de profondeurs intermédiaires inoccupées entre les objets graphiques. Gestion de l?empilement des objets d?affichage Pour modifier la superposition d?objets enfants au sein d?un DisplayObjectContainer, nous disposons de la méthode setChildIndex dont voici la signature : public function setChildIndex(child:DisplayObject, index:int):void La méthode setChildIndex est appelée sur le conteneur des objets enfants à manipuler. Si l?index passé est négatif ou ne correspond à aucun index occupé, son appel lève une exception de type RangeError. Pour bien comprendre comment fonctionne cette méthode, passons à un peu de pratique. Ouvrez un nouveau document Flash CS3 et créez deux clips de formes différentes. Superposez-les comme l?illustre la figure 3.6 Figure 3-6. Clips superposés Nous donnerons comme noms d?occurrence monRond et monRectangle. Nous souhaitons faire passer le clip monRond devant le clip monRectangle. Nous pouvons en déduire facilement que ce dernier est positionné l?index 1, et le premier à l?index 0. Chapitre 4 ? La liste d?affichage ? version 0.1.2 28 / 41 Thibault Imbert pratiqueactionscript3.bytearray.org Pour placer le rond à l?index 1 nous le passons à la méthode setChildIndex tout en précisant l?index de destination. Sur un calque AS, nous définissons le code suivant : setChildIndex( monRond, 1 ); Le rond est supprimé temporairement de la liste faisant chuter notre rectangle à l?index 0 pour laisser place au rond à l?index 1. Ce dernier est donc placé en premier plan comme l?illustre la figure 3.7 Figure 3-7. Changement de profondeurs. Le changement d?index d?un DisplayObject est divisé en plusieurs phases. Prenons l?exemple de huit objets graphiques. Nous souhaitons passer le DisplayObject de l?index 1 à l?index 6. La figure 3-8 illustre l?idée : Figure 3-8. Changement d?index pour un DisplayObject. Si nous ne connaissons pas le nom des objets graphiques, nous pouvons écrire : setChildIndex ( getChildAt ( 1 ), 6 ); Nous récupérons le DisplayObject à l?index 1 pour le passer à l?index 6. Lorsque ce dernier est retiré de son index d?origine, les Chapitre 4 ? La liste d?affichage ? version 0.1.2 29 / 41 Thibault Imbert pratiqueactionscript3.bytearray.org DisplayObject supérieurs vont alors descendre d?un index. Exactement comme si nous retirions une assiette d?une pile d?assiettes. Il s?agit du comportement que nous avons évoqué précédemment appelé effondrement des profondeurs. La figure 3-9 illustre le résultat du changement d?index du DisplayObject : Figure 3-9. Effondrement des profondeurs. Prenons une autre situation. Cette fois nous souhaitons déplacer un DisplayObject de l?index 5 à l?index 0. La figure 3-10 illustre l?idée : Figure 3-10. Déplacement vers le bas de la pile. Pour procéder à ce déplacement au sein de la liste d?objets enfants nous pouvons écrire : setChildIndex ( monDisplayObject, 0 ); Lorsqu?un DisplayObject est déplacé à un index inférieur à son index d?origine, les objets graphiques intermédiaires ne s?effondrent pas mais remontent d?un index. Le code précédent provoque donc l?organisation présentée par la figure 3-11 : Chapitre 4 ? La liste d?affichage ? version 0.1.2 30 / 41 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 3-11. Objets graphiques poussés d?un index vers le haut de la pile. Lors de l?utilisation de la méthode setChildIndex les objets graphiques intermédiaires compris entre l?index d?origine et d?arrivée peuvent être poussés ou descendus d?un niveau. Echange de profondeurs Dans certaines situations nous pouvons avoir besoin d?intervertir l?ordre d?empilement de deux objets graphiques. Pour cela nous utiliserons les méthodes swapChildren ou swapChildrenAt. Nous allons nous attarder sur la première dont voici la signature : public function swapChildren(child1:DisplayObject, child2:DisplayObject):void La méthode swapChildren accepte deux paramètres de type DisplayObject. Contrairement à la méthode setChildIndex, les méthodes swapChildren et swapChildrenAt ne modifient pas l?index des autres objets graphiques lors de l?échange de profondeur. Nous appelons la méthode swapChildren sur notre scénario principal qui contient nos deux objets graphiques. Le code suivant procède à l?échange des profondeurs : swapChildren ( monRond, monRectangle ); Si nous testons notre animation, nous obtenons l?affichage illustré par la figure 3-12. Chapitre 4 ? La liste d?affichage ? version 0.1.2 31 / 41 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 3-12. Echange des profondeurs. Un appel successif de la méthode swapChildren intervertit à chaque appel les deux DisplayObject. Si nous rajoutons un appel à la méthode swapChildren nous obtenons l?affichage de départ. Sur notre calque, nous rajoutons un deuxième appel à la méthode swapChildren : swapChildren ( monRond, monRectangle ); swapChildren ( monRond, monRectangle ); Nous obtenons l?organisation de départ comme l?illustre la figure 3- 13 : Figure 3-13. Organisation de départ. Lorsque nous n?avons pas de référence aux DisplayObject à intervertir nous pouvons utiliser la méthode swapChildrenAt. Cette dernière accepte deux index en paramètres : public function swapChildrenAt(index1:int, index2:int):void Si l?index est négatif ou n?existe pas dans la liste d?enfants, l?appel de la méthode lèvera une exception de type RangeError. Nous pouvons ainsi arriver au même résultat que notre exemple précédent sans spécifier de nom d?occurrence mais directement l?index des DisplayObject : swapChildrenAt ( 1, 0 ); Si nous avions seulement une référence et un index nous pourrions obtenir le même résultat à l?aide des différentes méthodes que nous avons abordées : swapChildrenAt ( getChildIndex (monRectangle), 0 ); Ou encore, même si l?intérêt est moins justifié : Chapitre 4 ? La liste d?affichage ? version 0.1.2 32 / 41 Thibault Imbert pratiqueactionscript3.bytearray.org swapChildren ( getChildAt (1), getChildAt (0) ); A retenir ? Pour changer l?ordre d?empilement des DisplayObject au sein de la liste d?objets enfants, nous utilisons la méthode setChildIndex. ? La méthode setChildIndex pousse d?un index vers le haut ou vers le bas les autres objets graphiques de la liste d?enfants. ? Pour intervertir l?ordre d?empilement de deux DisplayObject, les méthodes swapChildren et swapChildrenAt seront utilisées. ? Les méthodes swapChildren et swapChildrenAt ne modifient pas l?ordre d?empilements des autres objets graphiques de la liste d?objets enfants. Désactivation des objets graphiques Lors du développement d?applications ActionScript 3, il est essentiel de prendre en considération la désactivation des objets graphiques. Comme nous l?avons vu précédemment, lorsqu?un objet graphique est supprimé de la liste d?affichage, ce dernier continue de « vivre ». Afin de désactiver un objet graphique supprimé de la liste d?affichage, nous utilisons les deux événements suivants : ? Event.ADDED_TO_STAGE : diffusé lorsqu?un objet graphique est affiché. ? Event.REMOVED_FROM_STAGE : diffusé lorsqu?un objet graphique est supprimé de l?affichage. Ces deux événements extrêmement utiles sont diffusés automatiquement lorsque le lecteur affiche ou supprime de l?affichage un objet graphique. Dans le code suivant, la désactivation de l?objet graphique n?est pas gérée, lorsque le MovieClip est supprimé de l?affichage l?événement Event.ENTER_FRAME est toujours diffusé : var monClip:MovieClip = new MovieClip(); monClip.addEventListener( Event.ENTER_FRAME, ecouteur ); addChild ( monClip ); function ecouteur ( pEvt:Event ):void { trace("exécution"); Chapitre 4 ? La liste d?affichage ? version 0.1.2 33 / 41 Thibault Imbert pratiqueactionscript3.bytearray.org } removeChild ( monClip ); En prenant en considération la désactivation de l?objet graphique, nous écoutons l?événement Event.REMOVED_FROM_STAGE afin de supprimer l?écoute de l?événement Event.ENTER_FRAME lorsque l?objet est supprimé de l?affichage : var monClip:MovieClip = new MovieClip(); monClip.addEventListener( Event.ENTER_FRAME, ecouteur ); // écoute de l'événement Event.REMOVED_FROM_STAGE monClip.addEventListener( Event.REMOVED_FROM_STAGE, desactivation ); addChild ( monClip ); function ecouteur ( pEvt:Event ):void { trace("exécution"); } function desactivation ( pEvt:Event ):void { // suppression de l'écoute de l'événement Event.ENTER_FRAME pEvt.target.removeEventListener ( Event.ENTER_FRAME, ecouteur ); } stage.addEventListener ( MouseEvent.CLICK, supprimeAffichage ); function supprimeAffichage ( pEvt:MouseEvent ):void { // lors de la suppression de l'objet graphique // l'événement Event.REMOVED_FROM_STAGE est automatiquement // diffusé par l'objet removeChild ( monClip ); } De cette manière, lorsque l?objet graphique n?est pas affiché, ce dernier ne consomme quasiment aucune ressource car nous avons pris le soin de supprimer l?écoute de tous les événements consommateurs des ressources. A l?inverse, nous pouvons écouter l?événement Event.ADDED_TO_STAGE pour ainsi gérer l?activation et la désactivation de l?objet graphique : var monClip:MovieClip = new MovieClip(); Chapitre 4 ? La liste d?affichage ? version 0.1.2 34 / 41 Thibault Imbert pratiqueactionscript3.bytearray.org // écoute de l'événement Event.REMOVED_FROM_STAGE monClip.addEventListener( Event.REMOVED_FROM_STAGE, desactivation ); // écoute de l'événement Event.ADDED_TO_STAGE monClip.addEventListener( Event.ADDED_TO_STAGE, activation ); addChild ( monClip ); function ecouteur ( pEvt:Event ):void { trace("exécution"); } function activation ( pEvt:Event ):void { // écoute de l'événement Event.ENTER_FRAME pEvt.target.addEventListener ( Event.ENTER_FRAME, ecouteur ); } function desactivation ( pEvt:Event ):void { // suppression de l'écoute de l'événement Event.ENTER_FRAME pEvt.target.removeEventListener ( Event.ENTER_FRAME, ecouteur ); } stage.addEventListener ( MouseEvent.CLICK, supprimeAffichage ); function supprimeAffichage ( pEvt:MouseEvent ):void { // lors de la suppression de l'objet graphique // l'événement Event.REMOVED_FROM_STAGE est automatiquement // diffusé par l'objet if ( contains ( monClip ) ) removeChild ( monClip ); // lors de l'affichage de l'objet graphique // l'événement Event.ADDED_TO_STAGE est automatiquement // diffusé par l'objet else addChild ( monClip ); } Au sein de la fonction écouteur supprimeAffichage, nous testons si le scénario contient le clip monClip à l?aide de la méthode contains. Si ce n?est pas le cas nous l?affichons, dans le cas inverse nous le supprimons de l?affichage. Nous reviendrons sur le concept d?activation et désactivation des objets graphiques tout au long de l?ouvrage. Chapitre 4 ? La liste d?affichage ? version 0.1.2 35 / 41 Thibault Imbert pratiqueactionscript3.bytearray.org Afin de totalement désactiver notre MovieClip nous devons maintenant supprimer les références pointant vers lui. Pour cela, nous modifions la fonction supprimeAffichage : function supprimeAffichage ( pEvt:MouseEvent ):void { // lors de la suppression de l'objet graphique // l'événement Event.REMOVED_FROM_STAGE est automatiquement // diffusé par l'objet, l'objet est désactivé removeChild ( monClip ); // suppression de la référence pointant vers le MovieClip monClip = null; } Certains d?entre vous peuvent se demander l?intérêt de ces deux événements par rapport à une simple fonction personnalisée qui se chargerait de désactiver l?objet graphique. Au sein d?une application, une multitude de fonctions ou méthodes peuvent entraîner la suppression d?un objet graphique. Grâce à ces deux événements, nous savons que, quelque soit la manière dont l?objet est supprimé ou affiché, nous le saurons et nous pourrons gérer son activation et sa désactivation. Nous allons nous attarder au cours de cette nouvelle partie, au comportement de la tête de lecture afin de saisir tout l?intérêt des deux événements que nous venons d?aborder. Fonctionnement de la tête de lecture Dû au nouveau mécanisme de liste d?affichage du lecteur Flash 9. Le fonctionnement interne de la tête de lecture a lui aussi été modifié. Ainsi lorsque la tête de lecture rencontre une image ayant des objets graphiques celle-ci ajoute les objets graphiques à l?aide de la méthode addChild : Figure 3-14. Ajout d?objets graphiques. Chapitre 4 ? La liste d?affichage ? version 0.1.2 36 / 41 Thibault Imbert pratiqueactionscript3.bytearray.org A l?inverse lorsque la tête de lecture rencontre une image clé vide, le lecteur supprime les objets graphiques à l?aide de la méthode removeChild : Figure 3-15. Suppression d?objets graphiques. De par la suppression ou ajout des objets graphiques, ces derniers diffusent automatiquement les événements Event.ADDED_TO_STAGE et Event.REMOVED_FROM_STAGE. Nous reviendrons sur ce mécanisme d?activation et désactivation des objets graphiques au cours du chapitre 9 intitulé Etendre les classes natives. A retenir ? Il est fortement recommandé de gérer l?activation et la désactivation des objets graphiques. ? Pour cela, nous utilisons les événements Event.ADDED_TO_STAGE et Event.REMOVED_FROM_STAGE. ? Si de nouveaux objets sont placés sur une image clé, la tête de lecture les ajoute à l?aide de la méthode addChild. ? Si la tête de lecture rencontre une image clé vide, le lecteur supprime automatiquement les objets graphiques à l?aide de la méthode removeChild. Subtilités de la propriété stage Nous allons revenir dans un premier temps sur la propriété stage définie par la classe DisplayObject. Nous avons appris précédemment que l?objet Stage n?est plus accessible de manière globale comme c?était le cas en ActionScript 1 et 2. Pour accéder à l?objet Stage nous ciblons la propriété stage sur n?importe quel DisplayObject : monDisplayObject.stage Chapitre 4 ? La liste d?affichage ? version 0.1.2 37 / 41 Thibault Imbert pratiqueactionscript3.bytearray.org La subtilité réside dans le fait que ce DisplayObject doit obligatoirement être ajouté à la liste d?affichage afin de pouvoir retourner une référence à l?objet Stage. Ainsi, si nous ciblons la propriété stage sur un DisplayObject non présent dans la liste d?affichage, celle-ci nous renvoie null : var monClip:MovieClip = new MovieClip(); // affiche : null trace( monClip.stage ); Une fois notre clip ajouté à la liste d?affichage, la propriété stage contient une référence à l?objet Stage : var monClip:MovieClip = new MovieClip(); addChild ( monClip ); // affiche : [object Stage] trace( monClip.stage ); Grâce à l?événement Event.ADDED_TO_STAGE, nous pouvons accéder de manière sécurisée à l?objet Stage, car la diffusion de cet événement nous garantit que l?objet graphique est présent au sein de la liste d?affichage. Figure 3-16. Objet Stage. Contrairement aux scénarios qui peuvent être multiples dans le cas de multiples SWF, l?objet Stage est unique. Chapitre 4 ? La liste d?affichage ? version 0.1.2 38 / 41 Thibault Imbert pratiqueactionscript3.bytearray.org Afin de tester si un objet graphique est affiché actuellement, il suffit donc de tester si sa propriété renvoie null ou non. A retenir ? La propriété stage d?un DisplayObject renvoie null, tant que ce dernier n?est pas placé au sein de la liste d?affichage. Subtilités de la propriété root Les développeurs ActionScript 1 et 2 se souviennent surement de la propriété _root. En ActionScript 3 son fonctionnement a été entièrement revu, rendant son utilisation totalement sécurisée. Souvenez vous, la propriété root était accessible de manière globale et permettait de référencer rapidement le scénario principal d?une animation. Son utilisation était fortement déconseillée pour des raisons de portabilité de l?application développée. Prenons un cas typique : nous développions une animation en ActionScript 1 ou 2 qui était plus tard chargée dans un autre SWF. Si nous ciblions notre scénario principal à l?aide de _root, une fois l?animation chargée par le SWF, _root pointait vers le scénario du SWF chargeur et non plus vers le scénario de notre SWF chargé. C?est pour cette raison qu?il était conseillé de toujours travailler par ciblage relatif à l?aide de la propriété _parent et non par ciblage absolu avec la propriété _root. Comme pour la propriété stage, la propriété root renvoie null tant que le DisplayObject n?a pas été ajouté à la liste d?affichage. var monClip:MovieClip = new MovieClip(); // affiche : null trace( monClip.root ); Pour que la propriété root pointe vers le scénario auquel le DisplayObject est rattaché il faut impérativement l?ajouter à la liste d?affichage. Si l?objet graphique est un enfant du scénario principal, c'est-à-dire contenu par l?objet MainTimeline, alors sa propriété root pointe vers ce dernier. Le code suivant est défini sur le scénario principal : var monClip:MovieClip = new MovieClip(); // le clip monClip est ajouté au scénario principal addChild ( monClip ); Chapitre 4 ? La liste d?affichage ? version 0.1.2 39 / 41 Thibault Imbert pratiqueactionscript3.bytearray.org // affiche : [object MainTimeline] trace( monClip.root ); Si l?objet graphique n?est pas un enfant du scénario principal, mais un enfant de l?objet Stage, sa propriété root renvoie une référence vers ce dernier : var monClip:MovieClip = new MovieClip(); // le clip monClip est ajouté à l'objet Stage stage.addChild ( monClip ); // affiche : [object Stage] trace( monClip.root ); Ainsi, lorsque l?objet graphique n?est pas un enfant du scénario principal, les propriétés root et stage sont équivalentes car pointent toutes deux vers l?objet Stage. L?usage de la propriété root en ActionScript 3 est tout à fait justifié et ne pose aucun problème de ciblage comme c?était le cas en ActionScript 1 et 2. En ActionScript 3, la propriété root référence le scénario principal du SWF en cours. Même dans le cas de chargement externe, lorsqu?un SWF est chargé au sein d?un autre, nous pouvons cibler la propriété root sans aucun problème, nous ferons toujours référence au scénario du SWF en cours. La figure 3-17 illustre l?idée : Figure 3-17. Scénarios. Chapitre 4 ? La liste d?affichage ? version 0.1.2 40 / 41 Thibault Imbert pratiqueactionscript3.bytearray.org Nous reviendrons sur ce cas plus tard au cours du chapitre 13 intitulé Chargement de contenu externe. A retenir ? La propriété root d?un DisplayObject renvoie null, tant que ce dernier n?est pas placé au sein de la liste d?affichage. ? Lorsque l?objet graphique n?est pas un enfant du scénario principal, les propriétés root et stage renvoient toutes deux une référence à l?objet Stage. ? La propriété root référence le scénario du SWF en cours. ? En ActionScript 3 l?usage de la propriété root ne souffre pas des faiblesses existantes dans les précédentes versions d?ActionScript. Les _level En ActionScript 3 la notion de _level n?est plus disponible, souvenez vous, en ActionScript 1 et 2 nous pouvions écrire : _level0.createTextField ( "monChampTexte", 0, 0, 0, 150, 25 ); Ce code crée un champ texte au sein du _level0. En ActionScript 3 si nous tentons d?accéder à l?objet _level, nous obtenons une erreur à la compilation : var monChampTexte:TextField = new TextField(); monChampTexte.text = "Bonjour !"; _level0.addChild ( monChampTexte ); Affiche dans la fenêtre de sortie : 1120: Accès à la propriété non définie _level0. Il n?existe pas en ActionScript 3 d?objet similaire aux _level que nous connaissions. En revanche le même résultat est facilement réalisable en ActionScript 3. En ajoutant nos objets graphiques à l?objet Stage plutôt qu?à notre scénario, nous obtenons le même résultat : var monChampTexte:TextField = new TextField(); monChampTexte.text = "Bonjour !"; stage.addChild ( monChampTexte ); Le code précédent définit la liste d?affichage illustrée par la figure suivante : Chapitre 4 ? La liste d?affichage ? version 0.1.2 41 / 41 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 3-17. Simulation de _level. Notre champ texte est ici un enfant direct de l?objet Stage, nous obtenons donc le même concept d?empilement de scénario établit par les _level. Dans cette situation, l?objet Stage contient alors deux enfants directs : var monChampTexte:TextField = new TextField(); monChampTexte.text = "Bonjour !"; stage.addChild ( monChampTexte ); // affiche : 2 trace ( stage.numChildren ); Même si cette technique nous permet de simuler la notion de _level en ActionScript 3, son utilisation n?est pas recommandée sauf cas spécifique, comme le cas d?une fenêtre d?alerte qui devrait être affichée au dessus de tous les éléments de notre application. A retenir ? En ActionScript 3, la notion de _level n?existe plus. Chapitre 5 ? Symboles prédéfinis ? version 0.1.2 1 / 31 Thibault Imbert pratiqueactionscript3.bytearray.org 5 Symboles prédéfinis LES TYPES DE SYMBOLE................................................................................... 1 LE SYMBOLE CLIP.................................................................................................... 2 LA PROPRIÉTÉ NAME ............................................................................................... 4 INSTANCIATION DE SYMBOLES PAR PROGRAMMATION...................... 6 INSTANCIATION DE SYMBOLES PRÉDÉFINIS ............................................................. 8 EXTRAIRE UNE CLASSE DYNAMIQUEMENT ............................................................ 13 LE SYMBOLE BOUTON ........................................................................................... 14 LE SYMBOLE BOUTON ........................................................................................... 21 LE SYMBOLE GRAPHIQUE ...................................................................................... 22 LES IMAGES BITMAP.............................................................................................. 24 AFFICHER UNE IMAGE BITMAP.................................................................... 26 LE SYMBOLE SPRITE........................................................................................ 28 DEFINITION DU CODE DANS UN SYMBOLE............................................... 30 Les types de symbole Durant nos développements ActionScript nous avons très souvent besoin d?utiliser des symboles prédéfinis. Créés depuis l?environnement auteur de Flash, un logo, une animation, ou encore une image pourront être stockés au sein de la bibliothèque pour une utilisation future durant l?exécution de l?application. Pour convertir un objet graphique en symbole, nous sélectionnons ce dernier et appuyons sur F8. Le panneau convertir en symbole s?ouvre, comme l?illustre la figure 5.1 : Chapitre 5 ? Symboles prédéfinis ? version 0.1.2 2 / 31 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 5-1. Panneau Convertir en symbole. Trois types de symboles sont proposés : ? Clip ? Bouton ? Graphique Le type bitmap existe aussi pour les symboles, mais n?apparait pas ici car il est impossible de créer un bitmap depuis le panneau Convertir en symbole. Seules les images importées dans la bibliothèque sont intégrées en tant que type bitmap. Le symbole clip s?avère être le plus courant dans les développements, nous allons commencer par ce dernier en découvrant les nouveautés apportées par ActionScript 3. Le symbole clip Le symbole clip est sans doute l?objet graphique le plus utilisé dans les animations Flash. Si son fonctionnement n?a pas changé en ActionScript 3, son instanciation et sa manipulation par programmation a été entièrement revue. Dans un nouveau document Flash CS3 nous créons un symbole de type clip que nous appelons Balle. Celui-ci est aussitôt ajouté à la bibliothèque, comme l?illustre la figure 5-2 : Chapitre 5 ? Symboles prédéfinis ? version 0.1.2 3 / 31 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 5-2. Symbole clip en bibliothèque. En posant une occurrence de ce dernier sur la scène nous avons la possibilité de lui affecter à travers l?inspecteur de propriétés un nom d?occurrence comme l?illustre la figure 5-3 : Chapitre 5 ? Symboles prédéfinis ? version 0.1.2 4 / 31 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 5-3. Occurrence du symbole clip sur la scène principale. Aussitôt le nom d?occurrence affecté, Flash ajoutera au scénario concerné une propriété du même nom pointant vers l?occurrence. Le code suivant nous permet d?accéder à cette dernière : // affiche : [object MovieClip] trace( maBalle ); Si nous définissons une variable portant le même nom qu?un nom d?occurrence, une erreur à la compilation est générée nous indiquant un conflit de variables. Cette sécurité évite de définir une variable portant le même nom qu?une occurrence ce qui provoquait avec les précédentes versions d?ActionScript un écrasement de variables difficile à déboguer. Prenons le cas suivant : au sein d?une animation ActionScript 1 ou 2 un clip posé sur la scène possédait monMc comme nom d?occurrence. Le code suivant retournait une référence vers ce dernier : // affiche : _level0.monMc trace( monMc ); Si une variable du même nom était définie, l?accès à notre clip était perdu : var monMc:String = "bob"; // affiche : bob trace( monMc ); En ActionScript 3 cette situation ne peut pas se produire grâce à la gestion des conflits de variables à la compilation. Ici, nous définissons une variable appelée maBalle sur le même scénario que notre occurrence : var maBalle:MovieClip; L?erreur à la compilation suivante est générée : 1151: Conflit dans la définition maBalle dans l'espace de nom internal. Nous allons nous intéresser à présent à quelques subtilités liées à la propriété name de la classe flash.display.DisplayObject. La propriété name Lors du chapitre 3 intitulé La liste d?affichage nous avons vu que la propriété name d?un DisplayObject pouvait être modifiée dynamiquement. Chapitre 5 ? Symboles prédéfinis ? version 0.1.2 5 / 31 Thibault Imbert pratiqueactionscript3.bytearray.org Une autre nouveauté d?ActionScript 3 intervient dans la manipulation de la propriété name des occurrences de symboles placées depuis l?environnement auteur de Flash CS3. En ActionScript 1 et 2 nous pouvions modifier dynamiquement le nom d?un objet graphique grâce à la propriété _name. En procédant ainsi nous changions en même temps la variable permettant de le cibler. Dans l?exemple suivant, un clip possédait monMc comme nom d?occurrence : // affiche : monMc trace( monMc._name ) ; monMc._name = "monNouveauNom"; // affiche : _level0.monNouveauNom trace( monNouveauNom ); // affiche : undefined trace( monMc ); En ActionScript 3, cela n?est pas possible pour les occurrences de symboles placées sur la scène manuellement. Seuls les symboles graphiques créés par programmation permettent la modification de leur propriété name. Dans le code suivant nous tentons de modifier la propriété name d?une occurrence de symbole posée manuellement : // affiche : maBalle trace( maBalle.name ); maBalle.name = "monNouveauNom"; Ce qui génère l?erreur suivante à l?exécution : Error: Error #2078: Impossible de modifier la propriété de nom d'un objet placé sur le scénario. La liaison qui existait en ActionScript 1 et 2 entre la propriété _name et l?accès à l?occurrence n?est plus valable en ActionScript 3. Si nous créons un Sprite en ActionScript 3, et que nous lui affectons un nom par la propriété name, aucune variable du même nom n?est créée pour y accéder : var monSprite:Sprite = new Sprite; monSprite.name = "monNomOccurrence"; trace( monNomOccurrence ); L?erreur à la compilation suivante est générée : Chapitre 5 ? Symboles prédéfinis ? version 0.1.2 6 / 31 Thibault Imbert pratiqueactionscript3.bytearray.org 1120: Accès à la propriété non définie monNomOccurrence. Pour y accéder par ce nom, nous utilisons la méthode getChildByName définie par la classe flash.display.DisplayObjectContainer. var monSprite:Sprite = new Sprite; monSprite.name = "monNomOccurrence"; addChild ( monSprite ); // affiche : [object Sprite] trace( getChildByName ("monNomOccurrence") ); Une autre nouveauté d?ActionScript 3 concerne la suppression des objets graphiques posés depuis l?environnement auteur. En ActionScript 1 et 2 il était normalement impossible de supprimer de l?affichage ces derniers. Les développeurs devaient utiliser une astuce consistant à passer à une profondeur positive l?objet avant d?appeler une méthode comme removeMovieClip. En ActionScript 3 nous pouvons supprimer tous les objets graphiques posés en dur sur la scène à l?aide de la méthode removeChild. Le code suivant supprime notre occurrence de Balle posée manuellement : removeChild ( maBalle ); Alors que les graphistes se contenteront d?animer et manipuler des occurrences de symboles manuellement, un développeur voudra manipuler par programmation les symboles présents au sein de la bibliothèque. Découvrons ensemble le nouveau mécanisme apporté par ActionScript 3. A retenir ? Il est impossible de modifier la propriété name d?un objet graphique placé manuellement sur la scène. Instanciation de symboles par programmation Comme en ActionScript 1 et 2, un symbole clip ne peut être attaché dynamiquement par programmation si l?option Exporter pour ActionScript du panneau Propriétés de liaison n?est pas activée. Notons qu?en réalité nous ne donnons plus de nom de liaison au clip en ActionScript 3, mais nous spécifions désormais un nom de classe. Chapitre 5 ? Symboles prédéfinis ? version 0.1.2 7 / 31 Thibault Imbert pratiqueactionscript3.bytearray.org En mode de publication ActionScript 3, le champ Identifiant est grisé, nous ne l?utiliserons plus. En sélectionnant notre symbole Balle dans la bibliothèque nous choisissons l?option Liaison, puis nous cochons la case Exporter pour ActionScript. La figure 5-4 illustre le panneau Propriétés de Liaison : Figure 5-4. Panneau Propriétés de liaison. Le champ Classe de base indique la classe dont notre symbole héritera, une fois l?option Exporter pour ActionScript cochée, ce dernier est renseigné automatiquement. Notre symbole Balle est un clip et hérite donc de la classe flash.display.MovieClip. Le champ Classe contient par défaut le nom du symbole en bibliothèque. La grande nouveauté ici, est la possibilité de définir un nom de classe associée au symbole. Nous conservons Balle. En cliquant sur OK pour valider, Flash CS3 recherche aussitôt une classe appelée Balle à proximité de notre fichier .fla. S?il ne trouve aucune classe de ce nom il en génère une automatiquement. Le panneau de la figure 5-5 nous rapporte cette information : Chapitre 5 ? Symboles prédéfinis ? version 0.1.2 8 / 31 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 5-5. Panneau de génération automatique de classe. Attention, cette classe ne sera pas accessible pour l?édition. Elle sera utilisée en interne par le compilateur pour l?instanciation de notre symbole. Instanciation de symboles prédéfinis En ActionScript 1 et 2 la méthode attachMovie permettait d?attacher des symboles par programmation. Dès le départ cette méthode n?était pas très simple à appréhender, ses nombreux paramètres rendaient sa mémorisation difficile. De plus, nous étions obligés d?appeler cette méthode sur une occurrence de MovieClip, nous forçant à conserver une référence à un clip pour pouvoir instancier d?autres clips issus de la bibliothèque. En ActionScript 3 le mot-clé new nous permet aussi d?instancier des symboles présents dans la bibliothèque et offre donc beaucoup plus de souplesse que la méthode attachMovie. De n?importe où, sans aucune référence à un MovieClip existant, nous pouvons écrire le code suivant pour instancier notre symbole Balle : var maBalle:MovieClip = new Balle(); L?utilisation du mot clé new pour l?instanciation des objets graphiques et plus particulièrement des symboles résout une autre grande faiblesse d?ActionScript 2 que nous traiterons plus tard dans le chapitre 9 intitulé Etendre les classes natives. Nous avons utilisé le type MovieClip pour stocker la référence à notre occurrence de symbole Balle, alors que ce symbole possède un type spécifique que nous avons renseigné à travers le panneau Propriétés de liaison. Chapitre 5 ? Symboles prédéfinis ? version 0.1.2 9 / 31 Thibault Imbert pratiqueactionscript3.bytearray.org Dans la ligne ci-dessous, nous typons la variable à l?aide du type Balle : var maBalle:Balle = new Balle(); En testant le type de notre clip Balle nous voyons que ce dernier possède plusieurs types communs : var maBalle:Balle = new Balle(); // affiche : true trace( maBalle is MovieClip ); // affiche : true trace( maBalle is Balle ); Souvenons-nous que notre objet graphique est, pour le moment hors de la liste d?affichage. Pour le voir nous ajoutons notre symbole à notre scénario principal à l?aide de la méthode addChild : var maBalle:Balle = new Balle(); addChild ( maBalle ); Notre symbole est positionné en coordonnées 0 pour l?axe des x et 0 pour l?axe des y. Notre instance de la classe Balle est donc constituée d?une enveloppe MovieClip contenant notre forme vectorielle, ici de forme circulaire. Il parait donc logique que cette enveloppe contienne un objet graphique enfant de type flash.display.Shape. Le code suivant récupère le premier objet enfant de notre clip Balle à l?aide de la méthode getChildAt : var maBalle:Balle = new Balle(); addChild (maBalle); // affiche : [object Shape] trace( maBalle.getChildAt ( 0 ) ); Nous pourrions supprimer le contenu de notre clip avec la méthode removeChildAt : var maBalle:Balle = new Balle(); addChild (maBalle); // affiche : [object Shape] trace( maBalle.removeChildAt ( 0 ) ); Comme nous le découvrons depuis le début de cet ouvrage, ActionScript 3 offre une souplesse sans précédent pour la manipulation des objets graphiques. Chapitre 5 ? Symboles prédéfinis ? version 0.1.2 10 / 31 Thibault Imbert pratiqueactionscript3.bytearray.org Il serait intéressant d?instancier plusieurs objets Balle et de les positionner aléatoirement sur la scène avec une taille différente. Pour cela, nous allons au sein d?une boucle for créer de multiples instances de la classe Balle et les ajouter à la liste d?affichage : var maBalle:Balle; for ( var i:int = 0; i< 10; i++ ) { maBalle = new Balle(); addChild( maBalle ); } Si nous testons le code précédent nous obtenons dix instances de notre classe Balle positionnées en 0,0 sur notre scène. Il s?agit d?un comportement qui a toujours existé dans le lecteur Flash. Tous les objets affichés sont positionnés par défaut en coordonnées 0 pour les axes x et y. Nous allons les positionner aléatoirement sur la scène à l?aide de la méthode Math.random(). Nous devons donc récupérer la taille totale de la scène pour savoir sur quelle amplitude générer notre valeur aléatoire pour les axes x et y. En ActionScript 1 et 2 nous aurions écrit : maBalle._x = Math.random()*Stage.width; maBalle._y = Math.random()*Stage.height; En ActionScript 1 et 2, les propriétés Stage.width et Stage.height nous renvoyaient la taille de la scène. En ActionScript 3 l?objet Stage possède quatre propriétés relatives à sa taille, ce qui peut paraître relativement déroutant. Les deux propriétés width et height existent toujours mais renvoient la largeur et hauteur occupée par l?ensemble des DisplayObject contenus par l?objet Stage. La figure 5-6 illustre l?idée : Chapitre 5 ? Symboles prédéfinis ? version 0.1.2 11 / 31 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 5-6. Comportement des propriétés stage.width et stage.height. Ainsi dans un SWF vide, les propriétés stage.width et stage.height renvoient 0. La figure 5-6 montre une animation où trois instances du symbole Balle sont posées sur la scène. Les propriétés stage.width et stage.height renvoient la surface occupée par les objets graphiques présents dans la liste d?affichage. Pour récupérer la taille totale de la scène et non la surface occupée par les objets graphiques nous utilisons les propriétés stage.stageWidth et stage.stageHeight : // affiche : 550 trace( stage.stageWidth ); // affiche : 400 trace( stage.stageHeight ); En générant une valeur aléatoire sur la largueur et la hauteur totale nous positionnons aléatoirement nos instances du symbole Balle : var maBalle:Balle; for ( var i:int = 0; i< 10; i++ ) { Chapitre 5 ? Symboles prédéfinis ? version 0.1.2 12 / 31 Thibault Imbert pratiqueactionscript3.bytearray.org maBalle = new Balle(); maBalle.x = Math.random()*stage.stageWidth; maBalle.y = Math.random()*stage.stageHeight; addChild( maBalle ); } Le code génère l?animation suivante : Figure 5-7. Positionnement aléatoire des instances de la classe Balle. Si nous ne souhaitons pas voir nos occurrences sortir de la scène, nous allons intégrer une contrainte en générant un aléatoire prenant en considération la taille des occurrences. var maBalle:Balle; for ( var i:int = 0; i< 10; i++ ) { maBalle = new Balle(); maBalle.x = Math.random()*(stage.stageWidth - maBalle.width); maBalle.y = Math.random()*(stage.stageHeight - maBalle.height); addChild( maBalle ); } La figure 5-8 illustre le résultat : Chapitre 5 ? Symboles prédéfinis ? version 0.1.2 13 / 31 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 5-8. Positionnement aléatoire sans débordement des instances de la classe Balle. A retenir ? Pour instancier un symbole prédéfini, nous utilisons le mot-clé new. ? Les propriétés stage.width et stage.height renvoient la taille occupée par les DisplayObject présent au sein de la liste d?affichage. ? Pour récupérer les dimensions de la scène, nous utilisons les propriétés stage.stageWidth et stage.stageHeight. Extraire une classe dynamiquement Dans les précédentes versions d?ActionScript, nous pouvions stocker les noms de liaisons de symboles au sein d?un tableau, puis boucler sur ce dernier afin d?attacher les objets graphiques : // tableau contenant les identifiants de liaison des symboles var tableauLiaisons:Array = ["polygone", "balle", "polygone", "carre", "polygone", "carre", "carre"]; var lng:Number = tableauLiaisons.length; var ref:MovieClip; for ( var i:Number = 0; i< lng; i++ ) { // affichage des symboles ref = this.attachMovie ( tableauLiaisons[i], tableauLiaisons[i] + i, i ); Chapitre 5 ? Symboles prédéfinis ? version 0.1.2 14 / 31 Thibault Imbert pratiqueactionscript3.bytearray.org } De par l?utilisation du mot-clé new afin d?instancier les objets graphiques, nous ne pouvons plus instancier un objet graphique à l?aide d?une simple chaîne de caractères. Pour réaliser le code équivalent en ActionScript 3, nous devons au préalable extraire une définition de classe, puis instancier cette définition. Pour cela nous utilisons la fonction flash.utils.getDefinitionByName : // tableau contenant le noms des classes var tableauLiaisons:Array = ["Polygone", "Balle", "Polygone", "Carre", "Polygone", "Carre", "Carre"]; var lng:Number = tableauLiaisons.length; var Reference:Class; for ( var i:Number = 0; i< lng; i++ ) { // extraction des références de classe Reference = Class ( getDefinitionByName ( tableauLiaisons[i] ) ); // instanciation var instance:DisplayObject = DisplayObject ( new Reference() ); // ajout à la liste d'affichage addChild ( instance ); } Cette fonctionnalité permet l?évaluation du nom de la classe à extraire de manière dynamique. Nous pourrions imaginer un fichier XML contenant le nom des différentes classes à instancier. Un simple fichier XML pourrait ainsi décrire toute une interface graphique en spécifiant les objets graphiques devant êtres instanciés. A retenir ? La fonction getDefinitionByName permet d?extraire une définition de classe de manière dynamique. Le symbole bouton Lorsque nous créons un bouton dans l?environnement auteur, celui-ci n?est plus de type Button comme en ActionScript 1 et 2 mais de type flash.display.SimpleButton. Chapitre 5 ? Symboles prédéfinis ? version 0.1.2 15 / 31 Thibault Imbert pratiqueactionscript3.bytearray.org Il est tout à fait possible de créer et d?enrichir graphiquement des boutons par programmation, chose impossible avec les précédents lecteurs Flash. Nous reviendrons sur cette fonctionnalité dans le prochain chapitre 7 intitulé Interactivité. Nous allons créer un bouton dans le document en cours, puis poser une occurrence de ce dernier sur la scène principale. Dans notre exemple le bouton est de forme rectangulaire, illustrée en figure 5-9 : Figure 5-9. Bouton posé sur la scène. Nous lui donnons monBouton comme nom d?occurrence puis nous testons le code suivant : // affiche : [object SimpleButton] trace( monBouton ); En testant notre animation nous remarquons que notre bouton monBouton affiche un curseur représentant un objet cliquable lors du survol. Pour déclencher une action spécifique lorsque nous cliquons dessus, nous utilisons le modèle événementiel que nous avons découvert ensemble au cours du chapitre 3 intitulé Le modèle événementiel. L?événement diffusé par l?objet SimpleButton lorsqu?un clic est détecté est l?événement MouseEvent.CLICK. Nous souscrivons une fonction écouteur auprès de ce dernier en ciblant la classe flash.events.MouseEvent : monBouton.addEventListener( MouseEvent.CLICK, clicBouton ); Chapitre 5 ? Symboles prédéfinis ? version 0.1.2 16 / 31 Thibault Imbert pratiqueactionscript3.bytearray.org function clicBouton ( pEvt:MouseEvent ):void { // affiche : [object SimpleButton] trace( pEvt.target ); } Notre fonction clicBouton s?exécute à chaque clic effectué sur notre bouton monBouton. La propriété target de l?objet événementiel diffusé nous renvoie une référence à l?objet auteur de l?événement, ici notre bouton. Afin d?attacher dynamiquement nos instances du symbole Balle lors du clic sur notre bouton monBouton, nous plaçons au sein de la fonction clicBouton le processus d?instanciation auparavant créé : monBouton.addEventListener( MouseEvent.CLICK, clicBouton ); function clicBouton ( pEvt:MouseEvent ):void { var maBalle:Balle for ( var i:int = 0; i< 10; i++ ) { maBalle = new Balle(); maBalle.x = Math.random()*(stage.stageWidth - maBalle.width); maBalle.y = Math.random()*(stage.stageHeight - maBalle.height); addChild ( maBalle ); } } Au clic souris sur notre bouton, dix instances du symbole Balle sont attachées sur le scénario principal. Comme l?illustre la figure 5-10 : Chapitre 5 ? Symboles prédéfinis ? version 0.1.2 17 / 31 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 5-10. Occurrences de symbole Balle. Nous risquons d?être confrontés à un problème de chevauchement, il serait intéressant de remonter notre bouton au premier niveau pour éviter qu?il ne soit masqué par les instances de la classe Balle, comme l?illustre la figure 5-11 : Figure 5-11. Bouton masqué par les instances de la classe Balle. Chapitre 5 ? Symboles prédéfinis ? version 0.1.2 18 / 31 Thibault Imbert pratiqueactionscript3.bytearray.org Pour ramener un objet graphique au premier plan nous devons travailler sur son index au sein de la liste d?affichage. Nous savons que la propriété numChildren nous renvoie le nombre d?enfants total, numChildren-1 correspond donc à l?index de l?objet le plus haut de la pile. En échangeant l?index de notre bouton et du dernier objet enfant avec la méthode setChildIndex nous obtenons le résultat escompté : monBouton.addEventListener( MouseEvent.CLICK, clicBouton ); function clicBouton ( pEvt:MouseEvent ):void { var maBalle:Balle for ( var i:int = 0; i< 10; i++ ) { maBalle = new Balle(); maBalle.x = Math.random()*(stage.stageWidth - maBalle.width); maBalle.y = Math.random()*(stage.stageHeight - maBalle.height); addChild( maBalle ); } setChildIndex ( monBouton, numChildren - 1 ); } La figure 5-12 illustre le résultat : Chapitre 5 ? Symboles prédéfinis ? version 0.1.2 19 / 31 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 5-12. Bouton placé au premier plan. La méthode addChild empile chaque instance sans jamais supprimer les objets graphiques déjà présents sur la scène. Pour supprimer tous les objets graphiques nous pourrions parcourir la scène en supprimant chaque occurrence de type Balle. L?idée serait d?ajouter une fonction nettoie avant la boucle for, afin de supprimer les occurrences du symbole Balle déjà présentes sur la scène : monBouton.addEventListener( MouseEvent.CLICK, clicBouton ); function nettoie ( pConteneur:DisplayObjectContainer, pClasse:Class ):void { var monDisplayObject:DisplayObject; for ( var i:int = pConteneur.numChildren-1; i >= 0; i-- ) { monDisplayObject = pConteneur.getChildAt ( i ); if ( monDisplayObject is pClasse ) pConteneur.removeChild (monDisplayObject); } } function clicBouton ( pEvt:MouseEvent ):void { nettoie ( this, Balle ); var maBalle:Balle; for ( var i:int = 0; i< 10; i++ ) { maBalle = new Balle(); maBalle.x = Math.random()*(stage.stageWidth - maBalle.width); maBalle.y = Math.random()*(stage.stageHeight - maBalle.height); addChild( maBalle ); } setChildIndex ( monBouton, numChildren - 1 ); } La fonction nettoie parcourt le conteneur passé en paramètre ici notre scène, puis récupère chaque objet enfant et stocke sa référence Chapitre 5 ? Symboles prédéfinis ? version 0.1.2 20 / 31 Thibault Imbert pratiqueactionscript3.bytearray.org dans une variable monDisplayObject de type flash.display.DisplayObject. Nous utilisons ce type pour notre variable monDisplayObject car celle-ci peut être amenée à référencer différents sous-types de la classe DisplayObject. Nous choisissons ce type qui est le type commun à tout objet enfant. Nous testons son type à l?aide du mot-clé is. Si celui-ci est de type Balle alors nous supprimons l?instance de la liste d?affichage. Nous pourrions obtenir une structure plus simple en créant un objet graphique conteneur afin d?accueillir les occurrences du symbole Balle, puis vider entièrement ce dernier sans faire de test. De plus si nous souhaitons déplacer tous les objets plus tard, cette approche s?avèrera plus judicieuse : monBouton.addEventListener( MouseEvent.CLICK, clicBouton ); var conteneur:Sprite = new Sprite(); addChildAt ( conteneur, 0 ); function nettoie ( pConteneur:DisplayObjectContainer ):void { while ( pConteneur.numChildren ) pConteneur.removeChildAt ( 0 ); } function clicBouton ( pEvt:MouseEvent ):void { nettoie ( conteneur ); var maBalle:Balle; for ( var i:int = 0; i< 10; i++ ) { maBalle = new Balle(); maBalle.x = Math.random()*(stage.stageWidth - maBalle.width); maBalle.y = Math.random()*(stage.stageHeight - maBalle.height); conteneur.addChild( maBalle ); } } La fonction nettoie intègre une boucle while supprimant chaque objet enfant, tant qu?il en existe. En cliquant sur notre bouton nous Chapitre 5 ? Symboles prédéfinis ? version 0.1.2 21 / 31 Thibault Imbert pratiqueactionscript3.bytearray.org nettoyons le conteneur de type flash.display.Sprite puis nous y ajoutons nos occurrences du symbole Balle. Revenons sur certaines parties du code précédent, dans un premier temps nous créons un conteneur de type flash.display.Sprite. Nous utilisons un objet Sprite car utiliser un MovieClip ici ne serait d?aucune utilité, un objet Sprite suffit car nous devons simplement y stocker des objets enfants : var conteneur:Sprite = new Sprite; addChildAt ( conteneur, 0 ); Nous créons un conteneur puis nous l?ajoutons à la liste d?affichage à l?aide de la méthode addChildAt en le plaçant à l?index 0 déjà occupé par notre bouton seul objet graphique présent à ce moment là. Notre conteneur prend donc l?index du bouton déplaçant ce dernier à l?index 1. Notre bouton se retrouve ainsi au-dessus de l?objet conteneur, évitant ainsi d?être caché par les instances de la classe Balle. La fonction nettoie prend en paramètre le conteneur à nettoyer, et supprime chaque enfant tant que celui-ci en possède : function nettoie ( pConteneur:DisplayObjectContainer ):void { while ( pConteneur.numChildren ) pConteneur.removeChildAt ( 0 ); } Cette fonction nettoie peut être réutilisée pour supprimer tous les objets enfants de n?importe quel DisplayObjectContainer. A retenir ? En ActionScript 3, il est recommandé de créer des conteneurs afin de manipuler plus facilement un ensemble d?objets graphiques. Le symbole bouton Notre symbole bouton en bibliothèque peut aussi être attaché dynamiquement à un scénario en lui associant une classe spécifique grâce au panneau de liaison. En sélectionnant l?option Liaison sur notre symbole Bouton nous faisons apparaître le panneau Propriétés de liaison. Chapitre 5 ? Symboles prédéfinis ? version 0.1.2 22 / 31 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 5-13. Propriétés de liaison d?un symbole de type Bouton. En cochant l?option Exporter pour ActionScript nous rendons notre symbole disponible par programmation. Nous spécifions MonBouton comme nom de classe, puis nous cliquons sur OK. Pour instancier notre bouton dynamiquement nous écrivons : var monBouton:MonBouton = new MonBouton(); addChild ( monBouton ); Souvenez-vous, il était impossible en ActionScript 1 et 2, de créer de véritables boutons par programmation. Les développeurs devaient obligatoirement utiliser des clips ayant un comportement bouton. Nous verrons au cours du chapitre 7 intitulé Interactivité que l?utilisation de la classe SimpleButton s?avère en réalité rigide et n?offre pas une souplesse équivalente à la classe MovieClip. Le symbole graphique Lorsqu?un symbole graphique est placé en bibliothèque, celui-ci ne peut être manipulé par programmation et instancié dynamiquement au sein de l?application. De par sa non-interactivité ce dernier est de type flash.display.Shape. Pour rappel, la classe Shape n?hérite pas de la classe flash.display.interactiveObject et ne peut donc avoir une quelconque interactivité liée au clavier ou la souris. Lorsque nous sélectionnons l?option Liaison sur un symbole graphique toutes les options sont grisées, comme le démontre la figure 5-14 : Chapitre 5 ? Symboles prédéfinis ? version 0.1.2 23 / 31 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 5-14. Options de liaisons grisées pour le symbole graphique. Il est cependant possible de manipuler un graphique posé sur la scène en y accédant grâce aux méthodes d?accès de la liste d?affichage comme getChildAt comme nous l?avons vu durant le chapitre 4. Nous sommes obligés de pointer notre objet graphique en passant un index car aucun nom d?occurrence ne peut être affecté à une occurrence de symbole graphique. Si nous posons une occurrence de graphique sur une scène vide nous pouvons tout de même y accéder par programmation à l?aide du code suivant : var monGraphique:DisplayObject = getChildAt ( 0 ); // affiche : [object Shape] trace( monGraphique ); Nous pourrions être tentés de lui affecter un nom par la propriété name, et d?y faire référence à l?aide de la méthode getChildByName définie par la classe flash.display.DisplayObjectContainer. Malheureusement, comme nous l?avons vu précédemment, la modification de la propriété name d?un objet graphique posé depuis l?environnement auteur est impossible. Notez que si dans l?environnement auteur de Flash nous créons un graphique et imbriquons à l?intérieur un clip, à l?exécution notre imbrication ne pourra être conservée. En regardant de plus près l?héritage de la classe Shape nous remarquons que celle-ci n?hérite pas de la classe flash.display.DisplayObjectContainer et ne peut donc contenir des objets enfants. Chapitre 5 ? Symboles prédéfinis ? version 0.1.2 24 / 31 Thibault Imbert pratiqueactionscript3.bytearray.org Il peut pourtant arriver qu?un clip soit imbriqué depuis l?environnement auteur dans un graphique, même si dans l?environnement auteur cela ne pose aucun problème, cette imbrication ne pourra être conservée à l?exécution. Le clip sortira du graphique pour devenir un enfant direct du parent du graphique. Les deux objets se retrouvant ainsi au même niveau d?imbrication, ce comportement bien que troublant s?avère tout à fait logique et doit être connu afin qu?il ne soit pas source d?interrogations. A retenir ? Le symbole graphique ne peut pas être associé à une sous classe. Les images bitmap Depuis toujours nous pouvons intégrer au sein de sa bibliothèque des images bitmap de différents types. En réalité, il faut considérer dans Flash une image bitmap comme un symbole. Depuis Flash 8 nous avions la possibilité d?attribuer un nom de liaison à une image et de l?attacher dynamiquement à l?aide de la méthode BitmapData.loadBitmap puis de l?afficher à l?aide de la méthode MovieClip.attachBitmap. Pour attacher une image de la bibliothèque nous devions appeler la méthode loadBitmap de la classe BitmapData : import flash.display.BitmapData; var ref:BitmapData = BitmapData.loadBitmap ("LogoWiiFlash"); En lui passant un nom d?identifiant affecté par le panneau liaison, nous récupérions notre image sous la forme de BitmapData puis nous l?affichions avec la méthode attachBitmap : import flash.display.BitmapData; var monBitmap:BitmapData = BitmapData.loadBitmap ("LogoWiiFlash"); attachBitmap ( monBitmap, 0 ); En ActionScript 3, les images s?instancient comme tout objet graphique avec le mot-clé new. Importez une image de type quelconque dans la bibliothèque puis faites un clic-droit sur celle-ci pour sélectionner l?option Liaison comme illustrée en figure 5-15 : Chapitre 5 ? Symboles prédéfinis ? version 0.1.2 25 / 31 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 5-15. Propriétés de liaison d?un symbole de type bitmap. En cochant l?option Exporter pour ActionScript nous rendons les champs Classe et Classe de base éditable. Nous spécifions LogoWiiFlash comme nom de classe, et laissons comme classe de base flash.display.BitmapData. Notre image sera donc de type LogoWiiFlash héritant de BitmapData. Puis nous instancions notre image : var monBitmapData:LogoWiiFlash = new LogoWiiFlash(); Si nous testons le code précédent, le message d?erreur suivant s?affiche : 1136: Nombre d'arguments incorrect. 2 attendus. En ActionScript 3, un objet BitmapData ne peut être instancié sans préciser une largeur et hauteur spécifique au constructeur. Afin de ne pas être bloqué à la compilation nous devons obligatoirement passer une largeur et hauteur de 0,0 : var monBitmapData:LogoWiiFlash = new LogoWiiFlash(0,0); A l?exécution, le lecteur affiche l?image à sa taille d?origine. A retenir Chapitre 5 ? Symboles prédéfinis ? version 0.1.2 26 / 31 Thibault Imbert pratiqueactionscript3.bytearray.org ? Afin d?instancier une image bitmap issue de la bibliothèque, nous devons obligatoirement spécifier une hauteur et une largeur de 0 pixels. A l?exécution, le lecteur affiche l?image à sa taille d?origine. Afficher une image bitmap En ActionScript 1 et 2, une image BitmapData pouvait être affichée en étant enveloppée dans un MovieClip grâce à la méthode MovieClip.attachBitmap. En ActionScript 3 si nous tentons d?ajouter à la liste d?affichage un objet graphique de type BitmapData nous obtenons l?erreur suivante à la compilation : 1067: Contrainte implicite d'une valeur du type flash.display:BitmapData vers un type sans rapport flash.display:DisplayObject. Pour ajouter à la liste d?affichage un BitmapData nous devons obligatoirement l?envelopper dans un objet de type Bitmap. Celui-ci nous permettra plus tard de positionner l?objet BitmapData et d?effectuer d?autres manipulations sur ce dernier. Nous instancions un objet Bitmap, puis nous passons au sein de son constructeur l?objet BitmapData à envelopper : var monBitmapData:LogoWiiFlash = new LogoWiiFlash(0, 0); var monBitmap:Bitmap = new Bitmap( monBitmapData ); addChild ( monBitmap ); Une fois compilé, notre image est bien affichée : Chapitre 5 ? Symboles prédéfinis ? version 0.1.2 27 / 31 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 5-16. Image bitmap ajoutée à la liste d?affichage. La classe flash.display.Bitmap héritant de la classe flash.display.DisplayObject possède toutes les propriétés et méthodes nécessaires à la manipulation d?un objet graphique. Pour procéder à une translation de notre image, puis une rotation nous pouvons écrire : var monBitmapData:LogoWiiFlash = new LogoWiiFlash(0, 0); var monBitmap:Bitmap = new Bitmap( monBitmapData ); addChild ( monBitmap ); monBitmap.smoothing = true; monBitmap.rotation = 45; monBitmap.x += 250; Le code suivant, génère le rendu suivant : Chapitre 5 ? Symboles prédéfinis ? version 0.1.2 28 / 31 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 5-17.Translation et rotation sur un objet Bitmap. Nous reviendrons en détail sur la classe Bitmap au cours du chapitre 12 intitulé Programmation bitmap. A retenir ? Un symbole de type graphique n?est pas manipulable depuis la bibliothèque par programmation. ? Une image est associée au type flash.display.BitmapData, pour afficher celle-ci nous devons l?envelopper dans un objet flash.display.Bitmap. Le symbole Sprite L?objet graphique Sprite est très proche du classique MovieClip et possède quasiment toutes ses fonctionnalités mais ne contient pas de scénario et donc aucune des méthodes liées à sa manipulation telles gotoAndStop, gotoAndPlay, etc. C?est en quelque sorte un MovieClip version allégée. Le développeur Flash qui a toujours eu l?habitude de créer des clips vides dynamiquement se dirigera désormais vers l?objet Sprite plus léger en mémoire et donc plus optimisé. Chapitre 5 ? Symboles prédéfinis ? version 0.1.2 29 / 31 Thibault Imbert pratiqueactionscript3.bytearray.org Lorsque nous transformons une forme en symbole l?environnement de Flash CS3 nous propose trois types de symboles : ? Clip ? Bouton ? Graphique Le type Sprite n?apparaît pas, mais il est pourtant possible de créer un symbole de type Sprite depuis l?environnement auteur de Flash CS3. Pour cela, nous allons ouvrir un nouveau document et créer un symbole de type clip. Dans le panneau Propriétés de liaison, nous cochons la case Exporter pour ActionScript. Nous définissons comme nom de classe MonSprite, dans le champ Classe de base nous remplaçons la classe flash.display.MovieClip par la classe flash.display.Sprite, comme l?illustre la figure 5-18 : Figure 5-18.Panneau de propriétés de liaison. De cette manière notre symbole héritera de la classe Sprite au lieu de la classe MovieClip. Nous instancions notre symbole : var monSprite:MonSprite = new MonSprite(); Puis nous l?affichons : var monSprite:MonSprite = new MonSprite(); monSprite.x = 200; monSprite.y = 100; addChild ( monSprite ); Chapitre 5 ? Symboles prédéfinis ? version 0.1.2 30 / 31 Thibault Imbert pratiqueactionscript3.bytearray.org Si nous testons son type avec l?opérateur is, nous voyons que notre occurrence est bien de type Sprite : var monSprite:MonSprite = new MonSprite(); monSprite.x = 200; monSprite.y = 100; addChild ( monSprite ); // affiche : true trace( monSprite is Sprite ); Au sein de l?environnement de Flash CS3, le symbole Sprite est similaire au symbole clip mais l?absence de scénario n?est pas répercutée graphiquement. Comme nous l?avons vu précédemment, l?objet graphique Sprite n?a pas de scénario, mais que se passe t-il si nous ajoutons une animation au sein de ce dernier ? A l?exécution le symbole ne lira pas l?animation, si nous passons la classe de base du symbole à flash.display.MovieClip l?animation est alors jouée. A retenir ? Même si le symbole de type flash.display.Sprite n?est pas disponible depuis le panneau Convertir en symbole il est possible de créer des symboles Sprite depuis l?environnement auteur de Flash CS3. ? Pour cela, nous utilisons le champ Classe de base en spécifiant la classe flash.display.Sprite. ? Si une animation est placée au sein d?un symbole de type Sprite, celle-ci n?est pas jouée. Définition du code dans un symbole Dans les précédentes versions d?ActionScript, la définition de code était possible sur les occurrences de la manière suivante : on (release) { trace("cliqué"); } Même si cette technique était déconseillée dans de larges projets, elle permettait néanmoins d?attacher simplement le code aux objets. En ActionScript 3, la définition de code est impossible sur les Chapitre 5 ? Symboles prédéfinis ? version 0.1.2 31 / 31 Thibault Imbert pratiqueactionscript3.bytearray.org occurrences, mais il est possible de reproduire le même comportement en ActionScript 3. En plaçant le code suivant sur le scénario d?un MovieClip nous retrouvons le même comportement : // écoute de l'événement MouseEvent.CLICK addEventListener ( MouseEvent.CLICK, clic ); // activation du comportement bouton buttonMode = true; function clic ( pEvt:MouseEvent ):void { trace("cliqué"); } Nous reviendrons sur la propriété buttonMode au cours du chapitre 7 intitulé Interactivité. Chapitre 6 ? Propagation événementielle ? version 0.1.1 1 / 33 Thibault Imbert pratiqueactionscript3.bytearray.org 6 Propagation événementielle CONCEPT ................................................................................................................ 1 LA PHASE DE CAPTURE ..................................................................................... 3 LA NOTION DE N?UDS............................................................................................. 6 DETERMINER LA PHASE EN COURS .......................................................................... 7 OPTIMISER LE CODE AVEC LA PHASE DE CAPTURE .............................. 9 LA PHASE CIBLE ................................................................................................ 14 INTERVENIR SUR LA PROPAGATION.......................................................... 18 LA PHASE DE REMONTEE ............................................................................... 23 ECOUTER PLUSIEURS PHASES ...................................................................... 27 LA PROPRIETE EVENT.BUBBLES ............................................................................ 29 SUPPRESSION D?ECOUTEURS ........................................................................ 30 Concept Nous avons abordé la notion d?événements au cours du chapitre 3 intitulé Modèle événementiel et traité les nouveautés apportées par ActionScript 3. La propagation événementielle est un concept avancé d?ActionScript 3 qui nécessite une attention toute particulière. Nous allons au cours de ce chapitre apprendre à maîtriser ce nouveau comportement. Le concept de propagation événementielle est hérité du Document Object Model (DOM) du W3C dont la dernière spécification est disponible à l?adresse suivante : http://www.w3.org/TR/DOM-Level-3-Events/ Chapitre 6 ? Propagation événementielle ? version 0.1.1 2 / 33 Thibault Imbert pratiqueactionscript3.bytearray.org En ActionScript 3, seuls les objets graphiques sont concernés par la propagation événementielle. Ainsi, lorsqu?un événement survient au sein de la liste d?affichage, le lecteur Flash propage ce dernier de l?objet flash.display.Stage jusqu?au parent direct de l?objet auteur de la propagation. Cette descente de l?événement est appelée phase de capture. Grâce à ce mécanisme, nous pouvons souscrire un écouteur auprès de l?un des parents de l?objet ayant initié la propagation, afin d?en être notifié durant la phase descendante. Le terme de capture est utilisé car nous pouvons capturer l?événement durant sa descente et stopper sa propagation si nécessaire. La phase cible intervient lorsque l?événement a parcouru tous les objets parents et atteint l?objet ayant provoqué sa propagation, ce dernier est appelé objet cible ou n?ud cible. Une fois la phase cible terminée, le lecteur Flash propage l?événement dans le sens inverse de la phase de capture. Cette phase est appelée phase de remontée. Durant celle-ci l?événement remonte jusqu'à l?objet Stage c'est-à-dire à la racine de la liste d?affichage. Ces trois phases constituent la propagation événementielle, schématisée sur la figure 6-1 : Figure 6-1. Les trois phases du flot événementiel. Les événements diffusés en ActionScript 2 ne disposaient que d?une seule phase cible. Les deux nouvelles phases apportées par Chapitre 6 ? Propagation événementielle ? version 0.1.1 3 / 33 Thibault Imbert pratiqueactionscript3.bytearray.org ActionScript 3 vont nous permettre de mieux gérer l?interaction entre les objets graphiques au sein de la liste d?affichage. Attention, la propagation événementielle ne concerne que les objets graphiques. Il est important de noter qu?ActionScript 3 n?est pas le seul langage à intégrer la notion de propagation événementielle, des langages comme JavaScript ou Java, intègrent ce mécanisme depuis plusieurs années déjà. Nous allons nous attarder sur chacune des phases, et découvrir ensemble quels sont les avantages liés à cette propagation événementielle et comment en tirer profit dans nos applications. A retenir ? La propagation événementielle ne concerne que les objets graphiques. ? La propagation événementielle n?est pas propre à ActionScript 3. D?autres langages comme JavaScript, Java ou C# l?intègre depuis plusieurs années déjà. ? Le flot événementiel se divise en trois phases distinctes : la phase de capture, la phase cible, et la phase de remontée. La phase de capture Comme nous l?avons abordé précédemment, la phase de capture est la première phase du flot événementiel. En réalité, lorsque nous cliquons sur un bouton présent au sein de la liste d?affichage, tous les objets graphiques parents à ce dernier sont d?abord notifiés de l?événement et peuvent ainsi le diffuser. La figure 6-2 illustre une situation classique, un bouton est posé sur le scénario principal : Chapitre 6 ? Propagation événementielle ? version 0.1.1 4 / 33 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 6-2. Phase de capture. Lorsque l?utilisateur clique sur le bouton monBouton, la phase de capture démarre. Le lecteur Flash propage l?événement du haut vers le bas de la hiérarchie. Notons que la descente de l?événement s?arrête au parent direct du bouton, la phase suivante sera la phase cible. L?objet Stage, puis l?objet MainTimeline (root) sont ainsi notifiés de l?événement. Ainsi, si nous écoutons l?événement en cours de propagation sur l?un de ces derniers, il nous est possible de l?intercepter. En lisant la signature de la méthode addEventListener nous remarquons la présence d?un paramètre appelé useCapture : public function addEventListener(type:String, listener:Function, useCapture:Boolean = false, priority:int = 0, useWeakReference:Boolean = false):void Par défaut la méthode addEventListener ne souscrit pas l?écouteur passé à la phase de capture. Pour en profiter nous devons passer la valeur booléenne true au troisième paramètre nommé useCapture. L?idée est d?écouter l?événement sur l?un des objets parents à l?objet cible : objetParent.addEventListener ( MouseEvent.CLICK, clicBouton, true ); Passons à un peu de pratique, dans un nouveau document Flash CS3 nous créons un symbole de type bouton et nous posons une occurrence de ce dernier, appelée monBouton, sur la scène principale. Dans le code suivant nous souscrivons un écouteur auprès du scénario principal en activant la capture : Chapitre 6 ? Propagation événementielle ? version 0.1.1 5 / 33 Thibault Imbert pratiqueactionscript3.bytearray.org // souscription à l'événement MouseEvent.CLICK auprès // du scénario principal pour la phase de capture addEventListener ( MouseEvent.CLICK, clicBouton, true ); function clicBouton ( pEvt:MouseEvent ) { // affiche : [MouseEvent type="click" bubbles=true cancelable=false eventPhase=1 localX=13 localY=13 stageX=75.95 stageY=92 relatedObject=null ctrlKey=false altKey=false shiftKey=false delta=0] trace( pEvt ); } Lors du clic sur notre bouton monBouton, l?événement MouseEvent.CLICK entame sa phase de descente puis atteint le scénario principal qui le diffuse aussitôt. La fonction écouteur clicBouton est déclenchée. Contrairement à la méthode hasEventListener, la méthode willTrigger permet de déterminer si un événement spécifique est écouté auprès de l?un des parents lors de la phase de capture ou de remontée : // souscription à l'événement MouseEvent.CLICK auprès // du scénario principal pour la phase de capture addEventListener ( MouseEvent.CLICK, clicBouton, true ); function clicBouton ( pEvt:MouseEvent ) { trace( pEvt ); } // aucun écouteur n'est enregistré auprès du bouton // affiche : false trace( monBouton.hasEventListener (MouseEvent.CLICK) ); // un écouteur est enregistré auprès d'un des parents du bouton // affiche : true trace( monBouton.willTrigger( MouseEvent.CLICK ) ); // un écouteur est enregistré auprès du scénario principal // affiche : true trace( willTrigger( MouseEvent.CLICK ) ); Nous préférerons donc l?utilisation de la méthode willTrigger dans un contexte de propagation événementielle. Au cours du chapitre 3 intitulé Modèle événementiel nous avons découvert la propriété target de l?objet événementiel correspondant à l?objet auteur du flot événementiel que nous appelons généralement objet cible. Attention, durant la phase de capture ce dernier n?est pas celui qui a diffusé l?événement, mais celui qui est à la source de la propagation événementielle. Dans notre cas, l?objet MainTimeline a diffusé Chapitre 6 ? Propagation événementielle ? version 0.1.1 6 / 33 Thibault Imbert pratiqueactionscript3.bytearray.org l?événement MouseEvent.CLICK, suite à un déclenchement de la propagation de l?événement par notre bouton. L?intérêt majeur de la phase de capture réside donc dans la possibilité d?intercepter depuis un parent n?importe quel événement provenant d?un enfant. La notion de n?uds Lorsqu?un événement se propage, les objets graphiques notifiés sont appelés objets n?uds. Pour récupérer le n?ud sur lequel se trouve l?événement au cours de sa propagation, nous utilisons la propriété currentTarget de l?objet événementiel. La propriété currentTarget renvoie toujours l?objet sur lequel nous avons appelé la méthode addEventListener. Grace aux propriétés target et currentTarget nous pouvons donc savoir vers quel objet graphique se dirige l?événement ainsi que le n?ud traité actuellement. La figure 6-3 illustre l?idée : Figure 6-3. Propagation de l?événement au sein des objets n?uds. En testant le code suivant, nous voyons que le scénario principal est notifié de l?événement MouseEvent.CLICK se dirigeant vers l?objet cible : // souscription à l'événement MouseEvent.CLICK auprès // du scénario principal pour la phase de capture Chapitre 6 ? Propagation événementielle ? version 0.1.1 7 / 33 Thibault Imbert pratiqueactionscript3.bytearray.org addEventListener ( MouseEvent.CLICK, clicBouton, true ); function clicBouton ( pEvt:MouseEvent ) { /* // affiche : Objet cible : [object SimpleButton] Noeud en cours : [object MainTimeline] */ trace( "Objet cible : " + pEvt.target ) trace( "Noeud en cours : " + pEvt.currentTarget ); } Bien que nous connaissions les objets graphiques associés à la propagation de l?événement, nous ne pouvons pas déterminer pour le moment la phase en cours. L?événement actuellement diffusé correspond t?il à la phase de capture, cible, ou bien de remontée ? Déterminer la phase en cours Afin de savoir à quelle phase correspond un événement nous utilisons la propriété eventPhase de l?objet événementiel. Durant la phase de capture, la propriété eventPhase vaut 1, 2 pour la phase cible et 3 pour la phase de remontée. Dans le même document que précédemment nous écoutons l?événement MouseEvent.CLICK auprès du scénario principal durant la phase de capture : // souscription à l'événement MouseEvent.CLICK auprès // du scénario principal pour la phase de capture addEventListener ( MouseEvent.CLICK, clicBouton, true ); function clicBouton ( pEvt:MouseEvent ) { // affiche : Phase en cours : 1 trace ( "Phase en cours : " + pEvt.eventPhase ); } Lorsque nous cliquons sur notre bouton, la propriété eventPhase de l?objet événementiel vaut 1. Pour afficher une information relative à la phase en cours nous pourrions être tentés d?écrire le code suivant : // souscription à l'événement MouseEvent.CLICK auprès // du scénario principal pour la phase de capture addEventListener ( MouseEvent.CLICK, clicBouton, true ); function clicBouton ( pEvt:MouseEvent ) Chapitre 6 ? Propagation événementielle ? version 0.1.1 8 / 33 Thibault Imbert pratiqueactionscript3.bytearray.org { // affiche : phase de capture if ( pEvt.eventPhase == 1 ) trace("phase de capture"); } Attention, comme pour la souscription d?événements, nous utilisons toujours des constantes de classes pour déterminer la phase en cours. Pour cela, la classe flash.events.EventPhase contient trois propriétés associées à chacune des phases. En testant la valeur de chaque propriété nous récupérons les valeurs correspondantes : // affiche : 1 trace( EventPhase.CAPTURING_PHASE ); // affiche : 2 trace( EventPhase.AT_TARGET); // affiche : 3 trace( EventPhase.BUBBLING_PHASE); La figure 6-4 illustre les constantes de la classe EventPhase liées à chaque phase : Figure 6-4. Les trois phases de la propagation événementielle. En prenant cela en considération, nous pouvons réécrire la fonction écouteur clicBouton de la manière suivante : function clicBouton ( pEvt:MouseEvent ) { switch ( pEvt.eventPhase ) { case EventPhase.CAPTURING_PHASE : Chapitre 6 ? Propagation événementielle ? version 0.1.1 9 / 33 Thibault Imbert pratiqueactionscript3.bytearray.org trace("phase de capture"); break; case EventPhase.AT_TARGET : trace("phase cible"); break; case EventPhase.BUBBLING_PHASE : trace("phase de remontée"); break; } } Tout cela reste relativement abstrait et nous pouvons nous demander l?intérêt d?un tel mécanisme dans le développement d?applications ActionScript 3. Grâce à ce processus nous allons rendre notre code souple et optimisé. Nous allons en tirer profit au sein d?une application dans la partie suivante. A retenir ? La phase de capture démarre de l?objet Stage jusqu'au parent de l?objet cible. ? La phase de capture ne concerne que les objets parents. ? La propriété target de l?objet événementiel renvoie toujours une référence vers l?objet cible. ? La propriété currentTarget de l?objet événementiel renvoie toujours une référence vers l?objet sur lequel nous avons appelé la méthode addEventListener. ? Durant la propagation d?un événement, la propriété target ne change pas et fait toujours référence au même objet graphique (objet cible). ? La propriété eventPhase de l?objet événementiel renvoie une valeur allant de 1 à 3 associées à chaque phase : CAPTURING_PHASE, AT_TARGET et BUBBLING_PHASE. ? Pour déterminer la phase liée à un événement nous comparons la propriété eventPhase aux trois constantes de la classe flash.events.EventPhase. Optimiser le code avec la phase de capture Afin de mettre en évidence l?intérêt de la phase de capture, nous allons prendre un cas simple d?application ActionScript où nous souhaitons écouter l?événement MouseEvent.CLICK auprès de différents Chapitre 6 ? Propagation événementielle ? version 0.1.1 10 / 33 Thibault Imbert pratiqueactionscript3.bytearray.org boutons. Lorsque nous cliquons sur chacun d?entre eux, nous les supprimons de la liste d?affichage. Dans un nouveau document Flash CS3 nous créons un symbole bouton de forme rectangulaire lié à une classe Fenetre par le panneau Propriétés de liaison. Puis nous ajoutons plusieurs instances de celle-ci à notre scénario principal : // nombre de fenêtres var lng:int = 12; var maFenetre:Fenetre; for ( var i:int = 0; i< lng; i++ ) { maFenetre = new Fenetre(); maFenetre.x = 7 + Math.round ( i % 3 ) * ( maFenetre.width + 10 ); maFenetre.y = 7 + Math.floor ( i / 3 ) * ( maFenetre.height + 10 ); addChild ( maFenetre ); } Nous obtenons le résultat illustré par la figure 6-5 : Figure 6-5. Instances de la classe Fenetre. Chapitre 6 ? Propagation événementielle ? version 0.1.1 11 / 33 Thibault Imbert pratiqueactionscript3.bytearray.org Notre liste d?affichage pourrait être représentée de la manière suivante : Figure 6-6. Liste d?affichage avec instances de la classe Fenetre. Chaque clic sur une instance de symbole Fenetre provoque une propagation de l?événement MouseEvent.CLICK de l?objet Stage jusqu?à notre scénario principal MainTimeline. Ce dernier est donc notifié de chaque clic sur nos boutons durant la phase de capture. Comme nous l?avons vu précédemment, la phase de capture présente l?intérêt de pouvoir intercepter un événement durant sa phase descendante auprès d?un objet graphique parent. Cela nous permet donc de centraliser l?écoute de l?événement MouseEvent.CLICK en appelant une seule fois la méthode addEventListener. De la même manière, pour arrêter l?écoute d?un événement, nous appelons la méthode removeEventListener sur l?objet graphique parent seulement. Ainsi nous n?avons pas besoin de souscrire un écouteur auprès de chaque instance de la classe Fenetre mais seulement auprès du scénario principal : // nombre de fenêtres var lng:int = 12; var maFenetre:Fenetre; for ( var i:int = 0; i< lng; i++ ) { maFenetre = new Fenetre(); maFenetre.x = 7 + Math.round ( i % 3 ) * ( maFenetre.width + 10 ); maFenetre.y = 7 + Math.floor ( i / 3 ) * ( maFenetre.height + 10 ); addChild ( maFenetre ); Chapitre 6 ? Propagation événementielle ? version 0.1.1 12 / 33 Thibault Imbert pratiqueactionscript3.bytearray.org } // souscription à l'événement MouseEvent.CLICK auprès // du scénario principal pour la phase de capture addEventListener ( MouseEvent.CLICK, clicFenetre, true ); function clicFenetre ( pEvt:MouseEvent ):void { // affiche : [MouseEvent type="click" bubbles=true cancelable=false eventPhase=1 localX=85 localY=15 stageX=92 stageY=118 relatedObject=null ctrlKey=false altKey=false shiftKey=false delta=0] trace( pEvt ); } Si nous n?avions pas utilisé la phase de capture, nous aurions du souscrire un écouteur auprès de chaque symbole Fenetre. En ajoutant un écouteur auprès de l?objet parent nous pouvons capturer l?événement provenant des objets enfants, rendant ainsi notre code centralisé. Si les boutons viennent à être supprimés nous n?avons pas besoin d?appeler la méthode removeEventListener sur chacun d?entre eux afin de libérer les ressources, car seul le parent est écouté. Si par la suite les boutons sont recréés, aucun code supplémentaire n?est nécessaire. Attention, en capturant l?événement auprès du scénario principal nous écoutons tous les événements MouseEvent.CLICK des objets interactifs enfants. Si nous souhaitons seulement écouter les événements provenant des instances de la classe Fenetre, il nous faut alors les isoler dans un objet conteneur, et procéder à la capture des événements auprès de ce dernier. Nous préfèrerons donc l?approche suivante : // nombre de fenêtres var lng:int = 12; // création d'un conteneur var conteneurFenetres:Sprite = new Sprite(); // ajout à la liste d'affichage addChild ( conteneurFenetres ); var maFenetre:Fenetre; for ( var i:int = 0; i< lng; i++ ) { maFenetre = new Fenetre(); maFenetre.x = 7 + Math.round ( i % 3 ) * ( maFenetre.width + 10 ); Chapitre 6 ? Propagation événementielle ? version 0.1.1 13 / 33 Thibault Imbert pratiqueactionscript3.bytearray.org maFenetre.y = 7 + Math.floor ( i / 3 ) * ( maFenetre.height + 10 ); conteneurFenetres.addChild ( maFenetre ); } // souscription à l'événement MouseEvent.CLICK auprès // du conteneur pour la phase de capture conteneurFenetres.addEventListener ( MouseEvent.CLICK, clicFenetre, true ); function clicFenetre ( pEvt:MouseEvent ):void { // affiche : [MouseEvent type="click" bubbles=true cancelable=false eventPhase=1 localX=119 localY=46 stageX=126 stageY=53 relatedObject=null ctrlKey=false altKey=false shiftKey=false delta=0] trace( pEvt ); } Afin de supprimer de l?affichage chaque fenêtre cliquée, nous ajoutons l?instruction removeChild au sein de la fonction écouteur clicFenetre : function clicFenetre ( pEvt:MouseEvent ):void { // l'instance de Fenetre cliquée est supprimée de l'affichage pEvt.currentTarget.removeChild ( DisplayObject ( pEvt.target ) ); } La propriété target fait ainsi référence à l?instance de Fenetre cliquée, tandis que la propriété currentTarget référence le conteneur. A chaque clic, l?instance cliquée est supprimée de la liste d?affichage : Chapitre 6 ? Propagation événementielle ? version 0.1.1 14 / 33 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 6-7. Suppression d?instances de la classe Fenetre. Nous reviendrons tout au long de l?ouvrage sur l?utilisation de la phase de capture afin de maîtriser totalement ce concept. Nous allons nous attarder à présent sur la phase cible. A retenir ? Grâce à la phase de capture, nous souscrivons l?écouteur auprès de l?objet graphique parent afin de capturer les événements des objets enfants. ? Notre code est plus optimisé et plus centralisé. La phase cible La phase cible correspond à la diffusion de l?événement depuis l?objet cible. Certains événements associés à des objets graphiques ne participent qu?à celle-ci. C?est le cas des quatre événements suivants qui ne participent ni à la phase de capture ni à la phase de remontée : ? Event.ENTER_FRAME ? Event.ACTIVATE Chapitre 6 ? Propagation événementielle ? version 0.1.1 15 / 33 Thibault Imbert pratiqueactionscript3.bytearray.org ? Event.DEACTIVATE ? Event.RENDER Nous allons reprendre l?exemple précédent, en préférant cette fois la phase cible à la phase de capture : Figure 6-8. Phase cible. Afin d?écouter l?événement MouseEvent.CLICK, nous souscrivons un écouteur auprès de chaque instance de la classe Fenetre : // nombre de fenêtres var lng:int = 12; // création d'un conteneur var conteneurFenetres:Sprite = new Sprite(); // ajout à la liste d'affichage addChild ( conteneurFenetres ); var maFenetre:Fenetre; for (var i:int = 0; i< lng; i++ ) { maFenetre = new Fenetre(); // souscription auprès de chaque instance pour la phase cible maFenetre.addEventListener ( MouseEvent.CLICK, clicFenetre ); maFenetre.x = 7 + Math.round ( i % 3 ) * ( maFenetre.width + 10 ); maFenetre.y = 7 + Math.floor ( i / 3 ) * ( maFenetre.height + 10 ); conteneurFenetres.addChild ( maFenetre ); } function clicFenetre ( pEvt:MouseEvent ):void Chapitre 6 ? Propagation événementielle ? version 0.1.1 16 / 33 Thibault Imbert pratiqueactionscript3.bytearray.org { // affiche : [object Fenetre] trace( pEvt.target ); } Au clic, l?événement commence sa propagation puis atteint l?objet cible, notre fonction écouteur clicFenetre est déclenchée. Ainsi, lorsque nous appelons la méthode addEventListener sans spécifier le paramètre useCapture, nous souscrivons l?écouteur à la phase cible ainsi qu?à la phase de remontée que nous traiterons juste après. Si nous affichons le contenu de la propriété target de l?objet événementiel, celui-ci nous renvoie une référence vers l?objet cible, ici notre objet Fenetre. Pour supprimer chaque instance, nous rajoutons l?instruction removeChild au sein de notre fonction écouteur en passant l?objet référencé par la propriété target de l?objet événementiel : function clicFenetre ( pEvt:MouseEvent ):void { // l'instance de Fenetre cliquée est supprimée de l'affichage conteneurFenetres.removeChild ( DisplayObject ( pEvt.target ) ); // désinscription de la fonction clicFenetre à l?événement // MouseEvent.CLICK pEvt.target.removeEventListener ( MouseEvent.CLICK, clicFenetre ); } Il est important de noter que durant la phase cible, l?objet cible est aussi le diffuseur de l?événement. En testant le code suivant, nous voyons que les propriétés target et currentTarget de l?objet événementiel référencent le même objet graphique : function clicFenetre ( pEvt:MouseEvent ):void { // affiche : true trace( pEvt.target == pEvt.currentTarget ); // l'instance de Fenetre cliquée est supprimée de l'affichage conteneurFenetres.removeChild ( DisplayObject ( pEvt.target ) ); // désinscription de la fonction clicFenetre à l?événement // MouseEvent.CLICK Chapitre 6 ? Propagation événementielle ? version 0.1.1 17 / 33 Thibault Imbert pratiqueactionscript3.bytearray.org pEvt.target.removeEventListener ( MouseEvent.CLICK, clicFenetre ); } Pour tester si l?événement diffusé est issu de la phase cible nous pouvons comparer la propriété eventPhase de l?objet événementiel à la constante EventPhase.AT_TARGET : function clicFenetre ( pEvt:MouseEvent ):void { // affiche : true trace( pEvt.target == pEvt.currentTarget ); // affiche : phase cible if ( pEvt.eventPhase == EventPhase.AT_TARGET ) { trace("phase cible"); } // l'instance de Fenetre cliquée est supprimée de l'affichage conteneurFenetres.removeChild ( DisplayObject ( pEvt.target ) ); // désinscription de la fonction clicFenetre à l?événement // MouseEvent.CLICK pEvt.target.removeEventListener ( MouseEvent.CLICK, clicFenetre ); } En utilisant la phase cible nous avons perdu en souplesse en souscrivant la fonction écouteur clicFenetre auprès de chaque instance de la classe Fenetre, et géré la désinscription des écouteurs lors de la suppression de la liste d?affichage. Bien entendu, la phase de capture ne doit et ne peut pas être utilisée systématiquement. En utilisant la phase cible nous pouvons enregistrer une fonction écouteur à un bouton unique, ce qui peut s?avérer primordial lorsque la logique associée à chaque bouton est radicalement différente. Bien que très pratique dans certains cas, la phase de capture intercepte les clics de chaque bouton en exécutant une seule fonction écouteur ce qui peut s?avérer limitatif dans certaines situations. Chacune des phases doit donc être considérée et utilisée lorsque la situation vous paraît la plus appropriée. A retenir Chapitre 6 ? Propagation événementielle ? version 0.1.1 18 / 33 Thibault Imbert pratiqueactionscript3.bytearray.org ? La phase cible correspond à la diffusion de l?événement par l?objet cible. ? Les objets graphiques diffusent des événements pouvant se propager. ? Les objets non graphiques diffusent des événements ne participant qu?à la phase cible. ? Il convient de bien étudier l?imbrication des objets graphiques au sein de l?application, et d?utiliser la phase la plus adaptée à nos besoins. ActionScript 3 offre en plus la possibilité d?intervenir sur la propagation d?un événement. Cette fonctionnalité peut être utile lorsque nous souhaitons empêcher un événement d?atteindre l?objet cible en empêchant la poursuite de sa propagation. Nous allons découvrir à l?aide d?un exemple concret comment utiliser cette nouvelle notion. Intervenir sur la propagation Il est possible de stopper la propagation d?un événement depuis n?importe quel n?ud. Quel pourrait être l?intérêt de stopper la propagation d?un événement durant sa phase de capture ou sa phase de remontée ? Dans certains cas nous avons besoin de verrouiller l?ensemble d?une application, comme par exemple la sélection d?éléments interactifs dans une application ou dans un jeu. Deux méthodes définies par la classe flash.events.Event permettent d?intervenir sur la propagation : ? objtEvenementiel.stopPropagation() ? objtEvenementiel.stopImmediatePropagation() Ces deux méthodes sont quasiment identiques mais diffèrent quelque peu, nous allons nous y attarder à présent. Imaginons que nous souhaitions verrouiller tous les boutons de notre exemple précédent. Dans l?exemple suivant nous allons intervenir sur la propagation d?un événement MouseEvent.CLICK durant la phase de capture. En stoppant sa propagation au niveau du conteneur, nous allons l?empêcher d?atteindre l?objet cible. Ainsi la phase cible ne sera jamais atteinte. Chapitre 6 ? Propagation événementielle ? version 0.1.1 19 / 33 Thibault Imbert pratiqueactionscript3.bytearray.org La figure 6-9 illustre l?interruption de propagation d?un événement : Figure 6-9. Intervention sur la propagation d?un événement. Afin d?intercepter l?événement MouseEvent.CLICK avant qu?il ne parvienne jusqu?à l?objet cible, nous écoutons l?événement MouseEvent.CLICK lors de la phase de capture auprès du conteneur conteneurFenetres : // nombre de fenêtres var lng:int = 12; // création d'un conteneur var conteneurFenetres:Sprite = new Sprite(); // ajout à la liste d'affichage addChild ( conteneurFenetres ); var maFenetre:Fenetre; for (var i:int = 0; i< lng; i++ ) { maFenetre = new Fenetre(); // souscription auprès de chaque instance pour la phase cible maFenetre.addEventListener ( MouseEvent.CLICK, clicFenetre ); maFenetre.x = 7 + Math.round ( i % 3 ) * ( maFenetre.width + 10 ); maFenetre.y = 7 + Math.floor ( i / 3 ) * ( maFenetre.height + 10 ); conteneurFenetres.addChild ( maFenetre ); } // souscription à l'événement MouseEvent.CLICK auprès // du conteneur pour la phase de capture conteneurFenetres.addEventListener ( MouseEvent.CLICK, captureClic, true ); Chapitre 6 ? Propagation événementielle ? version 0.1.1 20 / 33 Thibault Imbert pratiqueactionscript3.bytearray.org function clicFenetre ( pEvt:Event ):void { // l'instance de Fenetre cliquée est supprimée de l'affichage conteneurFenetres.removeChild ( DisplayObject ( pEvt.target ) ); // désinscription de la fonction clicFenetre à l?événement // MouseEvent.CLICK pEvt.target.removeEventListener ( MouseEvent.CLICK, clicFenetre ); } function captureClic ( pEvt:MouseEvent ):void { // affiche : Capture de l'événement : click trace("Capture de l'événement : " + pEvt.type ); } Nous remarquons que la fonction captureClic est bien déclenchée à chaque clic bouton. Pour stopper la propagation, nous appelons la méthode stopPropagation sur l?objet événementiel diffusé : function captureClic ( pEvt:MouseEvent ):void { // affiche : Objet cible : [object Fenetre] trace( "Objet cible : " + pEvt.target ); // affiche : Objet notifié : [object Sprite] trace( "Objet notifié : " + pEvt.currentTarget ); // affiche : Capture de l'événement : click trace("Capture de l'événement : " + pEvt.type ); pEvt.stopPropagation(); } Lorsque la méthode stopPropagation est exécutée, l?événement interrompt sa propagation sur le n?ud en cours, la fonction clicFenetre n?est plus déclenchée. Grâce au code suivant, les instances de la classe Fenetre sont supprimées uniquement lorsque la touche ALT est enfoncée : function captureClic ( pEvt:MouseEvent ):void { // affiche : Objet cible : [object Fenetre] trace( "Objet cible : " + pEvt.target ); Chapitre 6 ? Propagation événementielle ? version 0.1.1 21 / 33 Thibault Imbert pratiqueactionscript3.bytearray.org // affiche : Objet notifié : [object Sprite] trace( "Objet notifié : " + pEvt.currentTarget ); // affiche : Capture de l'événement : click trace("Capture de l'événement : " + pEvt.type ); if ( !pEvt.altKey ) pEvt.stopPropagation(); } Nous reviendrons sur les propriétés de la classe MouseEvent au cours du prochain chapitre intitulé Interactivité. Attention, la méthode stopPropagation interrompt la propagation de l?événement auprès des n?uds suivants, mais autorise l?exécution des écouteurs souscrits au n?ud en cours. Si nous ajoutons un autre écouteur à l?objet conteneurFenetres, bien que la propagation soit stoppée, la fonction écouteur est déclenchée. Afin de mettre en évidence la méthode stopImmediatePropagation nous ajoutons une fonction captureClicBis comme autre écouteur du conteneur : // nombre de fenêtres var lng:int = 12; // création d'un conteneur var conteneurFenetres:Sprite = new Sprite(); // ajout à la liste d'affichage addChild ( conteneurFenetres ); var maFenetre:Fenetre; for (var i:int = 0; i< lng; i++ ) { maFenetre = new Fenetre(); // souscription auprès de chaque instance pour la phase cible maFenetre.addEventListener ( MouseEvent.CLICK, clicFenetre ); maFenetre.x = 7 + Math.round ( i % 3 ) * ( maFenetre.width + 10 ); maFenetre.y = 7 + Math.floor ( i / 3 ) * ( maFenetre.height + 10 ); conteneurFenetres.addChild ( maFenetre ); } // souscription à l'événement MouseEvent.CLICK auprès // du conteneur pour la phase de capture conteneurFenetres.addEventListener ( MouseEvent.CLICK, captureClic, true ); // souscription à l'événement MouseEvent.CLICK auprès // du conteneur pour la phase de capture conteneurFenetres.addEventListener ( MouseEvent.CLICK, captureClicBis, true ); Chapitre 6 ? Propagation événementielle ? version 0.1.1 22 / 33 Thibault Imbert pratiqueactionscript3.bytearray.org function clicFenetre ( pEvt:Event ):void { // l'instance de Fenetre cliquée est supprimée de l'affichage conteneurFenetres.removeChild ( DisplayObject ( pEvt.target ) ); // désinscription de la fonction clicFenetre à l?événement // MouseEvent.CLICK pEvt.target.removeEventListener ( MouseEvent.CLICK, clicFenetre ); } function captureClic ( pEvt:MouseEvent ):void { // affiche : Objet cible : [object Fenetre] trace( "Objet cible : " + pEvt.target ); // affiche : Objet notifié : [object Sprite] trace( "Objet notifié : " + pEvt.currentTarget ); // affiche : Capture de l'événement : click trace("Capture de l'événement : " + pEvt.type ); if ( !pEvt.altKey ) pEvt.stopPropagation(); } function captureClicBis ( pEvt:MouseEvent ):void { trace("fonction écouteur captureClicBis déclenchée"); } En testant le code précédent, nous voyons que notre fonction captureClicBis est bien déclenchée bien que la propagation de l?événement soit interrompue. A l?inverse, si nous appelons la méthode stopImmediatePropagation, la fonction captureClicBis n?est plus déclenchée : function captureClic ( pEvt:MouseEvent ):void { // affiche : Objet cible : [object Fenetre] trace( "Objet cible : " + pEvt.target ); // affiche : Objet notifié : [object Sprite] trace( "Objet notifié : " + pEvt.currentTarget ); // affiche : Capture de l'événement : click trace("Capture de l'événement : " + pEvt.type ); if ( !pEvt.altKey ) pEvt.stopImmediatePropagation(); } Chapitre 6 ? Propagation événementielle ? version 0.1.1 23 / 33 Thibault Imbert pratiqueactionscript3.bytearray.org Grâce aux méthodes stopPropagation et stopImmediatePropagation nous pouvons ainsi maîtriser le flot événementiel avec précision. A retenir ? Il est possible d?intervenir sur la propagation d?un événement à l?aide des méthodes stopPropagation et stopImmediatePropagation. ? La méthode stopPropagation interrompt la propagation d?un événement mais n?empêche pas sa diffusion auprès des écouteurs du n?ud en cours. ? La méthode stopImmediatePropagation interrompt la propagation d?un événement et empêche sa diffusion même auprès des écouteurs du même n?ud. La phase de remontée La phase de remontée constitue la dernière phase de la propagation. Durant cette phase, l?événement parcourt le chemin inverse de la phase de capture, notifiant chaque n?ud parent de l?objet cible. Le parcourt de l?événement durant cette phase s?apparente à celui d?une bulle remontant à la surface de l?eau. C?est pour cette raison que cette phase est appelée en anglais « bubbling phase ». L?événement remontant de l?objet cible jusqu?à l?objet Stage. La figure 6-10 illustre la phase de remontée : Figure 6-10. Phase de remontée. Pour souscrire un écouteur auprès de la phase de remontée nous appelons la méthode addEventListener sur un des objets Chapitre 6 ? Propagation événementielle ? version 0.1.1 24 / 33 Thibault Imbert pratiqueactionscript3.bytearray.org graphiques parent en passant la valeur booléenne false au paramètre useCapture : monObjetParent.addEventListener ( MouseEvent.CLICK, clicRemontee, false ); En lisant la signature de la méthode addEventListener nous voyons que le paramètre useCapture vaut false par défaut : public function addEventListener(type:String, listener:Function, useCapture:Boolean = false, priority:int = 0, useWeakReference:Boolean = false):void Ainsi, la souscription auprès de la phase de remontée peut s?écrire sans préciser le paramètre useCapture : objetParent.addEventListener ( MouseEvent.CLICK, clicRemontee ); Cela signifie que lorsque nous écoutons un événement pour la phase cible, la phase de remontée est automatiquement écoutée. La fonction clicRemontee recevra donc aussi les événements MouseEvent.CLICK provenant des enfants durant leur phase de remontée. Dans l?exemple suivant, nous écoutons l?événement MouseEvent.CLICK lors de la phase de capture et de remontée auprès du conteneur : // nombre de fenêtres var lng:int = 12; // création d'un conteneur var conteneurFenetres:Sprite = new Sprite(); // ajout à la liste d'affichage addChild ( conteneurFenetres ); var maFenetre:Fenetre; for (var i:int = 0; i< lng; i++ ) { maFenetre = new Fenetre(); maFenetre.x = 7 + Math.round ( i % 3 ) * ( maFenetre.width + 10 ); maFenetre.y = 7 + Math.floor ( i / 3 ) * ( maFenetre.height + 10 ); conteneurFenetres.addChild ( maFenetre ); } // souscription auprès du conteneur pour la phase de capture conteneurFenetres.addEventListener ( MouseEvent.CLICK, captureClic, true ); // souscription auprès du conteneur pour la phase de remontée conteneurFenetres.addEventListener ( MouseEvent.CLICK, clicRemontee ); Chapitre 6 ? Propagation événementielle ? version 0.1.1 25 / 33 Thibault Imbert pratiqueactionscript3.bytearray.org function captureClic ( pEvt:MouseEvent ):void { /* affiche : phase de capture de l'événement : click noeud en cours de notification : [object Sprite] */ if ( pEvt.eventPhase == EventPhase.CAPTURING_PHASE ) { trace("phase de capture de l'événement : " + pEvt.type ); trace("noeud en cours de notification : " + pEvt.currentTarget ); } } function clicRemontee ( pEvt:MouseEvent ):void { /* affiche : phase de remontée de l'événement : click noeud en cours de notification : [object Sprite] */ if ( pEvt.eventPhase == EventPhase.BUBBLING_PHASE ) { trace("phase de remontée de l'événement : " + pEvt.type ); trace("noeud en cours de notification : " + pEvt.currentTarget ); } } Nous allons aller plus loin en ajoutant une réorganisation automatique des fenêtres lorsqu?une d?entre elles est supprimée. Pour cela nous modifions le code en intégrant un effet d?inertie afin de disposer chaque instance de la classe Fenetre avec un effet de ralenti : // modification de la cadence de l'animation stage.frameRate = 30; for (var i:int = 0; i< lng; i++ ) { maFenetre = new Fenetre(); maFenetre.destX = 7 + Math.round ( i % 3 ) * ( maFenetre.width + 10 ); maFenetre.destY = 7 + Math.floor ( i / 3 ) * ( maFenetre.height + 10 ); maFenetre.addEventListener ( Event.ENTER_FRAME, mouvement ); conteneurFenetres.addChild ( maFenetre ); Chapitre 6 ? Propagation événementielle ? version 0.1.1 26 / 33 Thibault Imbert pratiqueactionscript3.bytearray.org } function mouvement ( pEvt:Event ):void { // algorithme d?inertie pEvt.target.x -= ( pEvt.target.x - pEvt.target.destX ) * .3; pEvt.target.y -= ( pEvt.target.y - pEvt.target.destY ) * .3; } Puis nous intégrons la logique nécessaire au sein des fonctions écouteurs captureClic et clicRemontee : // nombre de fenêtres var lng:int = 12; // création d'un conteneur var conteneurFenetres:Sprite = new Sprite(); // ajout à la liste d'affichage addChild ( conteneurFenetres ); var maFenetre:Fenetre; // modification de la cadence de l'animation stage.frameRate = 30; for (var i:int = 0; i< lng; i++ ) { maFenetre = new Fenetre(); maFenetre.destX = 7 + Math.round ( i % 3 ) * ( maFenetre.width + 10 ); maFenetre.destY = 7 + Math.floor ( i / 3 ) * ( maFenetre.height + 10 ); maFenetre.addEventListener ( Event.ENTER_FRAME, mouvement ); conteneurFenetres.addChild ( maFenetre ); } function mouvement ( pEvt:Event ):void { // algorithme d?inertie pEvt.target.x -= ( pEvt.target.x - pEvt.target.destX ) * .3; pEvt.target.y -= ( pEvt.target.y - pEvt.target.destY ) * .3; } // souscription auprès du conteneur pour la phase de capture conteneurFenetres.addEventListener ( MouseEvent.CLICK, captureClic, true ); // souscription auprès du conteneur pour la phase de remontée conteneurFenetres.addEventListener ( MouseEvent.CLICK, clicRemontee ); Chapitre 6 ? Propagation événementielle ? version 0.1.1 27 / 33 Thibault Imbert pratiqueactionscript3.bytearray.org function captureClic ( pEvt:MouseEvent ):void { pEvt.currentTarget.removeChild ( DisplayObject ( pEvt.target ) ); } function clicRemontee ( pEvt:MouseEvent ):void { var lng:int = pEvt.currentTarget.numChildren; var objetGraphique:DisplayObject; var maFenetre:Fenetre; while ( lng-- ) { // récupération des objets graphiques objetGraphique = pEvt.currentTarget.getChildAt ( lng ); // si l'un d'entre eux est de type Fenetre if ( objetGraphique is Fenetre ) { // nous le transtypons en type Fenetre maFenetre = Fenetre ( objetGraphique ); // repositionnement de chaque occurrence maFenetre.destX = 7 + Math.round ( lng % 3 ) * ( maFenetre.width + 10 ); maFenetre.destY = 7 + Math.floor ( lng / 3 ) * ( maFenetre.height + 10 ); } } } Nous profitons de la phase de remontée pour que le conteneur réorganise ses objets enfants lorsque l?un d?entre eux a été supprimé de la liste d?affichage. Nous verrons au cours du chapitre 8 intitulé Programmation orientée objet comment notre code actuel pourrait être encore amélioré. Ecouter plusieurs phases Afin de suivre la propagation d?un événement, nous pouvons souscrire un écouteur auprès de chacune des phases. En affichant les valeurs des propriétés eventPhase, target et currentTarget de l?objet événementiel nous obtenons le chemin parcouru par l?événement. Chapitre 6 ? Propagation événementielle ? version 0.1.1 28 / 33 Thibault Imbert pratiqueactionscript3.bytearray.org Dans le code suivant, la fonction ecoutePhase est souscrite auprès des trois phases de l?événement MouseEvent.CLICK : // nombre de fenêtres var lng:int = 12; // création d'un conteneur var conteneurFenetres:Sprite = new Sprite(); // ajout à la liste d'affichage addChild ( conteneurFenetres ); var maFenetre:Fenetre; // modification de la cadence de l'animation stage.frameRate = 30; for (var i:int = 0; i< lng; i++ ) { maFenetre = new Fenetre(); maFenetre.destX = 7 + Math.round ( i % 3 ) * ( maFenetre.width + 10 ); maFenetre.destY = 7 + Math.floor ( i / 3 ) * ( maFenetre.height + 10 ); // souscription auprès de chaque instance pour la phase cible maFenetre.addEventListener ( MouseEvent.CLICK, ecoutePhase ); maFenetre.addEventListener ( Event.ENTER_FRAME, mouvement ); conteneurFenetres.addChild ( maFenetre ); } function mouvement ( pEvt:Event ):void { // algorithme d?inertie pEvt.target.x -= ( pEvt.target.x - pEvt.target.destX ) * .3; pEvt.target.y -= ( pEvt.target.y - pEvt.target.destY ) * .3; } // souscription auprès du conteneur pour la phase de remontée conteneurFenetres.addEventListener ( MouseEvent.CLICK, ecoutePhase ); // souscription auprès du conteneur pour la phase de capture conteneurFenetres.addEventListener ( MouseEvent.CLICK, ecoutePhase, true ); function ecoutePhase ( pEvt:MouseEvent ):void { /* affiche : 1 : Phase de capture de l'événement : click Objet cible : [object Fenetre] Objet notifié : [object Sprite] 2 : Phase cible de l'événement : click Chapitre 6 ? Propagation événementielle ? version 0.1.1 29 / 33 Thibault Imbert pratiqueactionscript3.bytearray.org Objet cible : [object Fenetre] Objet notifié : [object Fenetre] 3 : Phase de remontée de l'événement : click Objet cible : [object Fenetre] Objet notifié : [object Sprite] */ switch ( pEvt.eventPhase ) { case EventPhase.CAPTURING_PHASE : trace ( pEvt.eventPhase + " : Phase de capture de l'événement : " + pEvt.type ); break; case EventPhase.AT_TARGET : trace ( pEvt.eventPhase + " : Phase cible de l'événement : " + pEvt.type ); break; case EventPhase.BUBBLING_PHASE : trace ( pEvt.eventPhase + " : Phase de remontée de l'événement : " + pEvt.type ); break; } trace( "Objet cible : " + pEvt.target ); trace( "Objet notifié : " + pEvt.currentTarget ); } Le panneau de sortie nous affiche l?historique de la propagation de l?événement MouseEvent.CLICK. A retenir ? A l?instar de la phase de capture, la phase de remontée ne concerne que les objets parents. ? La phase de remontée permet à un objet parent d?être notifié d?un événement provenant de l?un de ses enfants. ? Un même écouteur peut être souscrit aux différentes phases d?un événement. La propriété Event.bubbles Pour savoir si un événement participe ou non à la phase de remontée nous pouvons utiliser la propriété bubbles de l?objet événementiel. Nous pouvons mettre à jour la fonction ecoutePhase afin de savoir si l?événement en cours participe à la phase de remontée : function ecoutePhase ( pEvt:MouseEvent ):void { Chapitre 6 ? Propagation événementielle ? version 0.1.1 30 / 33 Thibault Imbert pratiqueactionscript3.bytearray.org /* affiche : 1 : Phase de capture de l'événement : click Objet cible : [object Fenetre] Objet notifié : [object Sprite] Evénement participant à la phase de remontée : true 2 : Phase cible de l'événement : click Objet cible : [object Fenetre] Objet notifié : [object Fenetre] Evénement participant à la phase de remontée : true 3 : Phase de remontée de l'événement : click Objet cible : [object Fenetre] Objet notifié : [object Sprite] Evénement participant à la phase de remontée : true */ switch ( pEvt.eventPhase ) { case EventPhase.CAPTURING_PHASE : trace ( pEvt.eventPhase + " : Phase de capture de l'événement : " + pEvt.type ); break; case EventPhase.AT_TARGET : trace ( pEvt.eventPhase + " : Phase cible de l'événement : " + pEvt.type ); break; case EventPhase.BUBBLING_PHASE : trace ( pEvt.eventPhase + " : Phase de remontée de l'événement : " + pEvt.type ); break; } trace( "Objet cible : " + pEvt.target ); trace( "Objet notifié : " + pEvt.currentTarget ); trace ( "Evénement participant à la phase de remontée : " + pEvt.bubbles ); } Notons que tout événement se propageant vers le haut participe forcément aux phases de capture et cible. Suppression d?écouteurs Comme nous l?avons traité lors du chapitre 3 intitulé Le modèle événementiel, lorsque nous souhaitons arrêter l?écoute d?un événement nous utilisons la méthode removeEventListener. Lorsque nous avons souscrit un écouteur pour une phase spécifique nous devons spécifier la même phase lors de l?appel de la méthode removeEventListener. Si nous écoutons un événement pour la phase de capture : Chapitre 6 ? Propagation événementielle ? version 0.1.1 31 / 33 Thibault Imbert pratiqueactionscript3.bytearray.org objetParent.addEventListener ( MouseEvent.CLICK, clicBouton, true ); Pour supprimer l?écoute nous devons spécifier la phase concernée : objetParent.removeEventListener ( MouseEvent.CLICK, clicBouton, true ); Il en est de même pour les phases de remontée et cible : objetParent.removeEventListener ( MouseEvent.CLICK, clicBouton, false ); objetCible.removeEventListener ( MouseEvent.CLICK, clicBouton, false ); Ou bien de manière implicite : objetParent.removeEventListener ( MouseEvent.CLICK, clicBouton ); objetCible.removeEventListener ( MouseEvent.CLICK, clicBouton ); En intégrant cela dans notre exemple précédent, nous obtenons le code suivant : // nombre de fenêtres var lng:int = 12; // création d'un conteneur var conteneurFenetres:Sprite = new Sprite(); // ajout à la liste d'affichage addChild ( conteneurFenetres ); var maFenetre:Fenetre; // modification de la cadence de l'animation stage.frameRate = 30; for (var i:int = 0; i< lng; i++ ) { maFenetre = new Fenetre(); maFenetre.destX = 7 + Math.round ( i % 3 ) * ( maFenetre.width + 10 ); maFenetre.destY = 7 + Math.floor ( i / 3 ) * ( maFenetre.height + 10 ); // souscription auprès de chaque instance pour la phase cible maFenetre.addEventListener ( MouseEvent.CLICK, ecoutePhase ); // désinscription auprès de chaque occurrence pour la phase cible maFenetre.removeEventListener ( MouseEvent.CLICK, ecoutePhase ); maFenetre.addEventListener ( Event.ENTER_FRAME, mouvement ); conteneurFenetres.addChild ( maFenetre ); } function mouvement ( pEvt:Event ):void { // algorithme d?inertie Chapitre 6 ? Propagation événementielle ? version 0.1.1 32 / 33 Thibault Imbert pratiqueactionscript3.bytearray.org pEvt.target.x -= ( pEvt.target.x - pEvt.target.destX ) * .3; pEvt.target.y -= ( pEvt.target.y - pEvt.target.destY ) * .3; } // souscription auprès du conteneur pour la phase de capture conteneurFenetres.addEventListener ( MouseEvent.CLICK, ecoutePhase, true ); // désinscription auprès du conteneur pour la phase de capture conteneurFenetres.removeEventListener ( MouseEvent.CLICK, ecoutePhase, true ); // souscription auprès du conteneur pour la phase de remontée conteneurFenetres.addEventListener ( MouseEvent.CLICK, ecoutePhase ); // désinscription auprès du conteneur pour la phase de remontée conteneurFenetres.removeEventListener ( MouseEvent.CLICK, ecoutePhase ); function ecoutePhase ( pEvt:MouseEvent ):void { /* affiche : 1 : Phase de capture de l'événement : click Objet cible : [object Fenetre] Objet notifié : [object Sprite] 2 : Phase cible de l'événement : click Objet cible : [object Fenetre] Objet notifié : [object Fenetre] 3 : Phase de remontée de l'événement : click Objet cible : [object Fenetre] Objet notifié : [object Sprite] */ switch ( pEvt.eventPhase ) { case EventPhase.CAPTURING_PHASE : trace ( pEvt.eventPhase + " : Phase de capture de l'événement : " + pEvt.type ); break; case EventPhase.AT_TARGET : trace ( pEvt.eventPhase + " : Phase cible de l'événement : " + pEvt.type ); break; case EventPhase.BUBBLING_PHASE : trace ( pEvt.eventPhase + " : Phase de remontée de l'événement : " + pEvt.type ); break; } trace( "Objet cible : " + pEvt.target ); trace( "Objet notifié : " + pEvt.currentTarget ); trace ( "Evénement participant à la phase de remontée : " + pEvt.bubbles ); } Chapitre 6 ? Propagation événementielle ? version 0.1.1 33 / 33 Thibault Imbert pratiqueactionscript3.bytearray.org A retenir ? La propriété bubbles d?un objet événementiel permet de savoir si l?événement en cours participe à la phase de remontée. ? Lorsque nous avons souscrit un écouteur pour une phase spécifique nous devons spécifier la même phase lors de l?appel de la méthode removeEventListener. Nous avons abordé au cours des chapitres précédents un ensemble de concepts clés que nous allons pouvoir mettre à profit dès maintenant. En route pour le nouveau chapitre intitulé Interactivité ! Chapitre 7 ? Interactivité ? version 0.1.2 1 / 60 Thibault Imbert pratiqueactionscript3.bytearray.org 7 Interactivité AU C?UR DE FLASH............................................................................................ 2 INTERACTIVITE AVEC SIMPLEBUTTON ...................................................... 2 ENRICHIR GRAPHIQUEMENT UN SIMPLEBUTTON..................................................... 4 CREER UN MENU DYNAMIQUE ....................................................................... 9 MOUVEMENT PROGRAMMATIQUE ......................................................................... 14 LES PIEGES DU RAMASSE-MIETTES............................................................ 19 AJOUT DE COMPORTEMENT BOUTON ....................................................... 20 ZONE RÉACTIVE .................................................................................................... 23 GESTION DU FOCUS .......................................................................................... 29 POUR ALLER PLUS LOIN ................................................................................. 34 ESPACE DE COORDONNEES ........................................................................... 37 EVENEMENT GLOBAL...................................................................................... 40 MISE EN APPLICATION .................................................................................... 41 MISE A JOUR DU RENDU.................................................................................. 43 GESTION DU CLAVIER ..................................................................................... 45 DÉTERMINER LA TOUCHE APPUYÉE....................................................................... 46 GESTION DE TOUCHES SIMULTANÉES .................................................................... 48 SIMULER LA METHODE KEY.ISDOWN.................................................................... 54 SUPERPOSITION ................................................................................................. 55 L?ÉVÉNEMENT EVENT.RESIZE...................................................................... 58 Chapitre 7 ? Interactivité ? version 0.1.2 2 / 60 Thibault Imbert pratiqueactionscript3.bytearray.org Au c?ur de Flash L?interactivité est l?une des forces majeures et le c?ur du lecteur Flash. En un temps réduit, nous avons toujours pu réaliser une combinaison d?objets réactifs à la souris, au clavier ou autres. La puissance apportée par ActionScript 3 introduit quelques nuances relatives à l?interactivité, que nous allons découvrir ensemble à travers différents exercices pratiques. Interactivité avec SimpleButton Comme nous l?avons découvert lors du chapitre 5 intitulé Les symboles, la classe SimpleButton représente les symboles de type boutons en ActionScript 3. Cette classe s?avère très pratique en offrant une gestion avancée des boutons créés depuis l?environnement auteur ou par programmation. Il faut considérer l?objet SimpleButton comme un bouton constitué de quatre DisplayObject affectés à chacun de ses états. Chacun d?entre eux est désormais accessible par des propriétés dont voici le détail : ? SimpleButton.upState : définit l?état haut ? SimpleButton.overState : définit l?état dessus ? SimpleButton.downState : définit l?état abaissé ? SimpleButton.hitTestState : définit l?état cliqué Pour nous familiariser avec cette nouvelle classe nous allons tout d?abord créer un simple bouton, puis à l?aide de ce dernier nous construirons un menu dynamique. Dans un nouveau document Flash CS3, nous créons un symbole bouton, celui-ci est aussitôt ajouté à la bibliothèque : Chapitre 7 ? Interactivité ? version 0.1.2 3 / 60 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 7-1. Symbole bouton. Puis nous définissons une classe Bouton associée, à l?aide du panneau Propriétés de liaison. En définissant une classe associée nous pourrons instancier plus tard notre bouton par programmation. La figure 7-2 illustré le panneau : Figure 7-2. Définition de classe associée. Nous posons une occurrence de ce dernier sur le scénario principal et lui donnons monBouton comme nom d?occurrence. Chaque propriété relative à l?état du bouton renvoie un objet de type flash.display.Shape : /*affiche : Chapitre 7 ? Interactivité ? version 0.1.2 4 / 60 Thibault Imbert pratiqueactionscript3.bytearray.org [object Shape] [object Shape] [object Shape] [object Shape] */ trace( monBouton.upState ); trace( monBouton.overState ); trace( monBouton.downState ); trace( monBouton.hitTestState ); Chaque état peut être défini par n?importe quel objet de type DisplayObject. Lorsque nous créons un bouton dans l?environnement auteur et qu?une simple forme occupe l?état Haut, nous obtenons un objet Shape pour chaque état. Si nous avions créé un clip pour l?état Haut nous aurions récupéré un objet de type flash.display.MovieClip. La figure 7-3 illustre la correspondance entre chaque état et chaque propriété : Figure 7-3. Correspondance des propriétés d?états. Il était auparavant impossible d?accéder dynamiquement aux différents états d?un bouton. Nous verrons plus tard qu?un bouton, ainsi que ces états et les sons associés, peuvent être entièrement créés ou définis par programmation. Nous reviendrons très vite sur les autres nouveautés apportées par la classe SimpleButton. Pour enrichir graphiquement un bouton, nous devons comprendre comment celui-ci fonctionne, voyons à présent comment décorer notre bouton afin de le rendre utilisable pour notre menu. Enrichir graphiquement un SimpleButton Un bouton ne se limite généralement pas à une simple forme cliquable, une légende ou une animation ou d?autres éléments peuvent être ajoutés afin d?enrichir graphiquement le bouton. Avant de commencer à coder, il faut savoir que la classe SimpleButton est Chapitre 7 ? Interactivité ? version 0.1.2 5 / 60 Thibault Imbert pratiqueactionscript3.bytearray.org une classe particulière. Nous pourrions penser que celle-ci hérite de la classe DisplayObjectContainer, mais il n'en est rien. La classe SimpleButton hérite de la classe InteractiveObject et ne peut se voir ajouter du contenu à travers les méthodes traditionnelles telles addChild, addChildAt, etc. Seules les propriétés upState, downState, overState et hitTestSate permettent d?ajouter et d?accéder au contenu d?une occurrence de SimpleButton. Le seul moyen de supprimer un état du bouton est de passer la valeur null à un état du bouton : // supprime l'état Abaissé monBouton.downState = null; Si nous testons le code précédent, nous voyons que le bouton ne dispose plus d?état abaissé lorsque nous cliquons dessus. Pour ajouter une légende à notre occurrence de SimpleButton nous devons ajouter un champ texte à chaque objet graphique servant d?état. Si nous ajoutons directement un objet TextField à l?un des états nous le remplaçons : // création du champ texte servant de légende var maLegende:TextField = new TextField(); // nous affectons le contenu maLegende.text = "Légende Bouton"; // nous passons la légende en tant qu'état (Haut) du bouton monBouton.upState = maLegende; En testant le code précédent, nous obtenons un bouton cliquable constitué d?une légende comme état Haut. La figure 7-4 illustre le résultat : Chapitre 7 ? Interactivité ? version 0.1.2 6 / 60 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 7-4. Bouton avec légende comme état Haut. Lorsque nous survolons le bouton, les trois autres états sont conservés seul l?état upState (Haut) a été écrasé. Ce comportement nous apprend que même si un seul état a été défini depuis l?environnement auteur, le lecteur copie l?état Haut pour chaque état du bouton. Si nous altérons un état les autres continuent de fonctionner. Ce n?est pas le résultat que nous avions escompté, il va nous falloir utiliser une autre technique. Le problème vient du fait que nous n?ajoutons pas le champ texte à un objet servant d?état mais nous remplaçons un état par un champ texte. Afin d?obtenir ce que nous souhaitons nous allons depuis l?environnement auteur convertir l?état Haut en clip et y imbriquer un champ texte auquel nous donnons maLegende comme nom d?occurrence. Chapitre 7 ? Interactivité ? version 0.1.2 7 / 60 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 7-4. Clip avec champ texte imbriqué pour l?état Haut. Nous devons obtenir un clip positionné sur l?état Haut, avec un champ texte imbriqué comme l?illustre la figure 7-5. Figure 7-5. Liste d?affichage du bouton. Si nous ciblons l?état upState de notre occurrence, nous récupérons notre clip tout juste créé : // récupération du clip positionné pour l'état Haut // affiche : [object MovieClip] trace( monBouton.upState ); Puis nous ciblons le champ texte par la syntaxe pointée traditionnelle : // variable référençant le clip utilisé pour l'état Haut var etatHaut:MovieClip = MovieClip ( monBouton.upState ); // affectation de contenu etatHaut.maLegende.text = "Ma légende"; Chapitre 7 ? Interactivité ? version 0.1.2 8 / 60 Thibault Imbert pratiqueactionscript3.bytearray.org Nous pourrions être tentés de donner un nom d?occurrence au clip positionné sur l?état haut, mais souvenons-nous que seules les propriétés upState, downState, overState et hitTestState permettent d?accéder aux différents états. Même si notre clip imbriqué s?appelait monClipImbrique le code suivant renverrait undefined : // affiche : undefined trace( monBouton.monClipImbrique ); Par défaut le lecteur Flash duplique l?état Haut pour chaque état à la compilation. Ce qui garantie que lorsqu?un état vient à être modifié à l?exécution, comme pour l?affectation de contenu au sein du champ texte, les autres états ne reflètent pas la modification. Lorsque nous souhaitons avoir un seul état global au bouton, nous trompons le lecteur en affectant notre clip servant d?état Haut à chaque état : // variable référençant le clip utilisé pour l'état Haut var etatHaut:MovieClip = MovieClip ( monBouton.upState ); // affectation de contenu etatHaut.maLegende.text = "Ma légende"; //affectation du clip pour tous les états monBouton.upState = etatHaut; monBouton.downState = etatHaut; monBouton.overState = etatHaut; monBouton.hitTestState = etatHaut; Nous obtenons le résultat suivant : Chapitre 7 ? Interactivité ? version 0.1.2 9 / 60 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 7-6. Occurrence de SimpleButton décoré. Une fois notre symbole bouton décoré, passons maintenant à son intégration au sein d?un menu. A retenir ? La classe SimpleButton est une classe particulière et n?hérite pas de la classe DisplayObjectContainer. ? Chaque état d?un SimpleButton est défini par quatre propriétés : upState, overState, downState et hitTestState. Créer un menu dynamique Toute application ou site internet Flash se doit d?intégrer une interface de navigation permettant à l?utilisateur de naviguer au sein du contenu. Le menu figure parmi les classiques du genre, nous avons tous développé au moins une fois un menu. La mise en application de ce dernier va s?avérer intéressante afin de découvrir de nouveaux comportements apportés par ActionScript 3. Nous allons ensemble créer un menu dynamique en intégrant le bouton sur lequel nous avons travaillé jusqu?à maintenant. Notre menu sera déployé verticalement, nous instancions chaque occurrence à travers une boucle for : Chapitre 7 ? Interactivité ? version 0.1.2 10 / 60 Thibault Imbert pratiqueactionscript3.bytearray.org // création du conteneur var conteneur:Sprite = new Sprite(); conteneur.x = 20; addChild ( conteneur ); function creeMenu ():void { var lng:int = 5; var monBouton:Bouton; for ( var i:int = 0; i< lng; i++ ) { // création des occurrences du symbole Bouton monBouton = new Bouton(); // variable référençant le clip utilisé pour l'état Haut var etatHaut:MovieClip = MovieClip ( monBouton.upState ); //affectation du clip pour tous les états monBouton.upState = etatHaut; monBouton.downState = etatHaut; monBouton.overState = etatHaut; monBouton.hitTestState = etatHaut; // disposition des instances monBouton.y = 20 + i * (monBouton.height + 10); conteneur.addChild ( monBouton ); } } creeMenu(); En testant notre code, nous obtenons le résultat illustré par la figure suivante : Chapitre 7 ? Interactivité ? version 0.1.2 11 / 60 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 7-7. Instances de symboles Bouton. Durant la boucle for nous décalons chaque bouton du menu grâce à la valeur de la variable i qui est incrémentée. En multipliant la hauteur de chaque occurrence par i nous obtenons une position y spécifique à chaque bouton. Très souvent, un menu est généré dynamiquement à partir de données externes provenant d?un tableau local, d?un fichier XML local, ou de données provenant d?un serveur. Nous allons modifier le code précédent en définissant un tableau contenant le nom des rubriques à représenter au sein du menu. Le nombre de boutons du menu sera lié au nombre d?éléments du tableau : // les rubriques var legendes:Array = new Array ( "Accueil", "Photos", "Liens", "Contact" ); // création du conteneur var conteneur:Sprite = new Sprite(); conteneur.x = 20; addChild ( conteneur ); function creeMenu ():void { Chapitre 7 ? Interactivité ? version 0.1.2 12 / 60 Thibault Imbert pratiqueactionscript3.bytearray.org var lng:int = legendes.length; var monBouton:Bouton; for ( var i:int = 0; i< lng; i++ ) { // création des occurrences du symbole Bouton monBouton = new Bouton(); // variable référençant le clip utilisé pour l'état Haut var etatHaut:MovieClip = MovieClip ( monBouton.upState ); //affectation du clip pour tous les états monBouton.upState = etatHaut; monBouton.downState = etatHaut; monBouton.overState = etatHaut; monBouton.hitTestState = etatHaut; // disposition des instances monBouton.y = 20 + i * (monBouton.height + 10); conteneur.addChild ( monBouton ); } } creeMenu(); Notre menu est maintenant lié au nombre d?éléments du menu, si nous retirons ou ajoutons des éléments au tableau source de données, notre menu sera mis à jour automatiquement. Afin que chaque bouton affiche une légende spécifique nous récupérons chaque valeur contenue dans le tableau et l?affectons à chaque champ texte contenu dans les boutons. Nous pouvons facilement récupérer le contenu du tableau au sein de la boucle, grâce à la syntaxe crochet : function creeMenu ():void { var lng:int = legendes.length; var monBouton:Bouton; for ( var i:int = 0; i< lng; i++ ) { // création des occurrences du symbole Bouton monBouton = new Bouton(); /* affiche : Accueil Photos Liens Chapitre 7 ? Interactivité ? version 0.1.2 13 / 60 Thibault Imbert pratiqueactionscript3.bytearray.org Contact */ trace( legendes[i] ); // variable référençant le clip utilisé pour l'état Haut var etatHaut:MovieClip = MovieClip ( monBouton.upState ); //affectation du clip pour tous les états monBouton.upState = etatHaut; monBouton.downState = etatHaut; monBouton.overState = etatHaut; monBouton.hitTestState = etatHaut; // disposition des instances monBouton.y = 20 + i * (monBouton.height + 10); conteneur.addChild ( monBouton ); } } Au sein de la boucle nous ciblons notre champ texte et nous lui affectons le contenu : function creeMenu ():void { var lng:int = legendes.length; var monBouton:Bouton; for ( var i:int = 0; i< lng; i++ ) { // création des occurrences du symbole Bouton monBouton = new Bouton(); // variable référençant le clip utilisé pour l'état Haut var etatHaut:MovieClip = MovieClip ( monBouton.upState ); // affectation du contenu etatHaut.maLegende.text = legendes[i]; //affectation du clip pour tous les états monBouton.upState = etatHaut; monBouton.downState = etatHaut; monBouton.overState = etatHaut; monBouton.hitTestState = etatHaut; // disposition des instances monBouton.y = 20 + i * (monBouton.height + 10); conteneur.addChild ( monBouton ); } } Chapitre 7 ? Interactivité ? version 0.1.2 14 / 60 Thibault Imbert pratiqueactionscript3.bytearray.org En testant le code précédent nous obtenons le résultat illustré par la figure 7-8 : Figure 7-8. Menu dynamique avec légendes. Notre menu est terminé, nous pourrions en rester là mais il faut avouer que ce dernier manque de vie. Nous allons lui donner vie en ajoutant un peu de mouvement. A l?aide d?un effet de glissement, nous allons rendre ce dernier plus attrayant. En route vers la notion de mouvement programmatique ! Mouvement programmatique Afin de créer différents mouvements par programmation nous pouvons utiliser nos propres algorithmes, différentes librairies open- source ou bien la classe Tween intégrée à Flash CS3. La classe Tween réside dans le paquetage fl.transitions et doit être importée afin d?être utilisée. Afin de donner du mouvement à un objet graphique dans Flash, nous pouvons utiliser les événements Event.ENTER_FRAME ou TimerEvent.TIMER. La classe Tween utilise en interne un événement Event.ENTER_FRAME afin de modifier la propriété de l?objet. Un large nombre de mouvements sont disponibles, de type cinétique, élastique, rebond ou bien constant. Chapitre 7 ? Interactivité ? version 0.1.2 15 / 60 Thibault Imbert pratiqueactionscript3.bytearray.org Nous allons ajouter un effet d?élasticité permettant à notre menu d?être déployé de manière ludique. Pour cela nous associons un objet Tween à chaque instance de bouton. Chaque référence à l?objet Tween est stockée au sein de l?occurrence bouton pour pouvoir être récupérée facilement plus tard. La classe Tween n?est pas automatiquement importée pour le compilateur, nous devons donc le faire afin de l?utiliser. Nous écoutons l?événement MouseEvent.CLICK des boutons : // import des classes Tween et Elastic pour le type de mouvement import fl.transitions.Tween; import fl.transitions.easing.Elastic; // les rubriques var legendes:Array = new Array ( "Accueil", "Photos", "Liens", "Contact" ); // création du conteneur var conteneur:Sprite = new Sprite(); conteneur.x = 20; addChild ( conteneur ); function creeMenu ():void { var lng:int = legendes.length; var monBouton:Bouton; for (var i:int = 0; i< lng; i++ ) { // création des occurrences du symbole Bouton monBouton = new Bouton(); // variable référençant le clip utilisé pour l'état Haut var etatHaut:MovieClip = MovieClip ( monBouton.upState ); etatHaut.maLegende.text = legendes[i]; //affectation du clip pour tous les états monBouton.upState = etatHaut; monBouton.downState = etatHaut; monBouton.overState = etatHaut; monBouton.hitTestState = etatHaut; // disposition des instances monBouton.tween = new Tween ( monBouton, "y", Elastic.easeOut, 0, 20 + i * (monBouton.height + 10), 3, true ); conteneur.addChild ( monBouton ); Chapitre 7 ? Interactivité ? version 0.1.2 16 / 60 Thibault Imbert pratiqueactionscript3.bytearray.org } } creeMenu(); // capture de l'événement MouseEvent.CLICK auprès du conteneur conteneur.addEventListener ( MouseEvent.CLICK, clicMenu, true ); function clicMenu ( pEvt:MouseEvent ):void { // affiche : [object Bouton] trace( pEvt.target ); // affiche : [object Sprite] trace( pEvt.currentTarget ); } La position dans l?axe des y de chaque bouton est désormais gérée par notre objet Tween. En stockant chaque objet Tween créé au sein des boutons nous pourrons y faire référence à n?importe quel moment en ciblant plus tard la propriété tween. Notre menu se déploie avec un effet d?élasticité et devient beaucoup plus interactif. Nous ne sommes pas restreints à un seul type de mouvement, nous allons aller plus loin en ajoutant un effet similaire lors du survol. Cette fois, le mouvement se fera sur la largeur des boutons. Nous stockons une nouvelle instance de la classe Tween pour gérer l?effet de survol de chaque bouton : // import des classes Tween et Elastic pour le type de mouvement import fl.transitions.Tween; import fl.transitions.easing.Elastic; // les rubriques var legendes:Array = new Array ( "Accueil", "Photos", "Liens", "Contact" ); // création du conteneur var conteneur:Sprite = new Sprite(); conteneur.x = 20; addChild ( conteneur ); function creeMenu ():void { var lng:int = legendes.length; var monBouton:Bouton; for ( var i:int = 0; i< lng; i++ ) { Chapitre 7 ? Interactivité ? version 0.1.2 17 / 60 Thibault Imbert pratiqueactionscript3.bytearray.org // création des occurrences du symbole Bouton monBouton = new Bouton(); // variable référençant le clip utilisé pour l'état Haut var etatHaut:MovieClip = MovieClip ( monBouton.upState ); etatHaut.maLegende.text = legendes[i]; //affectation du clip pour tous les états monBouton.upState = etatHaut; monBouton.downState = etatHaut; monBouton.overState = etatHaut; monBouton.hitTestState = etatHaut; // disposition des instances monBouton.tween = new Tween ( monBouton, "y", Elastic.easeOut, 0, 20 + i * (monBouton.height + 10), 3, true ); // un objet Tween est créé pour les effets de survol monBouton.tweenSurvol = new Tween ( monBouton, "scaleX", Elastic.easeOut, 1, 1, 2, true ); conteneur.addChild ( monBouton ); } } creeMenu(); // capture de l'événement MouseEvent.CLICK auprès du conteneur conteneur.addEventListener ( MouseEvent.CLICK, clicMenu, true ); conteneur.addEventListener ( MouseEvent.ROLL_OVER, survolBouton, true ); conteneur.addEventListener ( MouseEvent.ROLL_OUT, quitteBouton, true ); function survolBouton ( pEvt:MouseEvent ):void { var monTween:Tween = pEvt.target.tweenSurvol; monTween.continueTo ( 1.1, 2 ); } function quitteBouton ( pEvt:MouseEvent ):void { var monTween:Tween = pEvt.target.tweenSurvol; monTween.continueTo ( 1, 2 ); } function clicMenu ( pEvt:MouseEvent ):void { // affiche : [object Bouton] trace( pEvt.target ); Chapitre 7 ? Interactivité ? version 0.1.2 18 / 60 Thibault Imbert pratiqueactionscript3.bytearray.org // affiche : [object Sprite] trace( pEvt.currentTarget ); } Au sein des fonctions survolBouton et quitteBouton nous récupérons l?objet Tween associé à l?objet survolé au sein de la variable monTween. Grâce à la méthode continueTo de l?objet Tween, nous pouvons redémarrer, stopper ou bien lancer l?animation dans le sens inverse. Dans le code précédent nous avions défini les valeurs de départ et d?arrivée pour l?étirement du bouton. Nous augmentons de 10% la taille du bouton au survol et nous ramenons sa taille à 100% lorsque nous quittons le survol du bouton. Nous obtenons un menu élastique réagissant au survol, mais il nous reste une chose à optimiser. Si nous regardons bien, nous voyons que le champ texte interne au bouton est lui aussi redimensionné. Ce problème intervient car nous redimensionnons l?enveloppe principale du bouton dans lequel se trouve notre champ texte. En étirant l?enveloppe conteneur nous étirons les enfants et donc la légende. Nous allons modifier notre symbole Bouton afin de pouvoir redimensionner la forme de fond de notre bouton sans altérer le champ texte. Pour cela, au sein du symbole Bouton nous éditons le clip placé sur l?état Haut et transformons la forme de fond en clip auquel nous donnons fondBouton comme nom d?occurrence. Nous allons ainsi redimensionner le clip interne à l?état upState, sans altérer le champ texte. Nous modifions notre code en associant l?objet Tween au clip fondBouton : // un objet Tween est créé pour les effets de survol monBouton.tweenSurvol = new Tween ( etatHaut.fondBouton, "scaleX", Elastic.easeOut, 1, 1, 2, true ); Au final, la classe SimpleButton s?avère très pratique mais ne facilite pas tellement la tâche dès lors que nos boutons sont quelque peu travaillés. Contrairement à la classe Button en ActionScript 1 et 2, la classe SimpleButton peut diffuser un événement Event.ENTER_FRAME. L?utilisation de la classe SimpleButton s?avère limité de par son manque de souplesse en matière de décoration. Nous allons refaire le même exercice en utilisant cette fois des instances de Sprite auxquels nous ajouterons un comportement bouton, une fois terminé nous ferons un bilan. Chapitre 7 ? Interactivité ? version 0.1.2 19 / 60 Thibault Imbert pratiqueactionscript3.bytearray.org A retenir ? La classe Tween permet d?ajouter du mouvement à nos objets graphiques. ? Celle-ci réside dans le paquetage fl.transitions et doit être importée explicitement afin d?être utilisée. ? Les différentes méthodes et propriétés de la classe Tween permettent de gérer le mouvement. Les pièges du ramasse-miettes Comme nous l?avons vu depuis le début de l?ouvrage, le ramasse- miettes figure parmi les éléments essentiels à prendre en considération lors du développement d?applications ActionScript 3. Dans l?exemple précédent, nous avons utilisé la classe Tween afin de gérer les mouvements de chaque bouton. Une ancienne habitude provenant des précédentes versions d?ActionScript pourrait nous pousser à ne pas conserver de références aux objets Tween. Dans le code suivant, nous avons modifié la fonction creeMenu de manière à ne pas stocker de références aux objets Tween : function creeMenu ():void { var lng:int = legendes.length; var monBouton:Bouton; var tweenMouvement:Tween; var tweenSurvol:Tween; for ( var i:int = 0; i< lng; i++ ) { // création des occurrences du symbole Bouton monBouton = new Bouton(); // variable référençant le clip utilisé pour l'état Haut var etatHaut:MovieClip = MovieClip ( monBouton.upState ); etatHaut.maLegende.text = legendes[i]; //affectation du clip pour tous les états monBouton.upState = etatHaut; monBouton.downState = etatHaut; monBouton.overState = etatHaut; monBouton.hitTestState = etatHaut; // disposition des instances tweenMouvement = new Tween ( monBouton, "y", Elastic.easeOut, 0, 20 + i * (monBouton.height + 10), 3, true ); Chapitre 7 ? Interactivité ? version 0.1.2 20 / 60 Thibault Imbert pratiqueactionscript3.bytearray.org // un objet Tween est créé pour les effets de survol tweenSurvol = new Tween ( etatHaut.fondBouton, "scaleX", Elastic.easeOut, 1, 1, 2, true ); conteneur.addChild ( monBouton ); } } Une fois l?exécution de la fonction creeMenu, les variables tweenMouvement et tweenSurvol sont supprimées. Nos objets Tween ne sont plus référencés au sein de notre application. Si nous déclenchons manuellement le passage du ramasse-miettes après la création du menu : creeMenu(); // déclenchement du ramasse-miettes System.gc(); Nous remarquons que le mouvement de chaque bouton est interrompu, car le ramasse-miettes vient de supprimer les objets Tween de la mémoire. A retenir ? Veillez à bien référencer les objets nécessaires afin qu?ils ne soient pas supprimés par le ramasse-miettes sans que vous ne l?ayez décidé. Ajout de comportement bouton Un grand nombre de développeurs Flash utilisaient en ActionScript 1 et 2 des symboles clips en tant que boutons. En définissant l?événement onRelease ces derniers devenaient cliquables. En ActionScript 3 le même comportement peut être obtenu en activant la propriété buttonMode sur une occurrence de Sprite ou MovieClip. Dans un nouveau document, nous créons un symbole Sprite grâce à la technique abordée lors du chapitre 5 intitulé Les symboles. Nous lui associons Bouton comme nom de classe grâce au panneau Propriétés de Liaison, puis nous transformons la forme contenue dans ce dernier en un nouveau clip. Nous lui donnons fondBouton comme nom d?occurrence. Au dessus de ce clip nous créons un champ texte dynamique que nous appelons maLegende : Chapitre 7 ? Interactivité ? version 0.1.2 21 / 60 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 7-9. Symbole clip. Puis nous disposons nos instances de Bouton verticalement : // les rubriques var legendes:Array = new Array ( "Accueil", "Nouveautés", "Photos", "Liens", "Contact" ); // création du conteneur var conteneur:Sprite = new Sprite(); conteneur.x = 20; addChild ( conteneur ); function creeMenu ():void { // nombre de rubriques var lng:int = legendes.length; var monBouton:Bouton; for ( var i:int = 0; i< lng; i++ ) { Chapitre 7 ? Interactivité ? version 0.1.2 22 / 60 Thibault Imbert pratiqueactionscript3.bytearray.org // instanciation du symbole Bouton monBouton = new Bouton(); monBouton.y = 20 + i * (monBouton.height + 10); // ajout à la liste d'affichage conteneur.addChild ( monBouton ); } } creeMenu(); Afin d?activer le comportement bouton sur des objets autres que SimpleButton nous devons activer la propriété buttonMode : // activation du comportement bouton monBouton.buttonMode = true; Puis nous ajoutons le texte : function creeMenu ():void { // nombre de rubriques var lng:int = legendes.length; var monBouton:Bouton; for ( var i:int = 0; i< lng; i++ ) { // instanciation du symbole Bouton monBouton = new Bouton(); monBouton.y = 20 + i * (monBouton.height + 10); // activation du comportement bouton monBouton.buttonMode = true; // affectation du contenu monBouton.maLegende.text = legendes[i]; // ajout à la liste d'affichage conteneur.addChild ( monBouton ); } } En utilisant d?autres objets que la classe SimpleButton pour créer des boutons nous devons prendre en considération certains comportements comme la réactivité des objets imbriqués avec la souris. La manière dont les objets réagissent aux événements souris a été modifiée en ActionScript 3. Nous allons à présent découvrir ces subtilités. Chapitre 7 ? Interactivité ? version 0.1.2 23 / 60 Thibault Imbert pratiqueactionscript3.bytearray.org Zone réactive Si nous survolons les boutons de notre menu, nous remarquons que le curseur représentant une main ne s?affiche pas sur la totalité de la surface des boutons. Le comportement est illustré par la figure suivante : Figure 7-10. Zone réactive. Si nous cliquons sur la zone occupée par le champ texte imbriqué dans chaque bouton, l?objet cible n?est plus le bouton mais le champ texte. A l?inverse si nous cliquons sur la zone non occupée par le champ texte, l?objet cible est le clip fondBouton. Ainsi, en cliquant sur les boutons de notre menu, l?objet réactif peut être le clip ou le champ texte imbriqué. Nous capturons l?événement MouseEvent.CLICK auprès du conteneur : // capture de l'événement MouseEvent.CLICK auprès du conteneur conteneur.addEventListener ( MouseEvent.CLICK, clicMenu, true ); function clicMenu ( pEvt:MouseEvent ):void { // affiche : [object TextField] trace( pEvt.target ); Chapitre 7 ? Interactivité ? version 0.1.2 24 / 60 Thibault Imbert pratiqueactionscript3.bytearray.org // affiche : [object Sprite] trace( pEvt.currentTarget ); } A chaque clic la fonction clicMenu est déclenchée, la propriété target de l?objet événementiel renvoie une référence vers l?objet cible de l?événement. La propriété currentTarget référence le conteneur actuellement notifié de l?événenement MouseEvent.CLICK. Souvenez-vous, la propriété target référence l?objet cible de l?événement. La propriété currentTarget référence l?objet sur lequel nous avons appelé la méthode addEventListener. Si nous cliquons sur le bouton, les objets enfants réagissent aux entrées souris. La propriété target renvoie une référence vers chaque objet interactif. Si nous cliquons à côté du champ texte imbriqué, le clip fondBouton réagit : function clicMenu ( pEvt:MouseEvent ):void { // affiche : [object MovieClip] trace( pEvt.target ); } Si nous cliquons sur le champ texte imbriqué la propriété target renvoie une référence vers ce dernier : function clicMenu ( pEvt:MouseEvent ):void { // affiche : [object TextField] trace( pEvt.target ); } Afin d?être sûrs que notre zone sensible sera uniquement l?enveloppe parente nous désactivons la sensibilité à la souris pour tous les objets enfants grâce à la propriété mouseChildren. Ainsi, l?objet cible sera l?enveloppe principale du bouton : // désactivation des objets enfants monBouton.mouseChildren = false; Une fois la propriété mouseChildren passée à false, l?ensemble des objets enfants au bouton ne réagissent plus à la souris. Notre bouton est parfaitement cliquable sur toute sa surface : Chapitre 7 ? Interactivité ? version 0.1.2 25 / 60 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 7-11. Désactivation des objets enfants. En cliquant sur chacun des boutons, la propriété target référence le bouton cliqué : function clicMenu ( pEvt:MouseEvent ):void { // affiche : [object Bouton] trace( pEvt.target ); } Les boutons du menu sont désormais parfaitement cliquables, nous allons intégrer la classe Tween que nous avons utilisé dans l?exemple précédent : // import des classes Tween et Elastic pour le type de mouvement import fl.transitions.Tween; import fl.transitions.easing.Elastic; // création du conteneur var conteneur:Sprite = new Sprite(); conteneur.x = 20; addChild ( conteneur ); // les rubriques var legendes:Array = new Array ( "Accueil", "Nouveautés", "Photos", "Liens", "Contact" ); Chapitre 7 ? Interactivité ? version 0.1.2 26 / 60 Thibault Imbert pratiqueactionscript3.bytearray.org function creeMenu () { var lng:int = legendes.length; var monBouton:Bouton; for ( var i:int = 0; i< lng; i++ ) { // création des occurrences du symbole Bouton monBouton = new Bouton(); // activation du comportement bouton monBouton.buttonMode = true; // désactivation des objets enfants monBouton.mouseChildren = false; // affectation du contenu monBouton.maLegende.text = legendes[i]; // disposition des instances monBouton.tween = new Tween ( monBouton, "y", Elastic.easeOut, 0, 20 + i * (monBouton.height + 10), 3, true ); // un objet Tween est créé pour les effets de survol monBouton.tweenSurvol = new Tween ( monBouton.fondBouton, "scaleX", Elastic.easeOut, 1, 1, 2, true ); conteneur.addChild ( monBouton ); } } creeMenu(); // capture de l'événement MouseEvent.CLICK auprès du conteneur conteneur.addEventListener ( MouseEvent.CLICK, clicMenu, true ); conteneur.addEventListener ( MouseEvent.ROLL_OVER, survolBouton, true ); conteneur.addEventListener ( MouseEvent.ROLL_OUT, quitteBouton, true ); function survolBouton ( pEvt:MouseEvent ):void { var monTween:Tween = pEvt.target.tweenSurvol; monTween.continueTo ( 1.1, 2 ); } function quitteBouton ( pEvt:MouseEvent ):void { var monTween:Tween = pEvt.target.tweenSurvol; monTween.continueTo ( 1, 2 ); Chapitre 7 ? Interactivité ? version 0.1.2 27 / 60 Thibault Imbert pratiqueactionscript3.bytearray.org } function clicMenu ( pEvt:MouseEvent ):void { // affiche : [object Bouton] trace( pEvt.target ); // affiche : [object Sprite] trace( pEvt.currentTarget ); } Nous obtenons le même menu qu?auparavant à l?aide d?occurrences de SimpleButton. Si nous souhaitons donner un style différent à notre menu, nous pouvons jouer avec les propriétés et méthodes des objets Tween. Dans le code suivant nous disposons le menu avec un effet de rotation. Pour cela nous modifions simplement les lignes gérant la disposition des occurrences : function creeMenu () { var lng:int = legendes.length; var monBouton:Bouton; var angle:int = 360 / lng; for ( var i:int = 0; i< lng; i++ ) { // création des occurrences du symbole Bouton monBouton = new Bouton(); // activation du comportement bouton monBouton.buttonMode = true; // désactivation des objets enfants monBouton.mouseChildren = false; // affectation du contenu monBouton.maLegende.text = legendes[i]; // disposition des instances monBouton.tween = new Tween ( monBouton, "rotation", Elastic.easeOut, 0, i * angle, 3, true ); // un objet Tween est créé pour les effets de survol monBouton.tweenSurvol = new Tween ( monBouton.fondBouton, "scaleX", Elastic.easeOut, 1, 1, 2, true ); conteneur.addChild ( monBouton ); } Chapitre 7 ? Interactivité ? version 0.1.2 28 / 60 Thibault Imbert pratiqueactionscript3.bytearray.org } Puis nous déplacons le conteneur : conteneur.x = 150; conteneur.y = 150; Si le texte des boutons disparaît, cela signifie que nous n?avons pas intégré les contours de polices pour nos champs texte. Le lecteur Flash ne peut rendre à l?affichage un champ texte ayant subi une rotation ou un masque, s?il contient une police non embarquée. Il faut toujours s?assurer d?avoir bien intégré les contours de polices. Une fois les contours de polices intégrés, en modifiant simplement ces quelques lignes nous obtenons un menu totalement différent comme l?illustre la figure 7-12 : Figure 7-12. Disposition du menu avec effet de rotation. Libre à vous d?imaginer toutes sortes d?effets en travaillant avec les différentes propriétés de la classe DisplayObject. Dans le code précédent nous avons modifié la propriété rotation. Une application Flash peut prendre en compte des comportements interactifs relativement subtils comme la perte de focus de Chapitre 7 ? Interactivité ? version 0.1.2 29 / 60 Thibault Imbert pratiqueactionscript3.bytearray.org l?animation. Le lecteur Flash 9 peut en ActionScript 3 détecter facilement la perte et le gain du focus de l?animation. Nous allons adapter notre menu afin qu?il prenne en considération cette fonctionnalité. A retenir ? La propriété buttonMode affecte un comportement bouton aux objets MovieClip et Sprite. ? Pour s?assurer que l?enveloppe principale seulement reçoive les entrées souris, nous passons la valeur false à la propriété mouseChildren. Gestion du focus Nous allons travailler sur la gestion du focus à présent, deux nouveaux événements ont vu le jour en ActionScript 3 : ? Event.ACTIVATE : événement diffusé lorsque le lecteur Flash gagne le focus. ? Event.DEACTIVATE : événement diffusé lorsque le lecteur Flash perd le focus. Ces deux événements sont diffusés par tout DisplayObject présent ou non au sein de la liste d?affichage. Grâce à ces événements nous allons pouvoir fermer le menu lorsque l?animation perdra le focus puis le rouvrir à l?inverse. Pour savoir quand l?utilisateur n?a plus le focus sur le lecteur il suffit d?écouter l?événement Event.DEACTIVATE de n?importe quel DisplayObject, qu?il soit sur la liste d?affichage ou non : // souscription auprès de l'événement Event.DEACTIVATE auprès du conteneur conteneur.addEventListener ( Event.DEACTIVATE, perteFocus ); function perteFocus ( pEvt:Event ):void { // récupération du nombre de boutons var lng:int = pEvt.target.numChildren; var bouton:Bouton; for (var i:int = 0; i< lng; i++ ) { // nous récupérons chaque bouton du menu bouton = Bouton ( pEvt.target.getChildAt ( i ) ); // nous ciblons chaque objet Tween var myTween:Tween = bouton.tween; Chapitre 7 ? Interactivité ? version 0.1.2 30 / 60 Thibault Imbert pratiqueactionscript3.bytearray.org myTween.func = Strong.easeOut; // nous définissons les points de départ et d'arrivée myTween.continueTo ( i * 10, 1 ); } } Attention, la classe Strong doit être importée : import fl.transitions.Tween; import fl.transitions.easing.Elastic; import fl.transitions.easing.Strong; Lorsque l?animation perd le focus notre menu se referme avec un effet d?inertie, la figure suivante illustre le résultat : Figure 7-13. Menu refermé. Il nous faut ouvrir à nouveau le menu lorsque l?application récupère le focus, pour cela nous écoutons l?événement Event.ACTIVATE : function perteFocus ( pEvt:Event ):void { // récupération du nombre de boutons var lng:int = pEvt.target.numChildren; var bouton:Bouton; for (var i:int = 0; i< lng; i++ ) { // nous récupérons chaque bouton du menu bouton = Bouton ( pEvt.target.getChildAt ( i ) ); // nous ciblons chaque objet Tween var myTween:Tween = bouton.tween; myTween.func = Strong.easeOut; // nous définissons les points de départ et d'arrivée myTween.continueTo ( i * 10, 1 ); } if( pEvt.target.hasEventListener( Event.ACTIVATE) == false ) Chapitre 7 ? Interactivité ? version 0.1.2 31 / 60 Thibault Imbert pratiqueactionscript3.bytearray.org { // souscription auprès de l'événement Event.ACTIVATE auprès du conteneur pEvt.target.addEventListener ( Event.ACTIVATE, gainFocus ); } } function gainFocus ( pEvt:Event ):void { // récupération du nombre de boutons var lng:int = pEvt.target.numChildren; var bouton:Bouton; for (var i:int = 0; i< lng; i++ ) { // nous récupérons chaque bouton du menu bouton = Bouton ( pEvt.target.getChildAt ( i ) ); // nous ciblons chaque objet Tween var myTween:Tween = bouton.tween; myTween.func = Elastic.easeOut; // nous définissons les points de départ et d'arrivée myTween.continueTo ( (360/lng) * i, 2 ); } } Nous déclenchons les différents mouvements l?aide la méthode continueTo de l?objet Tween, nous pouvons jouer avec les différentes valeurs et propriétés altérées par le mouvement pour obtenir d?autres effets : Voici le code complet de notre menu dynamique : // import des classes Tween et Elastic pour le type de mouvement import fl.transitions.Tween; import fl.transitions.easing.Elastic; import fl.transitions.easing.Strong; // création du conteneur var conteneur:Sprite = new Sprite(); conteneur.x = 150; conteneur.y = 150; addChild ( conteneur ); // les rubriques var legendes:Array = new Array ( "Accueil", "Nouveautés", "Photos", "Liens", "Contact" ); function creeMenu () { Chapitre 7 ? Interactivité ? version 0.1.2 32 / 60 Thibault Imbert pratiqueactionscript3.bytearray.org var lng:int = legendes.length; var monBouton:Bouton; var angle:int = 360 / lng; for ( var i:int = 0; i< lng; i++ ) { // création des occurrences du symbole Bouton monBouton = new Bouton(); // activation du comportement bouton monBouton.buttonMode = true; // désactivation des objets enfants monBouton.mouseChildren = false; // affectation du contenu monBouton.maLegende.text = legendes[i]; // disposition des instances monBouton.tween = new Tween ( monBouton, "rotation", Elastic.easeOut, 0, i * angle, 3, true ); // un objet Tween est créé pour les effets de survol monBouton.tweenSurvol = new Tween ( monBouton.fondBouton, "scaleX", Elastic.easeOut, 1, 1, 2, true ); conteneur.addChild ( monBouton ); } } creeMenu(); // capture de l'événement click sur le scénario principal conteneur.addEventListener ( MouseEvent.CLICK, clicMenu, true ); conteneur.addEventListener ( MouseEvent.ROLL_OVER, survolBouton, true ); conteneur.addEventListener ( MouseEvent.ROLL_OUT, quitteBouton, true ); function survolBouton ( pEvt:MouseEvent ):void { var monTween:Tween = pEvt.target.tweenSurvol; monTween.continueTo ( 1.1, 2 ); } function quitteBouton ( pEvt:MouseEvent ):void { var monTween:Tween = pEvt.target.tweenSurvol; monTween.continueTo ( 1, 2 ); } Chapitre 7 ? Interactivité ? version 0.1.2 33 / 60 Thibault Imbert pratiqueactionscript3.bytearray.org function clicMenu ( pEvt:MouseEvent ):void { // affiche : [object Bouton] trace( pEvt.target ); // affiche : [object Sprite] trace( pEvt.currentTarget ); } // souscription auprès de l'événement Event.DEACTIVATE auprès du conteneur conteneur.addEventListener ( Event.DEACTIVATE, perteFocus ); function perteFocus ( pEvt:Event ):void { // récupération du nombre de boutons var lng:int = pEvt.target.numChildren; var bouton:Bouton; for (var i:int = 0; i< lng; i++ ) { // nous récupérons chaque bouton du menu bouton = Bouton ( pEvt.target.getChildAt ( i ) ); // nous ciblons chaque objet Tween var myTween:Tween = bouton.tween; myTween.func = Strong.easeOut; // nous définissons les points de départ et d'arrivée myTween.continueTo ( i * 10, 1 ); } if( pEvt.target.hasEventListener( Event.ACTIVATE) == false ) { // souscription auprès de l'événement Event.ACTIVATE auprès du conteneur pEvt.target.addEventListener ( Event.ACTIVATE, gainFocus ); } } function gainFocus ( pEvt:Event ):void { // récupération du nombre de boutons var lng:int = pEvt.target.numChildren; var bouton:Bouton; for (var i:int = 0; i< lng; i++ ) { Chapitre 7 ? Interactivité ? version 0.1.2 34 / 60 Thibault Imbert pratiqueactionscript3.bytearray.org // nous récupérons chaque bouton du menu bouton = Bouton ( pEvt.target.getChildAt ( i ) ); // nous ciblons chaque objet Tween var myTween:Tween = bouton.tween; myTween.func = Elastic.easeOut; // nous définissons les points de départ et d'arrivée myTween.continueTo ( 60 * (i+1), 2 ); } } Notre menu dynamique réagit désormais à la perte ou au gain du focus de l?animation. Nous pourrions varier les différents effets et augmenter les capacités du menu sans limites, c?est ici que réside la puissance de Flash ! A retenir ? Les événements Event.ACTIVATE et Event.DEACTIVATE permettent de gérer la perte ou le gain de focus du lecteur. Pour aller plus loin Nous n?avons pas encore utilisé la fonction clicMenu souscrite auprès de l?événement MouseEvent.CLICK de chaque bouton. En associant un lien à chaque bouton nous ouvrirons une fenêtre navigateur. Il nous faut dans un premier temps stocker chaque lien, pour cela nous allons créer un second tableau spécifique : // les liens var liens:Array = new Array ("http://www.oreilly.com", "http://www.bytearray.org", "http://www.flickr.com", "http://www.linkdup.com", "http://www.myspace.com"); Chaque bouton contient au sein de sa propriété lien un lien associé : function creeMenu () { var lng:int = legendes.length; var monBouton:Bouton; var angle:int = 360 / lng; for ( var i:int = 0; i< lng; i++ ) { // création des occurrences du symbole Bouton monBouton = new Bouton(); // activation du comportement bouton Chapitre 7 ? Interactivité ? version 0.1.2 35 / 60 Thibault Imbert pratiqueactionscript3.bytearray.org monBouton.buttonMode = true; // désactivation des objets enfants monBouton.mouseChildren = false; // affectation du contenu monBouton.maLegende.text = legendes[i]; // chaque bouton stocke son lien associé monBouton.lien = liens[i]; // disposition des instances monBouton.tween = new Tween ( monBouton, "rotation", Elastic.easeOut, 0, i * angle, 3, true ); // un objet Tween est créé pour les effets de survol monBouton.tweenSurvol = new Tween ( monBouton.fondBouton, "scaleX", Elastic.easeOut, 1, 1, 2, true ); conteneur.addChild ( monBouton ); } } Lorsque la fonction clicMenu est déclenchée, nous ouvrons une nouvelle fenêtre navigateur : function clicMenu ( pEvt:MouseEvent ):void { // ouvre une fenêtre navigateur pour le lien cliqué navigateToURL ( new URLRequest ( pEvt.target.lien ) ); } Nous récupérons le lien au sein du bouton cliqué, référencé par la propriété target de l?objet événementiel. La fonction navigateToURL stockée au sein du paquetage flash.net nous permet d?ouvrir une nouvelle fenêtre navigateur et d?atteindre le lien spécifié. En ActionScript 3 tout lien HTTP doit être enveloppé dans un objet URLRequest. Pour plus d?informations concernant les échanges externes, rendez- vous au chapitre 15 intitulé Communication externe. Notons qu?il est possible de créer une propriété lien car la classe MovieClip est dynamique. La création d?une telle propriété au sein d?une instance de SimpleButton est impossible. Notre code pourrait être amélioré en préférant un tableau associatif aux deux tableaux utilisés actuellement. Nous pourrions regrouper nos deux tableaux liens et legendes en un seul tableau : Chapitre 7 ? Interactivité ? version 0.1.2 36 / 60 Thibault Imbert pratiqueactionscript3.bytearray.org // rubriques et liens var donnees:Array = new Array (); // ajout des rubriques et liens associés donnees.push ( { rubrique : "Accueil", lien : "http://www.oreilly.com" } ); donnees.push ( { rubrique : "Nouveautés", lien : "http://www.bytearray.org" } ); donnees.push ( { rubrique : "Photos", lien : "http://www.flickr.com" } ); donnees.push ( { rubrique : "Liens", lien : "http://www.linkdup.com" } ); donnees.push ( { rubrique : "Contact", lien : "http://www.myspace.com" } ); En utilisant un tableau associatif nous organisons mieux nos données et rendons l?accès simplifié. Nous modifions la fonction creeMenu afin de cibler les informations au sein du tableau associatif : function creeMenu () { var lng:int = donnees.length; var monBouton:Bouton; var angle:int = 360 / lng; for ( var i:int = 0; i< lng; i++ ) { // création des occurrences du symbole Bouton monBouton = new Bouton(); // activation du comportement bouton monBouton.buttonMode = true; // désactivation des objets enfants monBouton.mouseChildren = false; // affectation du contenu monBouton.maLegende.text = donnees[i].rubrique; // chaque bouton stocke son lien associé monBouton.lien = donnees[i].lien; // disposition des instances monBouton.tween = new Tween ( monBouton, "rotation", Elastic.easeOut, 0, i * angle, 3, true ); // un objet Tween est créé pour les effets de survol monBouton.tweenSurvol = new Tween ( monBouton.fondBouton, "scaleX", Elastic.easeOut, 1, 1, 2, true ); conteneur.addChild ( monBouton ); } } Notre menu est maintenant terminé ! Intéressons-nous maintenant à l?espace de coordonnées. Chapitre 7 ? Interactivité ? version 0.1.2 37 / 60 Thibault Imbert pratiqueactionscript3.bytearray.org A retenir ? Il est préférable d?utiliser des tableaux associatifs plutôt que des tableaux à index. L?organisation et l?accès aux données seront optimisés et simplifiés. Espace de coordonnées Pour toutes les entrées souris, les objets graphiques diffusent des objets événementiels de type flash.events.MouseEvent. Cette classe possède de nombreuses propriétés dont voici le détail : ? MouseEvent.altKey : censée indiquer si la touche ALT est enfoncée au moment du clic. ? MouseEvent.buttonDown : indique si le bouton principal de la souris est enfoncé au moment du clic. ? MouseEvent.delta : indique le nombre de lignes qui doivent défiler chaque fois que l'utilisateur fait tourner la molette de sa souris d?un cran. ? MouseEvent.localX : indique les coordonnées X de la souris par rapport à l?espace de coordonnées de l?objet cliqué. ? MouseEvent.localY : indique les coordonnées Y de la souris par rapport à l?espace de coordonnées de l?objet cliqué. ? MouseEvent.relatedObject : indique l?objet sur lequel la souris pointe lors de l?événement MouseEvent.MOUSE_OUT. ? MouseEvent.shiftKey : indique si la touche SHIFT est enfoncée au moment du clic. ? MouseEvent.stageX : indique les coordonnées X de la souris par rapport à l?espace de coordonnées de l?objet Stage. ? MouseEvent.stageY : indique les coordonnées Y de la souris par rapport à l?espace de coordonnées de l?objet Stage. En traçant l?objet événementiel, une représentation toString() est effectuée : // souscription auprès de l'événement MouseMove du bouton monBouton.addEventListener ( MouseEvent.MOUSE_MOVE, bougeSouris ); function bougeSouris ( pEvt:MouseEvent ):void { //affiche : [MouseEvent type="mouseMove" bubbles=true cancelable=false eventPhase=2 localX=28 localY=61 stageX=158.95000000000002 stageY=190 relatedObject=null ctrlKey=false altKey=false shiftKey=false delta=0] trace(pEvt); Chapitre 7 ? Interactivité ? version 0.1.2 38 / 60 Thibault Imbert pratiqueactionscript3.bytearray.org } Ces propriétés nous permettent par exemple de savoir à quelle position se trouve la souris par rapport aux coordonnées de l?objet survolé : // souscription auprès de l'événement MouseMove du bouton monBouton.addEventListener ( MouseEvent.MOUSE_MOVE, bougeSouris ); function bougeSouris ( pEvt:MouseEvent ):void { /*affiche : Position X de la souris par rapport au bouton : 14 Position Y de la souris par rapport au bouton : 14 */ trace("Position X de la souris par rapport au bouton : " + pEvt.localX); trace("Position Y de la souris par rapport au bouton : " + pEvt.localY); } La figure 7-14 illustre l?intérêt des propriétés localX et localY : Figure 7-14. Propriétés localX et localY. Afin de savoir où se trouve la souris par rapport au stage et donc au scénario principal nous utilisons toujours les propriétés stageX et stageY de l?objet événementiel de type MouseEvent : // souscription auprès de l'événement MouseMove du bouton monBouton.addEventListener ( MouseEvent.MOUSE_MOVE, bougeSouris ); function bougeSouris ( pEvt:MouseEvent ):void Chapitre 7 ? Interactivité ? version 0.1.2 39 / 60 Thibault Imbert pratiqueactionscript3.bytearray.org { /*affiche : Position X par rapport à la scène : 184 Position Y par rapport à la scène : 204 */ trace("Position X de la souris par rapport à la scène : " + pEvt.stageX); trace("Position Y de la souris par rapport à la scène : " + pEvt.stageY); } La figure 7-15 illustre les propriétés stageX et stageY : Figure 7-15. Propriétés stageX et stageY. Notons que lorsque l?objet Stage est la cible d?un événement souris, les propriétés stageX, stageY et localX et localY de l?objet événementiel renvoient la même valeur. A retenir Chapitre 7 ? Interactivité ? version 0.1.2 40 / 60 Thibault Imbert pratiqueactionscript3.bytearray.org ? Les propriétés localX et localY renvoient les coordonnées de la souris par rapport aux coordonnées locale de l?objet cliqué. ? Les propriétés stageX et stageY renvoient les coordonnées de la souris par rapport au scénario principal. Evénement global En ActionScript 1 et 2 les clips étaient capables d?écouter tous les événements souris, même si ces derniers étaient invisibles ou non cliquables. Pour écouter les déplacements de la souris sur la scène nous pouvions écrire : // définition d'un gestionnaire d'événement monClip.onMouseMove = function () { // affiche : 78 : 211 trace( _xmouse + " : " + _ymouse ); } En définissant une fonction anonyme sur la propriété onMouseMove nous disposions d?un moyen efficace d?écouter les déplacements de la souris sur toute l?animation. En ActionScript 3 les comportements souris ont été modifiés, si nous écoutons des événements souris sur un objet non présent au sein de la liste d?affichage, l?événement n?est pas diffusé, car aucune interaction n?intervient entre la souris et l?objet graphique. A l?inverse, si l?objet est visible l?événement n?est diffusé que lorsque la souris se déplace au-dessus de l?objet. Pour nous en rendre compte, nous posons une occurrence de symbole bouton sur le scénario principal que nous appelons monBouton et nous écoutons l?événement MouseEvent.MOUSE_MOVE : // souscription auprès de l'événement MouseMove du bouton monBouton.addEventListener ( MouseEvent.MOUSE_MOVE, bougeSouris ); function bougeSouris ( pEvt:MouseEvent ):void { trace("Déclenché uniquement lors du déplacement de la souris sur le bouton monBouton"); } Afin de mettre en application la notion d?événements globaux, nous allons développer une application de dessin, nous ajouterons plus tard de nouvelles fonctionnalités à celle-ci comme l?export JPEG et PNG afin de sauvegarder notre dessin. Chapitre 7 ? Interactivité ? version 0.1.2 41 / 60 Thibault Imbert pratiqueactionscript3.bytearray.org Mise en application Pour mettre en application la notion d?événement global nous allons créer ensemble une application de dessin dans laquelle l?utilisateur dessine à l?écran à l?aide de la souris. La première étape pour ce type d?application consiste à utiliser les capacités de dessin offertes par la classe propriété graphics de tout objet de type flash.display.DisplayObject. Notons que l?API de dessin n?est plus directement disponible sur la classe MovieClip mais depuis la propriété graphics de tout DisplayObject. Dans un nouveau document Flash CS3 nous allons tout d?abord nous familiariser avec l?API de dessin. Contrairement à l?API disponible en ActionScript 1 et 2 qui s?avérait limitée, la version ActionScript 3 a été enrichie. Afin de dessiner nous allons créer un DisplayObject le plus simple possible et le plus optimisé, pour cela nous nous orientons vers la classe flash.display.Shape : // création du conteneur de tracés vectoriels var monDessin:Shape = new Shape(); // ajout à la liste d'affichage addChild ( monDessin ); Nous allons reproduire le même mécanisme que dans la réalité en traçant à partir du point cliqué. Le premier événement à écouter est donc l?événement MouseEvent.MOUSE_DOWN, pour détecter le premier clic et déplacer la mine à cette position : // création du conteneur de tracés vectoriels var monDessin:Shape = new Shape(); // ajout à la liste d'affichage addChild ( monDessin ); // souscription auprès de l'événement MouseEvent.MOUSE_DOWN du scénario stage.addEventListener ( MouseEvent.MOUSE_DOWN, clicSouris ); function clicSouris ( pEvt:MouseEvent ):void { // position de la souris var positionX:Number = pEvt.stageX; var positionY:Number = pEvt.stageY; } A chaque clic souris nous récupérons la position de la souris, nous allons à présent déplacer la mine en appelant la méthode moveTo : // création du conteneur de tracés vectoriels Chapitre 7 ? Interactivité ? version 0.1.2 42 / 60 Thibault Imbert pratiqueactionscript3.bytearray.org var monDessin:Shape = new Shape(); // ajout à la liste d'affichage addChild ( monDessin ); // souscription auprès de l'événement MouseEvent.MOUSE_DOWN du scénario stage.addEventListener ( MouseEvent.MOUSE_DOWN, clicSouris ); function clicSouris ( pEvt:MouseEvent ):void { // position de la souris var positionX:Number = pEvt.stageX; var positionY:Number = pEvt.stageY; // la mine est déplacée à cette position // pour commencer à dessiner à partir de cette position monDessin.graphics.moveTo ( positionX, positionY ); } Pour l?instant rien n?est dessiné lorsque nous cliquons car nous ne faisons que déplacer la mine de notre stylo imaginaire mais nous ne lui ordonnons pas de tracer, afin de dessiner nous devons appeler la méthode lineTo en permanence lorsque notre souris est en mouvement. Il nous faut donc écouter un nouvel événement. La première chose à ajouter est l?écoute de l?événement MouseEvent.MOUSE_MOVE qui va nous permettre de déclencher une fonction qui dessinera lorsque la souris sera déplacée : // création du conteneur de tracés vectoriels var monDessin:Shape = new Shape(); // ajout à la liste d'affichage addChild ( monDessin ); // souscription auprès de l'événement MouseEvent.MOUSE_DOWN du scénario stage.addEventListener ( MouseEvent.MOUSE_DOWN, clicSouris ); function clicSouris ( pEvt:MouseEvent ):void { // position de la souris var positionX:Number = pEvt.stageX; var positionY:Number = pEvt.stageY; // la mine est déplacée à cette position // pour commencer à dessiner à partir de cette position monDessin.graphics.moveTo ( positionX, positionY ); // lorsque la souris est cliquée nous commencons l'écoute de celle ci pEvt.currentTarget.addEventListener ( MouseEvent.MOUSE_MOVE, bougeSouris ); } function bougeSouris ( pEvt:MouseEvent ):void Chapitre 7 ? Interactivité ? version 0.1.2 43 / 60 Thibault Imbert pratiqueactionscript3.bytearray.org { var positionX:Number = pEvt.stageX; var positionY:Number = pEvt.stageY; // la mine est déplacée à cette position // pour commencer à dessiner à partir de cette position monDessin.graphics.lineTo ( positionX, positionY ); } Si nous testons le code précédent, aucun tracé n?apparaîtra car nous n?avons pas défini le style du tracé avec la méthode lineStyle de la classe Graphics : // initialisation du style de tracé monDessin.graphics.lineStyle ( 1, 0x990000, 1 ); Afin de ne pas continuer à tracer lorsque l?utilisateur relâche la souris nous devons supprimer l?écoute de l?événement MouseEvent.MOUSE_MOVE lorsque l?événement MouseEvent.MOUSE_UP est diffusé : // souscription auprès de l'événement MouseEvent.MOUSE_UP du scénario stage.addEventListener ( MouseEvent.MOUSE_UP, relacheSouris ); function relacheSouris ( pEvt:MouseEvent ):void { // désinscription auprès de l'évènement MouseEvent.MOUSE_MOVE pEvt.currentTarget.removeEventListener ( MouseEvent.MOUSE_MOVE, bougeSouris ); } Tout fonctionne très bien, mais en regardant attentivement nous voyons que le rendu du tracé n?est pas très fluide. Si nous augmentons la cadence de l?animation à environ 60 img/sec nous remarquons que le rendu est plus fluide. Il existe néanmoins une méthode beaucoup plus optimisée pour améliorer le rafraîchissement du rendu, nous allons intégrer cette fonctionnalité dans notre application. A retenir ? Seul l?objet Stage permet d?écouter la souris de manière globale. Mise à jour du rendu C?est à l?époque du lecteur Flash 5 que la fonction updateAfterEvent a vu le jour. Cette fonction permet de mettre à jour le rendu du lecteur indépendamment de la cadence de l?animation. Dès que la fonction updateAfterEvent est déclenchée Chapitre 7 ? Interactivité ? version 0.1.2 44 / 60 Thibault Imbert pratiqueactionscript3.bytearray.org le lecteur rend à nouveau les vecteurs, en résumé cette fonction permet de mettre à jour l?affichage entre deux images clés. Cette fonction n?a d?intérêt qu?au sein de fonctions n?étant pas liées à la cadence de l?animation. C?est pour cette raison qu?en ActionScript 3, seules les classes MouseEvent, KeyboardEvent et TimerEvent possèdent une méthode updateAfterEvent. Afin d?optimiser le rendu des tracés dans notre application de dessin nous forçons le rafraîchissement du rendu au sein de la fonction bougeSouris : function bougeSouris ( pEvt:MouseEvent ):void { var positionX:Number = pEvt.stageX; var positionY:Number = pEvt.stageY; // la mine est déplacée à cette position // pour commencer à dessiner à partir de cette position monDessin.graphics.lineTo ( positionX, positionY ); // force le rendu pEvt.updateAfterEvent(); } Voici le code final de notre application de dessin : // création du conteneur de tracés vectoriels var monDessin:Shape = new Shape(); // ajout à la liste d'affichage addChild ( monDessin ); // initialisation du style de tracé monDessin.graphics.lineStyle ( 1, 0x990000, 1 ); // souscription auprès de l'événement MouseEvent.MOUSE_DOWN du scénario stage.addEventListener ( MouseEvent.MOUSE_DOWN, clicSouris ); // souscription auprès de l'événement MouseEvent.MOUSE_UP du scénario stage.addEventListener ( MouseEvent.MOUSE_UP, relacheSouris ); function clicSouris ( pEvt:MouseEvent ):void { // position de la souris var positionX:Number = pEvt.stageX; var positionY:Number = pEvt.stageY; // la mine est déplaçée à cette position // pour commencer à dessiner à partir de cette position monDessin.graphics.moveTo ( positionX, positionY ); // lorsque la souris est cliquée nous commencons l'écoute de celle ci Chapitre 7 ? Interactivité ? version 0.1.2 45 / 60 Thibault Imbert pratiqueactionscript3.bytearray.org pEvt.currentTarget.addEventListener ( MouseEvent.MOUSE_MOVE, bougeSouris ); } function bougeSouris ( pEvt:MouseEvent ):void { var positionX:Number = pEvt.stageX; var positionY:Number = pEvt.stageY; // la mine est déplaçée à cette position // pour commencer à dessiner à partir de cette position monDessin.graphics.lineTo ( positionX, positionY ); // force le rendu pEvt.updateAfterEvent(); } function relacheSouris ( pEvt:MouseEvent ):void { // désinscription auprès de l'évènement MouseEvent.MOUSE_MOVE pEvt.currentTarget.removeEventListener ( MouseEvent.MOUSE_MOVE, bougeSouris ); } Sans augmenter la cadence de notre animation, nous améliorons le rendu des tracés, en conservant une cadence réduite notre application consomme peu de ressources sans mettre de côté les performances. Grâce à la propriété frameRate de la classe Stage nous réduisons la cadence de l?animation à l?exécution : stage.frameRate = 2; Avec une cadence réduite à deux image/sec nos tracés restent fluides. Intéressons-nous désormais à une autre notion liée à l?interactivité, la gestion du clavier. A retenir ? Grâce à la méthode updateAfterEvent, le rendu peut être forçé entre deux images. Il en resulte d?un meilleur rafraichissement de l?affichage. Gestion du clavier Grâce au clavier nous pouvons ajouter une autre dimension à nos applications, cette fonctionnalité n?est d?ailleurs aujourd?hui pas assez utilisée dans les sites traditionnels, nous allons capturer certaines Chapitre 7 ? Interactivité ? version 0.1.2 46 / 60 Thibault Imbert pratiqueactionscript3.bytearray.org touches du clavier afin d?intégrer de nouvelles fonctionnalités dans notre application de dessin créée auparavant. Il serait judicieux d?intégrer un raccourci clavier afin d?effacer le dessin en cours. Par défaut, l?objet Stage gère les entrées clavier. Deux événements sont liés à l?écoute du clavier : ? KeyboardEvent.KEY_DOWN : diffusé lorsqu?une touche du clavier est enfoncée. ? KeyboardEvent.KEY_UP : diffusé lorsqu?une touche du clavier est relâchée. Nous allons écouter l?événement KeyboardEvent.KEY_DOWN auprès de l?objet Stage, à chaque fois qu?une touche est enfoncée la fonction écouteur ecouteClavier est déclenchée : // souscription auprès de l'objet Stage pour l'événement KEY_DOWN stage.addEventListener ( KeyboardEvent.KEY_DOWN, ecouteClavier ); function ecouteClavier ( pEvt:KeyboardEvent ):void { // affiche : [KeyboardEvent type="keyDown" bubbles=true cancelable=false eventPhase=2 charCode=114 keyCode=82 keyLocation=0 ctrlKey=false altKey=false shiftKey=false] trace( pEvt ); } Un objet événementiel de type KeyboardEvent est diffusé, cette classe possède de nombreuses propriétés dont voici le détail : ? KeyboardEvent.altKey : indique si la touche ALT est enfoncée, cette propriété n?est pas prise en charge pour le moment. ? KeyboardEvent.charCode : contient le code caractère Unicode correspondant à la touche du clavier. ? KeyboardEvent.ctrlKey : indique si la touche CTRL du clavier est enfoncée. ? KeyboardEvent.keyCode : valeur de code correspondant à la touche enfoncée ou relâchée. ? KeyboardEvent.keyLocation : indique l?emplacement de la touche sur le clavier. ? KeyboardEvent.shiftKey : indique si la touche SHIFT du clavier est enfoncée. Déterminer la touche appuyée La propriété charCode de l?objet événementiel diffusé lors d?un événement clavier nous permet de déterminer la touche enfoncée. Chapitre 7 ? Interactivité ? version 0.1.2 47 / 60 Thibault Imbert pratiqueactionscript3.bytearray.org Grâce à la méthode String.fromCharCode nous évaluons le code Unicode et récupérons le caractère correspondant : // souscription auprès de l'objet stage pour l'événement KEY_DOWN stage.addEventListener ( KeyboardEvent.KEY_DOWN, ecouteClavier ); function ecouteClavier ( pEvt:KeyboardEvent ):void { // affiche : d,f,g, etc... trace( String.fromCharCode( pEvt.charCode ) ); } Pour tester la touche appuyée, nous utilisons les propriétés statiques de la classe Keyboard. Les codes de touche les plus courants sont stockés au sein de propriétés statiques de la classe Keyboard. Pour savoir si la touche espace est enfoncée nous écrivons : // souscription auprès de l'objet stage pour l'événement KEY_DOWN stage.addEventListener ( KeyboardEvent.KEY_DOWN, ecouteClavier ); function ecouteClavier ( pEvt:KeyboardEvent ):void { if ( pEvt.keyCode == Keyboard.SPACE ) { trace("Touche ESPACE enfoncée"); } } Nous allons supprimer le dessin lorsque la touche ESPACE sera enfoncée, afin de pouvoir commencer un nouveau dessin : // souscription auprès de l'objet stage pour l'événement KEY_DOWN stage.addEventListener ( KeyboardEvent.KEY_DOWN, ecouteClavier ); function ecouteClavier ( pEvt:KeyboardEvent ):void { if ( pEvt.keyCode == Keyboard.SPACE ) { // non effaçons tous les précédent tracés monDessin.graphics.clear(); // Attention, nous devons obligatoirement redéfinir un style monDessin.graphics.lineStyle ( 1, 0x990000, 1 ); } } Chapitre 7 ? Interactivité ? version 0.1.2 48 / 60 Thibault Imbert pratiqueactionscript3.bytearray.org Nous pourrions changer la couleur des tracés pour chaque nouveau dessin : // souscription auprès de l'objet stage pour l'événement KEY_DOWN stage.addEventListener ( KeyboardEvent.KEY_DOWN, ecouteClavier ); function ecouteClavier ( pEvt:KeyboardEvent ):void { if ( pEvt.keyCode == Keyboard.SPACE ) { // non effaçons tout les précédent traçés monDessin.graphics.clear(); // Attention, nous devons obligatoirement redéfinir un style monDessin.graphics.lineStyle ( 1, Math.random()*0xFFFFFF, 1 ); } } Nous reviendrons sur la manipulation avancée des couleurs au cours du chapitre 12 intitulé Programmation bitmap. Gestion de touches simultanées En ActionScript 1 et 2 la gestion des touches simultanées avec le clavier n?était pas facile. En ActionScript 3 grâce aux propriétés de la classe MouseEvent nous pouvons très facilement détecter la combinaison de touches. Celle-ci est rendue possible grâce aux propriétés altKey, ctrlKey et shiftKey. Nous allons ajouter la notion d?historique dans notre application de dessin. Lorsque l?utilisateur cliquera sur CTRL+Z nous reviendrons en arrière, pour repartir en avant l?utilisateur cliquera sur CTRL+Y. Pour concevoir cette fonctionnalité nous avons besoin de pouvoir dissocier chaque tracé, pour cela nous allons intégrer chaque tracé dans un objet Shape différent et l?ajouter à un conteneur principal. Pour pouvoir contenir des objets Shape, notre conteneur principal devra être un DisplayObjectContainer, nous modifions donc les premières lignes de notre application pour intégrer un conteneur de type Sprite : // création du conteneur de tracés vectoriels var monDessin:Sprite = new Sprite(); // ajout à la liste d'affichage addChild ( monDessin ); Nous devons stocker au sein de deux tableaux les formes supprimées et les formes affichées : Chapitre 7 ? Interactivité ? version 0.1.2 49 / 60 Thibault Imbert pratiqueactionscript3.bytearray.org // tableau référençant les formes tracées et supprimées var tableauTraces:Array = new Array(); var tableauAncienTraces:Array = new Array(); A chaque fois que la souris sera cliquée, nous devons créer un objet Shape spécifique à chaque tracé, ce dernier est alors référencé au sein du tableau tableauTraces. Nous modifions le corps de la fonction clicSouris : function clicSouris ( pEvt:MouseEvent ):void { // position de la souris var positionX:Number = pEvt.stageX; var positionY:Number = pEvt.stageY; // un nouvel objet Shape est créé pour chaque tracé var monNouveauTrace:Shape = new Shape(); // nous ajoutons le conteneur de tracé au conteneur principal monDessin.addChild ( monNouveauTrace ); // puis nous référençons le tracé au sein du tableau // référençant les tracés affichés tableauTraces.push ( monNouveauTrace ); // nous définisson un style de tracé monNouveauTrace.graphics.lineStyle ( 1, 0x990000, 1 ); // la mine est déplaçée à cette position // pour commencer à dessiner à partir de cette position monNouveauTrace.graphics.moveTo ( positionX, positionY ); // si un nouveau tracé intervient alors que nous sommes // repartis en arrière nous repartons de cet état if ( tableauAncienTraces.length ) tableauAncienTraces = new Array(); // lorsque la souris est cliquée nous commencons l'écoute de celle ci pEvt.currentTarget.addEventListener ( MouseEvent.MOUSE_MOVE, bougeSouris ); } Lorsque la souris est en mouvement nous devons tracer au sein du dernier objet Shape ajouté, grâce à notre tableau tableauTraces nous récupérons le dernier objet Shape et traçons au sein de ce dernier : function bougeSouris ( pEvt:MouseEvent ):void { var positionX:Number = pEvt.stageX; var positionY:Number = pEvt.stageY; // nous récupérons le dernier objet Shape ajouté // prêt à accueillir le nouveau tracé var monNouveauTrace:Shape = tableauTraces[tableauTraces.length-1]; // la mine est déplaçée à cette position Chapitre 7 ? Interactivité ? version 0.1.2 50 / 60 Thibault Imbert pratiqueactionscript3.bytearray.org // pour commencer à dessiner à partir de cette position monNouveauTrace.graphics.lineTo ( positionX, positionY ); // force le rafraichissement du rendu pEvt.updateAfterEvent(); } La dernière fonction que nous devons modifier est la fonction ecouteClavier, c?est au sein de celle-ci que nous testons la combinaison de touches et décidons s?il faut supprimer des tracés ou les réafficher : function ecouteClavier ( pEvt:KeyboardEvent ):void { // si la barre espace est enfoncée if ( pEvt.keyCode == Keyboard.SPACE ) { // nombre d'objets Shape contenant des tracés var lng:int = tableauTraces.length; // suppression des traçés de la liste d'affichage while ( lng-- ) monDessin.removeChild ( tableauTraces[lng] ); // les tableaux d'historiques sont reinitialisés // les références supprimées tableauTraces = new Array(); tableauAncienTraces = new Array(); } // code des touches Z et Y var ZcodeTouche:int = 90; var YcodeTouche:int = 89; if ( pEvt.ctrlKey ) { // si retour en arrière (CTRL+Z) if( pEvt.keyCode == ZcodeTouche && tableauTraces.length ) { // nous supprimons le dernier tracé var aSupprimer:Shape = tableauTraces.pop() // nous stockons chaque tracé supprimé dans le tableau spécifique tableauAncienTraces.push ( aSupprimer ); // nous supprimons le tracé de la liste d'affichage monDessin.removeChild( aSupprimer ); // si retour en avant (CTRL+Y) } else if ( pEvt.keyCode == YcodeTouche && tableauAncienTraces.length ) { Chapitre 7 ? Interactivité ? version 0.1.2 51 / 60 Thibault Imbert pratiqueactionscript3.bytearray.org // nous recupèrons le dernier tracé ajouté var aAfficher:Shape = tableauAncienTraces.pop(); // nous le replaçons dans le tableau de tracés à l'affichage tableauTraces.push ( aAfficher ); // puis nous l'affichons monDessin.addChild ( aAfficher ); } } } Voici le code complet de notre application de dessin avec gestion de l?historique : // création du conteneur de tracés vectoriels var monDessin:Sprite = new Sprite(); // ajout à la liste d'affichage addChild ( monDessin ); // souscription auprès de l'évènement MouseEvent.MOUSE_DOWN du scénario stage.addEventListener ( MouseEvent.MOUSE_DOWN, clicSouris ); // souscription auprès de l'évènement MouseEvent.MOUSE_UP du scénario stage.addEventListener ( MouseEvent.MOUSE_UP, relacheSouris ); // tableaux référençant les formes tracées et supprimées var tableauTraces:Array = new Array(); var tableauAncienTraces:Array = new Array(); function clicSouris ( pEvt:MouseEvent ):void { // position de la souris var positionX:Number = pEvt.stageX; var positionY:Number = pEvt.stageY; // un nouvel objet Shape est créé pour chaque tracé var monNouveauTrace:Shape = new Shape(); // nous ajoutons le conteneur de tracé au conteneur principal monDessin.addChild ( monNouveauTrace ); // puis nous référençons le tracé au sein du tableau // référençant les tracés affichés tableauTraces.push ( monNouveauTrace ); // nous définisson un style de tracé monNouveauTrace.graphics.lineStyle ( 1, 0x990000, 1 ); // la mine est déplaçée à cette position // pour commencer à dessiner à partir de cette position monNouveauTrace.graphics.moveTo ( positionX, positionY ); // si un nouveau tracé intervient alors que nous sommes repartis // en arrière nous repartons de cet état if ( tableauAncienTraces.length ) tableauAncienTraces = new Array; Chapitre 7 ? Interactivité ? version 0.1.2 52 / 60 Thibault Imbert pratiqueactionscript3.bytearray.org // lorsque la souris est cliquée nous commencons l'écoute de celle ci pEvt.currentTarget.addEventListener ( MouseEvent.MOUSE_MOVE, bougeSouris ); } function bougeSouris ( pEvt:MouseEvent ):void { var positionX:Number = pEvt.stageX; var positionY:Number = pEvt.stageY; // nous récupérons le dernier objet Shape ajouté // prêt à accueillir le nouveau tracé var monNouveauTrace:Shape = tableauTraces[tableauTraces.length-1]; // la mine est déplaçée à cette position // pour commencer à dessiner à partir de cette position monNouveauTrace.graphics.lineTo ( positionX, positionY ); // force le rafraichissement du rendu pEvt.updateAfterEvent(); } function relacheSouris ( pEvt:MouseEvent ):void { // désinscription auprès de l'évènement MOUSE_MOVE pEvt.currentTarget.removeEventListener ( MouseEvent.MOUSE_MOVE, bougeSouris ); } // souscription auprès de l'objet stage pour l'évènement KEY_DOWN stage.addEventListener ( KeyboardEvent.KEY_DOWN, ecouteClavier ); function ecouteClavier ( pEvt:KeyboardEvent ):void { // si la barre espace est enfoncée if ( pEvt.keyCode == Keyboard.SPACE ) { // nombre d'objets Shape contenant des tracés var lng:int = tableauTraces.length; // suppression des traçés de la liste d'affichage while ( lng-- ) monDessin.removeChild ( tableauTraces[lng] ); // les tableaux d'historiques sont reinitialisés // les références supprimées tableauTraces = new Array(); tableauAncienTraces = new Array(); } // code des touches Z et Y var ZcodeTouche:int = 90; Chapitre 7 ? Interactivité ? version 0.1.2 53 / 60 Thibault Imbert pratiqueactionscript3.bytearray.org var YcodeTouche:int = 89; if ( pEvt.ctrlKey ) { // si retour en arrière (CTRL+Z) if( pEvt.keyCode == ZcodeTouche && tableauTraces.length ) { // nous supprimons le dernier tracé var aSupprimer:Shape = tableauTraces.pop() // nous stockons chaque tracé supprimé dans le tableau spécifique tableauAncienTraces.push ( aSupprimer ); // nous supprimons le tracé de la liste d'affichage monDessin.removeChild( aSupprimer ); // si retour en avant (CTRL+Y) } else if ( pEvt.keyCode == YcodeTouche && tableauAncienTraces.length ) { // nous recupèrons le dernier tracé ajouté var aAfficher:Shape = tableauAncienTraces.pop(); // nous le replaçons dans le tableau de tracés à l'affichage tableauTraces.push ( aAfficher ); // puis nous l'affichons monDessin.addChild ( aAfficher ); } } } Nous verrons au cours du chapitre 10 intitulé Programmation Bitmap comment capturer des données vectorielles afin de les compresser et les exporter en un format d?image spécifique type PNG ou JPEG. Attention : Lors du test de cette application, veillez à bien cocher l?option Désactiver les raccourcis clavier du menu Contrôle du lecteur Flash. Autrement les touches correspondant aux outils de l?environnement auteur de Flash ne pourront être détectées par le lecteur. Nous pourrions imaginer une application permettant à l?utilisateur de dessiner, puis de sauvegarder sur son ordinateur le dessin au format favori. ActionScript nous réserve encore bien des surprises ! A retenir Chapitre 7 ? Interactivité ? version 0.1.2 54 / 60 Thibault Imbert pratiqueactionscript3.bytearray.org ? La classe MouseEvent permet grâce aux propriétés shiftKey et ctrlKey la pression de touches simultanées. Simuler la méthode Key.isDown ActionScript 3 n?intègre pas de méthode équivalente à la méthode isDown de la classe Key existante en ActionScript 1 et 2. Dans le cas de développements de jeux il peut être important de simuler cet ancien comportement. Nous allons nous y attarder à présent. Afin de développer cette fonctionnalité, nous utilisons un symbole de type clip afin de le déplacer sur la scène à l?aide du clavier. La figure 7-16 illustre le symbole : Figure 7-16. Occurrence de symbole clip. Grâce à un objet de mémorisation, nous pouvons reproduire le même comportement que la méthode isDown à l?aide du code suivant : // écoute des événements clavier stage.addEventListener ( KeyboardEvent.KEY_DOWN, toucheEnfoncee ); stage.addEventListener ( KeyboardEvent.KEY_UP, toucheRelachee ); // objet de mémorisation de l'état des touches var touches:Object = new Object(); function toucheEnfoncee ( pEvt:KeyboardEvent ):void { // marque la touche en cours comme enfoncée touches [ pEvt.keyCode ] = true; } Chapitre 7 ? Interactivité ? version 0.1.2 55 / 60 Thibault Imbert pratiqueactionscript3.bytearray.org function toucheRelachee ( pEvt:KeyboardEvent ):void { // marque la touche en cours comme relachée touches [ pEvt.keyCode ] = false; } monPersonnage.addEventListener ( Event.ENTER_FRAME, deplace ); var etat:Number = personnage_mc.scaleX; var vitesse:Number = 15; function deplace ( pEvt:Event ):void { // si la touche Keyboard.RIGHT est enfoncée if ( touches [ Keyboard.RIGHT ] ) { pEvt.target.x += vitesse; pEvt.target.scaleX = etat; // si la touche Keyboard.LEFT est enfoncée } else if ( touches [ Keyboard.LEFT ] ) { pEvt.target.x -= vitesse; pEvt.target.scaleX = -etat; } } En testant le code précédent, le symbole est manipulé par le clavier. Nous venons de simuler le fonctionnement de la méthode Key.isDown qui n?existe plus en ActionScript 3. A retenir ? La propriété keyCode de l?objet événementiel de type KeyboardEvent renvoie le code de la touche. ? Il n?existe pas d?équivalent à la méthode isDown de la classe Key en ActionScript 3. Il est en revanche facile de simuler un comportement équivalent. Superposition Le lecteur 9 en ActionScript 3 intègre un nouveau comportement concernant la superposition des objets graphiques. En ActionScript 1 et 2 lorsqu?un objet non cliquable était superposé sur un bouton, le bouton recouvert recevait toujours les entrées souris. Chapitre 7 ? Interactivité ? version 0.1.2 56 / 60 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 7-17. Le bouton reste cliquable même recouvert. La figure 7-17 illustre une animation ActionScript 1/2, un clip de forme rectangulaire à opacité réduite recouvre un bouton qui demeure cliquable et affiche le curseur représentant une main. Si nous cliquions sur le bouton, son événement onRelease était déclenché. En ActionScript 3 le lecteur 9 prend l?objet le plus haut de la hiérarchie, les objets étant censés recevoir des entrées souris recouverts par un objet graphique même non cliquable ne reçoivent plus les entrées les souris et n?affichent pas le curseur main. La figure 7-18 illustre le comportement en ActionScript 3 : Chapitre 7 ? Interactivité ? version 0.1.2 57 / 60 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 7-18. Le bouton n?est plus cliquable si recouvert. Si l?objet placé au-dessus découvre une partie du bouton, cette partie devient alors cliquable : Chapitre 7 ? Interactivité ? version 0.1.2 58 / 60 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 7-19. Zone cliquable sur bouton. Afin de rendre un objet superposé par un autre cliquable nous devons passer par la propriété mouseEnabled définie par la classe InteractiveObject. En passant la valeur false à la propriété mouseEnabled de l?objet superposé nous rendant les objets placés en dessous sensibles aux entrées souris. En ayant au préalable nommé le clip superposé monClip, nous rendons cliquable le bouton situé en dessous : // désactive la réaction aux entrées souris monClip.mouseEnabled = false; Nous pouvons en déduire que lorsqu?une occurrence de symbole recouvre un objet cliquable, ce dernier ne peut recevoir d?événement souris. A l?inverse lorsqu?une occurrence de symbole contient un objet cliquable, ce dernier continue de recevoir des événements souris. C?est un comportement que nous avons traité en début de chapitre lors de la construction du menu. Les objets enfants des occurrences de Sprite continuaient de recevoir les entrées souris et rendaient nos boutons partiellement cliquables. Afin de désactiver tous les objets enfants, nous avions utilisé la propriété mouseChildren, qui revient à passer la propriété mouseEnabled de tous les objets enfants d?un DisplayObjectContainer. L?événement Event.RESIZE Lorsque l?animation est redimensionnée un événement Event.RESIZE est diffusé par l?objet Stage. Grace à cet événement nous pouvons gérer le redimensionnement de notre animation. Dans un nouveau document Flash CS3 nous créons un clip contenant le logo de notre site et nommons l?occurrence monLogo. Le code suivant nous permet de conserver notre logo toujours centré quel que soit le redimensionnement effectué sur notre animation : // import des classes de mouvement import fl.transitions.Tween; import fl.transitions.easing.Strong; // alignement de la scène en haut à gauche stage.align = StageAlign.TOP_LEFT; // nous empêchons le contenu d'être étiré stage.scaleMode = StageScaleMode.NO_SCALE; // écoute de l'événement auprès de l'objet Stage stage.addEventListener ( Event.RESIZE, redimensionne ); // calcul de la position en x et y Chapitre 7 ? Interactivité ? version 0.1.2 59 / 60 Thibault Imbert pratiqueactionscript3.bytearray.org var initCentreX:int = (stage.stageWidth - monLogo.width)/2; var initCentreY:int = (stage.stageHeight - monLogo.height)/2; // deux objets Tween sont créés pour chaque axe var tweenX:Tween = new Tween ( monLogo, "x", Strong.easeOut, monLogo.x, initCentreX, 15 ); var tweenY:Tween = new Tween ( monLogo, "y", Strong.easeOut, monLogo.y, initCentreY, 15 ); function redimensionne ( pEvt : Event ):void { // calcul de la position en x et y var centreX:int = (stage.stageWidth - monLogo.width)/2; var centreY:int = (stage.stageHeight - monLogo.height)/2; // nous affectons les valeurs de départ et d'arrivée et relançons le mouvement tweenX.begin = monLogo.x; tweenX.finish = centreX; tweenX.start(); tweenY.begin = monLogo.y; tweenY.finish = centreY; tweenY.start(); } La figure 7-20 illustre le résultat : Figure 7-20. Logo centré automatiquement. Nous pouvons utiliser l?événement Event.RESIZE afin de gérer le repositionnement d?un ensemble d?éléments graphiques au sein d?une application ou d?un site. Chapitre 7 ? Interactivité ? version 0.1.2 60 / 60 Thibault Imbert pratiqueactionscript3.bytearray.org Les précédents chapitres nous ont permis de comprendre certains mécanismes avancés d?ActionScript 3 que nous avons traités de manière procédurale. Nous allons durant les prochains chapitres concevoir nos applications à l?aide d?objets communiquant, ce style de programmation est appelé programmation orientée objet ou plus communément POO. Chapitre 8 ? Programmation orientée objet ? version 0.1.2 1 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org 8 Programmation orientée objet CONCEVOIR AUTREMENT................................................................................ 1 TOUT EST OBJET...................................................................................................... 4 NOTRE PREMIERE CLASSE............................................................................... 9 INTRODUCTION AUX PAQUETAGES ........................................................................ 10 DÉFINITION DE PROPRIÉTÉS .................................................................................. 12 ATTRIBUTS DE PROPRIÉTÉS DE CLASSE ................................................................. 13 ATTRIBUTS DE CLASSE.......................................................................................... 15 LE CONSTRUCTEUR ............................................................................................... 16 UTILISATION DU MOT CLÉ THIS ............................................................................. 21 DÉFINITION DE MÉTHODES.................................................................................... 22 L?ENCAPSULATION ........................................................................................... 25 MISE EN PRATIQUE DE L?ENCAPSULATION ............................................................ 28 LES MÉTHODES D?ACCÈS....................................................................................... 28 CONTRÔLE D?AFFECTATION .................................................................................. 34 MÉTHODES EN LECTURE/ÉCRITURE....................................................................... 39 CAS D?UTILISATION DE L?ATTRIBUT STATIC.......................................... 45 LA CLASSE JOUEURMANAGER ..................................................................... 51 L?HERITAGE ........................................................................................................ 57 SOUS-TYPES ET SUPER-TYPE ................................................................................. 60 SPÉCIALISER UNE CLASSE ..................................................................................... 63 LE TRANSTYPAGE ............................................................................................. 65 SURCHARGE ........................................................................................................ 70 Concevoir autrement La programmation orientée objet doit être considérée comme une manière de penser et de concevoir une application, certains la considère d?ailleurs comme un paradigme de programmation. Cette Chapitre 8 ? Programmation orientée objet ? version 0.1.2 2 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org nouvelle manière de penser n?est pas limitée à ActionScript 3 et peut être appliquée à un grand nombre de langages. Avant de démarrer, nous allons tout d?abord nous familiariser avec le vocabulaire puis nous entamerons notre aventure orientée objet à travers différents exemples concrets. C?est en 1967 que le premier langage orienté objet vu le jour sous le nom de Simula-67. Comme son nom l?indique, Simula permettait de simuler toutes sortes de situations pour les applications de l?époque telles les applications de gestions, de comptabilité ou autres. Nous devons la notion d?objets, de classes, de sous-classes, d?événements ainsi que de ramasse-miettes (garbage collector) à Simula. Alan Key, inventeur du terme programmation orientée objet et ingénieur chez Xerox étendit les concepts apportés par Simula et développa le langage Smalltalk, aujourd?hui considéré comme le réel point de départ de la programmation orientée objet. L?intérêt de la programmation orientée objet réside dans la séparation des tâches, l?organisation et la réutilisation du code. Nous pourrions résumer ce chapitre en une seule phrase essentielle à la base du concept de la programmation orientée objet : A chaque type d?objet une tâche spécifique. Afin de développer un programme ActionScript nous avons le choix entre deux approches. La première, appelée couramment programmation procédurale ou fonctionnelle ne définit aucune séparation claire des tâches. Les différentes fonctionnalités de l?application ne sont pas affectées à un objet spécifique, des fonctions sont déclenchées dans un ordre précis et s?assurent que l?application s?exécute correctement. En résumé, il s?agit de la première approche de programmation que tout développeur chevronné a connue. Au moindre bug ou mise à jour, c?est l?angoisse, aucun moyen d?isoler les comportements et les bogues, bienvenue dans le code spaghetti. La seconde approche, appelée programmation orientée objet définit une séparation claire des tâches. Chaque objet s?occupe d?une tâche spécifique, dans le cas d?une galerie photo un objet peut gérer la connexion au serveur afin de récupérer les données, un autre s?occupe de l?affichage, enfin un dernier objet traite les entrées souris et clavier de l?utilisateur. En connectant ces objets entre eux, nous donnons vie à une application. Nous verrons plus tard que toute la difficulté de la programmation orientée objet, réside dans la communication inter objets. La figure 8-1 illustre un exemple de galerie photo composée de trois objets principaux. Chapitre 8 ? Programmation orientée objet ? version 0.1.2 3 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 8-1. Concept de séparation des tâches. Même si cela nous paraît naturel, la notion de programmation orientée objet reste abstraite sans un cas concret appliqué aux développements de tous les jours. Afin de simplifier tout cela, nous pouvons regarder les objets qui nous entourent. Nous retrouvons à peu près partout le concept de séparation des tâches. Afin d?optimiser le temps de développement et de maintenance, un programme peut être considéré comme un ensemble d?objets communicants pouvant à tout moment être remplacés ou réutilisés. Une simple voiture en est l?exemple parfait. Le moteur, les roues, l?embrayage, les pédales, permettent à notre voiture de fonctionner. Si celle-ci tombe en panne, chaque partie sera testée, au cas où l?une d?entre elles s?avère défectueuse, elle est aussitôt remplacée ou réparée permettant une réparation rapide de la voiture. En programmation, la situation reste la même, si notre application objet est boguée, grâce à la séparation des tâches nous pouvons très facilement isoler les erreurs et corriger l?application ou encore ajouter de nouvelles fonctionnalités en ajoutant de nouveaux objets. Chapitre 8 ? Programmation orientée objet ? version 0.1.2 4 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org Tout est objet Dans le monde qui nous entoure tout peut être représenté sous forme d?objet, une ampoule, une voiture, un arbre, la terre elle-même peut être considérée comme un objet. En programmation, il faut considérer un objet comme une entité ayant un état et permettant d?effectuer des opérations. Pour simplifier nous pouvons dire qu?un objet est capable d?effectuer certaines tâches et possède des caractéristiques spécifiques. Une télévision peut être considérée comme un objet, en possédant une taille, une couleur, ainsi que des fonctionnalités, comme le fait d?être allumée, éteinte ou en veille. Ici encore, la séparation des tâches est utilisée, le tube cathodique s?occupe de créer l?image, l?écran se charge de l?afficher, le capteur s?occupe de gérer les informations envoyées par la télécommande. Cet ensemble d?objets produit un résultat fonctionnel et utilisable. Nous verrons que de la même manière nous pouvons concevoir une application ActionScript 3 en associant chaque objet à une tâche spécifique. Afin de porter notre exemple de télévision en ActionScript 3, nous pourrions définir une classe Television permettant de créer des objets télévisions. Chaque objet télévision serait donc de type Television. Lorsque un développeur parle de la classe Array nous savons de quel type d?objet il s?agit et de ses capacités, comme ajouter, supprimer ou trier des éléments. De la même manière, en parlant de la classe Television nous savons quelles sont les fonctionnalités offertes par ce type. Si nous regardons à l?intérieur d?une télévision, nous y trouvons toutes sortes de composants. Des hauts parleurs, des boutons, ainsi qu?un ensemble de composants électroniques. En programmation, nous pouvons exactement reproduire cette conception, nous verrons que plusieurs objets travaillant ensemble peuvent donner naissance à une application concrète. Chaque composant peut ainsi être réutilisé dans un autre programme, les hauts parleurs d?un modèle de télévision pourront être réutilisés dans une autre. C?est ce que nous appelons la notion de composition, nous reviendrons sur ce sujet au cours du chapitre 10 intitulé Diffusion d?événements personnalisés. Une fois notre objet créé, nous pouvons lui demander d?exécuter certaines actions, lorsque nous achetons une télévision nous la choisissons selon ses capacités, et nous nous attendons au moins à pouvoir l?allumer, l?éteindre, monter le son ou baisser le son. Ces capacités propres à un objet sont définies par son interface, les Chapitre 8 ? Programmation orientée objet ? version 0.1.2 5 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org capacités de celle-ci sont directement liées au type. La figure 8-2 illustre l?exemple d?une télévision. Figure 8-2. Classe Television. Afin d?instancier un objet à partir d?une classe nous utilisons le mot clé new : // création d'une instance de la classe Television au sein de la variable maTV var maTV:Television = new Television(); Le type Television détermine l?interface de notre télévision, c'est-à- dire ce que l?objet est capable de faire. Les fonctions allumer, eteindre, monterVolume, et baisserVolume sont appelées méthodes car elles sont rattachées à un objet et définissent ses capacités. En les appelants, nous exécutons tout un ensemble de mécanismes totalement transparents pour l?utilisateur. Dans l?exemple suivant, nous instancions une télévision, s?il est trop tard nous l?éteignons : // création d'une instance de la classe Television au sein de la variable maTV var maTV:Television = new Television(); // nous allumons la télé maTV.allumer() // si il est trop tard if ( heureActuelle > 12 ) { // nous étéignons la télé maTV.eteindre(); } Comment stockons-nous les informations telles la taille, la couleur, ou bien le numéro de série de la télévision ? Chapitre 8 ? Programmation orientée objet ? version 0.1.2 6 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org Bonne question, ces données représentent les caractéristiques d?un objet et sont stockées au sein de propriétés. Nous pouvons imaginer qu?une télévision ait une taille, un poids, une couleur, et un numéro de série. Figure 8-3. Méthodes et propriétés du type Television. Toutes les instances de télévisions créées à partir de la même classe possèdent ainsi les mêmes fonctionnalités, mais des caractéristiques différentes comme la couleur, la taille ou encore le poids. Pour résumer, nous pouvons dire que les méthodes et propriétés déterminent le type. Pour récupérer la couleur ou le poids d?une télévision, nous ciblons la propriété voulue : // nous récupérons la taille var taille:Number = maTV.taille; // nous récupérons la couleur var couleur:Number = maTV.couleur; Au sein de Flash nous retrouvons partout le concept d?objets, prenons le cas de la classe MovieClip, dans le code suivant nous créons une instance de MovieClip : // instanciation d'un clip var monClip:MovieClip = new MovieClip(); Nous savons d?après le type MovieClip les fonctionnalités disponibles sur une instance de MovieClip : // méthodes de la classe MovieClip monClip.gotoAndPlay(2); monClip.startDrag(); Chapitre 8 ? Programmation orientée objet ? version 0.1.2 7 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org En créant plusieurs instances de MovieClip, nous obtenons des objets ayant les mêmes capacités mais des caractéristiques différentes comme par exemple la taille, la position ou encore la transparence : // instanciation d'un premier clip var monPremierClip:MovieClip = new MovieClip(); // utilisation de l'api de dessin monPremierClip.graphics.lineStyle ( 1 ); monPremierClip.graphics.beginFill ( 0x880099, 1); monPremierClip.graphics.drawCircle ( 30, 30, 30 ); // caractéristiques du premier clip monPremierClip.scaleX = .8; monPremierClip.scaleY = .8; monPremierClip.alpha = .5; monPremierClip.x = 150; monPremierClip.y = 150; // affichage du premier clip addChild ( monPremierClip ); // instanciation d'un second clip var monSecondClip:MovieClip = new MovieClip(); // utilisation de l'api de dessin monSecondClip.graphics.lineStyle ( 1 ); monSecondClip.graphics.beginFill ( 0x997711, 1); monSecondClip.graphics.drawCircle ( 60, 60, 60 ); // caractéristiques du second clip monSecondClip.scaleX = .8; monSecondClip.scaleY = .8; monSecondClip.alpha = 1; monSecondClip.x = 300; monSecondClip.y = 190; // affichage du second clip addChild ( monSecondClip ); Si nous testons le code précédent nous obtenons deux clips de forme circulaire ayant des fonctionnalités communes mais des caractéristiques différentes, comme l?illustre la figure 8-4 : Chapitre 8 ? Programmation orientée objet ? version 0.1.2 8 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 8-4. Deux clips de caractéristiques différentes. Au sein de Flash tout est objet, toutes les classes permettent d?instancier des objets ayant des fonctionnalités spécifiques comme la gestion de l?affichage, le chargement de données externe, etc. Nous allons à présent découvrir comment concevoir nos propres objets en ActionScript 3 afin de les rendre réutilisable et facile d?utilisation. Dans un nouveau fichier ActionScript nous allons créer une classe Joueur qui nous permettra de représenter un joueur dans un jeu. En quelques minutes notre classe sera définie et prête à être utilisée. A retenir Chapitre 8 ? Programmation orientée objet ? version 0.1.2 9 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org ? Tout est objet. ? A chaque objet une tâche spécifique. ? Les méthodes définissent les capacités d?un objet. ? Les propriétés définissent ses caractéristiques. ? Une instance de classe est un objet créé à partir d?une classe. ? Pour instancier un objet à partir d?une classe, nous utilisons le mot clé new. Notre première classe Pour créer un nouveau type d?objet en ActionScript 3 nous devons créer une classe. Il faut considérer celle-ci comme un moule permettant de créer des objets de même type. A partir d?une classe nous pouvons créer autant d?instances de cette classe que nous voulons. Avant de commencer à coder, il est important de noter que les classes ActionScript 3 résident dans des fichiers externes portant l?extension .as. A coté de notre document Flash nous allons définir une classe ActionScript 3 avec un éditeur tel FlashDevelop ou Eclipse. Pour cela nous utilisons le mot clé class : class Joueur { } Nous sauvons cette classe à coté de notre document en cours et nous l?appelons Joueur.as. Sur le scénario principal nous instancions notre joueur avec le mot clé new : var monJoueur:Joueur = new Joueur(); A la compilation nous obtenons les deux erreurs suivantes : 1046: Ce type est introuvable ou n'est pas une constante de compilation : Joueur. 1180: Appel à une méthode qui ne semble pas définie, Joueur. Le compilateur ne semble pas apprécier notre classe, il ne reconnaît pas cette classe que nous venons de définir. Contrairement à ActionScript 2, en ActionScript 3, une classe doit être contenue dans un conteneur de classes appelé paquetage. Chapitre 8 ? Programmation orientée objet ? version 0.1.2 10 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org Introduction aux paquetages Les paquetages (packages en anglais) permettent dans un premier temps d?organiser les classes en indiquant dans quel répertoire est située une classe. Nous pouvons imaginer une application dynamique dans laquelle une classe Connector serait placée dans un répertoire serveur, une autre classe PNGEncoder serait elle placée dans un répertoire encodage. Le compilateur trouvera la classe selon le chemin renseigné par le paquetage, afin de définir un paquetage nous utilisons le mot clé package. Si notre classe Joueur était placée dans un répertoire jeu nous devrions définir notre classe de la manière suivante : package jeu { public class Joueur { } } Il serait alors nécessaire de spécifier son chemin au compilateur en utilisant l?instruction import avant de pouvoir l?utiliser : import jeu.Joueur; Lorsque notre classe n?est pas placée dans un répertoire spécifique mais réside simplement à coté de notre document FLA aucun import n?est nécessaire, le compilateur vérifie automatiquement à côté du document Flash si les classes existent. Dans notre cas, la classe Joueur est placée à coté du document Flash et ne réside dans aucun répertoire, nous devons donc spécifier un paquetage vide : package { class Joueur { } } Dans ce cas, aucun import préalable n?est nécessaire. En ActionScript 2, le mot clé package n?existait pas, pour indiquer le chemin d?accès Chapitre 8 ? Programmation orientée objet ? version 0.1.2 11 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org à une classe nous spécifions le chemin d?accès directement lors de sa déclaration : class jeu.Joueur { } La notion de paquetages an ActionScript 3 ne sert pas uniquement à refléter l?emplacement d?une classe, en ActionScript 2, une seule classe pouvait être définie au sein d?un fichier source. En résumé, une seule et unique classe pour chaque fichier .as. En ActionScript 3, quelques subtilités on été ajoutées, nous pouvons désormais définir autant de classes que nous souhaitons pour chaque fichier .as. Une seule classe résidera à l'intérieur d'une déclaration de paquetage, les autres devront être définies à l?extérieur de celui-ci et ne pourront pas être utilisées par un code extérieur. Ne vous inquiétez pas, cela parait relativement abstrait au départ, nous allons au cours des prochains chapitres apprendre à maîtriser la notion de paquetages et découvrir à nouveau de nouvelles fonctionnalités. Si nous tentons de compiler notre classe à nouveau, les mêmes erreurs s?affichent. Afin de pouvoir instancier notre classe depuis l?extérieur nous devons utiliser l?attribut de classe public. Nous allons revenir sur la notion d?attributs de classe très bientôt : package { public class Joueur { } } Une fois le mot clé public ajouté, la compilation s?effectue sans problème. L?intérêt de notre classe est de pouvoir être instanciée et recevoir différents paramètres lorsque nous utiliserons cette classe dans nos applications. Il y?a de fortes chances qu?un joueur ait un nom, un prénom, ainsi que d?autres propriétés. Pour instancier notre instance de Joueur nous utilisons le mot clé new : // instanciation d'un joueur var monJoueur:Joueur = new Joueur(); Notre objet Joueur est créé pour savoir si un objet correspond à un type spécifique nous utilisons le mot clé is : Chapitre 8 ? Programmation orientée objet ? version 0.1.2 12 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org // instanciation d'un joueur var monJoueur:Joueur = new Joueur (); // est-ce que notre objet est de type Joueur ? // affiche : true trace( monJoueur is Joueur ); Le mot clé instanceof utilisé en ActionScript 1 et 2 n?existe plus et a été remplacé par le mot clé is. En étant de type Joueur notre instance nous garantit qu?elle possède toutes les capacités et propriétés propres au type Joueur. A retenir : ? Pour définir une classe, nous utilisons le mot clé class. ? Les classes ActionScript 3 résident dans des fichiers externes portant l?extension .as. ? Les classes ActionScript 3 doivent résider dans des paquetages ? Le nom de la classe doit être le même que le fichier .as ? Afin d?utiliser une classe nous devons l?importer avec le mot clé import. ? Afin de définir un paquetage, nous utilisons le mot clé package. ? Pour tester le type d?un objet nous utilisons le mot clé is. Définition de propriétés Comme nous l?avons vu précédemment, les propriétés d?un objet décrivent ses caractéristiques. Un être humain peut être considéré comme une instance de la classe Humain où chaque caractéristique propre à l?être humain telle la taille, la couleur de ses yeux, ou son nom seraient stockées au sein de propriétés. Nous allons définir des propriétés au sein de la classe Joueur afin que tous les joueurs créés aient leurs propres caractéristiques. Chaque joueur aura un nom, un prénom, une ville d?origine, un âge, et un identifiant les caractérisant. Nous définissons donc cinq propriétés au sein de la classe Joueur. Pour définir des propriétés au sein d?une classe, nous utilisons la définition de classe, un espace situé juste en dessous de la ligne définissant la classe : package { public class Joueur { // définition des propriétés de la classe var nom:String; Chapitre 8 ? Programmation orientée objet ? version 0.1.2 13 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org var prenom:String; var age:int; var ville:String; } } En lisant les lignes précédentes nous nous rendons compte que la syntaxe nous rappelle la définition de simples variables. Les propriétés sont en réalité des variables comme les autres, mais évoluant dans le contexte d?un objet. En définissant ces propriétés, nous garantissons que chaque instance de la classe Joueur les possède. A tout moment, nous pouvons récupérer ces informations en ciblant les propriétés sur l?instance de classe : // instanciation d'un joueur var monJoueur:Joueur = new Joueur(); // recupération du prénom du joueur var prenom = monJoueur.prenom; A la compilation le code précédent échoue, car contrairement à ActionScript 2, lorsque nous ne spécifions pas d?attributs pour chaque propriété, celles-ci sont considérées comme non visible depuis l?extérieur du paquetage en cours. Pour gérer l?accès aux propriétés d?une classe nous utilisons les attributs de propriétés. Attributs de propriétés de classe Nous définissons comme membres d?une classe, les propriétés ainsi que les méthodes. Afin de gérer l?accès à chaque membre, nous utilisons des attributs de propriétés. Au sein du modèle d?objet ActionScript les méthodes sont aussi définies par des propriétés. Une méthode n?étant qu?une fonction évoluant dans le contexte d?un objet. Cinq attributs de propriétés de classe existent en ActionScript 3 : ? internal : Par défaut, le membre est accessible uniquement depuis le paquetage en cours. ? public : Accessible depuis n?importe quelle partie du code. ? private : Le membre n?est accessible que depuis la même classe. Les sous-classes n?ont pas accès au membre. ? protected : Le membre est accessible depuis la même classe, et les sous-classes. Chapitre 8 ? Programmation orientée objet ? version 0.1.2 14 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org ? static : Le membre est accessible uniquement depuis la classe, non depuis les instances. Nous n?avons pas défini pour le moment d?attributs pour les propriétés de notre classe Joueur, lorsqu?aucun attribut n?est défini, la propriété ou méthode est considérée comme internal et n?est accessible que depuis une classe appartenant au même paquetage. Nous reviendrons sur ces subtilités plus tard, pour le moment nous souhaitons accéder à nos propriétés depuis notre scénario nous les définissons comme public : package { public class Joueur { // définition des propriétés publiques de la classe public var nom:String; public var prenom:String; public var age:int; public var ville:String; } } C?est pour cette raison que nous avons défini notre classe publique afin que celle-ci puisse être instanciée depuis l?extérieur. Une fois nos propriétés rendues publiques nous pouvons y accéder depuis l?extérieur, nous instancions un objet Joueur puis nous accédons à sa propriété prenom : // instanciation d'un joueur var monJoueur:Joueur = new Joueur(); // recupération du prénom du joueur var prenom = monJoueur.prenom; // affiche : null trace( prenom ); En ciblant la propriété prenom d?un joueur nous récupérons la valeur null. Ce comportement est tout à fait normal, car pour l?instant aucunes données ne sont contenues par les propriétés. Nous les avons simplement définies. En ActionScript 2 le code précédent aurait retourné undefined au lieu de null. Nous venons de voir qu?à l?aide d?attributs de propriétés de classe nous pouvons gérer l?accès et le comportement des membres d?une Chapitre 8 ? Programmation orientée objet ? version 0.1.2 15 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org classe. Il existe en ActionScript 3 d?autres attributs liés cette fois aux classes, ces derniers sont appelés attributs de classe. Attributs de classe Certaines règles peuvent être appliquées aux classes directement à l?aide de quatre attributs, notons que l?attribut abstract n?existe pas en ActionScript 3 : ? dynamic : La classe accepte l?ajout de propriétés ou méthodes à l?exécution. ? final : La classe ne peut pas être étendue. ? internal (par défaut) : La classe n?est accessible que depuis les classes appartenant au même paquetage. ? public : La classe est accessible depuis n?importe quel emplacement. Afin de rendre la classe Joueur instanciable depuis l?extérieur nous avons du la rendre publique à l?aide de l?attribut public. Celle-ci est d?ailleurs considérée comme non dynamique, cela signifie qu?il est impossible d?ajouter à l?exécution de nouvelles méthodes ou propriétés à une occurrence de la classe ou à la classe même. Imaginons que nous souhaitions ajouter une nouvelle propriété appelée sSexe à l?exécution : // création d'un joueur et d'un administrateur var premierJoueur:Joueur = new Joueur (); // ajout d'une nouvelle propriété à l'exécution premierJoueur.sSexe = "H"; Le compilateur se plaint et génère l?erreur suivante : 1119: Accès à la propriété sSexe peut-être non définie, via la référence de type static Joueur. Si nous rendons la classe dynamique à l?aide de l?attribut dynamic, l?ajout de membres à l?exécution est possible : package { dynamic public class Joueur { // définition des propriétés publiques de la classe public var nom:String; public var prenom:String; public var age:int; public var ville:String; } Chapitre 8 ? Programmation orientée objet ? version 0.1.2 16 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org } Dans le code suivant nous créons une nouvelle propriété sSexe au sein l?instance de classe Joueur et récupérons sa valeur : // création d'un joueur et d'un administrateur var premierJoueur:Joueur = new Joueur (); // ajout d'une nouvelle propriété à l'exécution premierJoueur.sSexe = "H"; // récupération de la valeur // affiche : H trace( premierJoueur.sSexe ); Bien que cela soit possible, il est fortement déconseillé d?ajouter de nouveaux membres à l?exécution à une classe ou instance de classe, la lecture de la classe ne reflètera pas les réelles capacités ou caractéristiques de la classe. En lisant la définition de la classe Joueur le développeur pensera trouver cinq propriétés, en réalité une sixième est rajoutée à l?exécution. Ce dernier serait obligé de parcourir tout le code de l?application afin de dénicher toutes les éventuelles modifications apportées à la classe à l?exécution. Afin d?initialiser notre objet Joueur lors de sa création nous devons lui passer des paramètres. Même si notre objet Joueur est bien créé, son intérêt pour le moment reste limité car nous ne passons aucun paramètre lors son instanciation. Pour passer des paramètres à un objet lors de son instanciation nous définissons une méthode au sein de la classe appelée méthode constructeur. Le constructeur Le but du constructeur est d?initialiser l?objet créé. Celui-ci sera appelé automatiquement dès que la classe sera instanciée. Attention, le constructeur doit impérativement avoir le même nom que la classe. Si ce n?est pas le cas, il sera considéré comme une méthode classique et ne sera pas déclenché. Nous allons prévoir quatre paramètres d?initialisation pour chaque joueur : ? Nom : Une chaîne de caractères représentant son nom. ? Prénom : Une chaîne de caractères représentant son prénom. ? Age : Un entier représentant son âge. ? Ville : Une chaîne de caractères représentant sa location. Chapitre 8 ? Programmation orientée objet ? version 0.1.2 17 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org Au sein du constructeur nous définissons quatre paramètres, qui serviront à accueillir les futurs paramètres passés : package { public class Joueur { // définition des propriétés de la classe public var nom:String; public var prenom:String; public var age:int; public var ville:String; // fonction constructeur function Joueur ( pPrenom:String, pNom:String, pAge:int, pVille:String ) { trace( this ); } } } Si nous tentons d?instancier notre joueur sans passer les paramètres nécessaires : // instanciation d'un joueur var monJoueur:Joueur = new Joueur(); Le compilateur nous renvoie une erreur : 1136: Nombre d'arguments incorrect. 4 attendus. Le compilateur ActionScript 2 ne nous renvoyait aucune erreur lorsque nous tentions d?instancier une classe sans paramètres alors que celle-ci en nécessitait. ActionScript 3 est plus strict et ne permet pas l?instanciation de classes sans paramètres si cela n?est pas spécifié à l?aide du mot clé rest. Nous verrons plus tard comment définir une classe pouvant recevoir ou non des paramètres à l?initialisation. Pour obtenir un joueur, nous créons une instance de la classe Joueur en passant les paramètres nécessaires : // instanciation d'un joueur var monJoueur:Joueur = new Joueur("Stevie", "Wonder", 57, "Michigan"); // recupération du prénom du joueur var prenom = monJoueur.prenom; // affiche : null trace( prenom ); Chapitre 8 ? Programmation orientée objet ? version 0.1.2 18 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org Si nous tentons d?instancier un joueur en passant plus de paramètres que prévus : // création d'un joueur connu :) var monJoueur:Joueur = new Joueur ("Stevie", "Wonder", 57, "Michigan", 50); L?erreur de compilation suivante est générée : 1137: Nombre d'arguments incorrect. Nombre maximum attendu : 4. Le compilateur ActionScript 2 était moins strict et permettait de passer un nombre de paramètres en plus de ceux présents au sein de la signature d?une méthode, en ActionScript 3 cela est impossible. Nous avons instancié notre premier joueur, mais lorsque nous tentons de récupérer son prénom, la propriété prenom nous renvoie à nouveau null. Nous avons bien passé les paramètres au constructeur, et pourtant nous récupérons toujours null lorsque nous ciblons la propriété prenom. Est-ce bien normal ? C?est tout à fait normal, il ne suffit pas de simplement définir un constructeur pour que l?initialisation se fasse comme par magie. C?est à nous de gérer cela, et d?intégrer au sein du constructeur une affectation des paramètres aux propriétés correspondantes. Une fois les paramètres passés, nous devons les stocker au sein de l?objet afin de les mémoriser. C?est le travail du constructeur, ce dernier doit recevoir les paramètres et les affecter à des propriétés correspondantes définies au préalable. Pour cela nous modifions le constructeur de manière à affecter chaque paramètre passé aux propriétés de l?objet : package { public class Joueur { // définition des propriétés de la classe public var nom:String; public var prenom:String; public var age:int; public var ville:String; // fonction constructeur function Joueur ( pPrenom:String, pNom:String, pAge:int, pVille:String ) { // affectation de chaque propriété prenom = pPrenom; nom = pNom; age = pAge; ville = pVille; Chapitre 8 ? Programmation orientée objet ? version 0.1.2 19 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org } } } Une fois les propriétés affectées, nous pouvons récupérer les valeurs associées : // instanciation d'un joueur var monJoueur:Joueur = new Joueur("Stevie", "Wonder", 57, "Michigan"); // récupération du prénom du joueur var prenom:String = monJoueur.prenom; var nom:String = monJoueur.nom; var age:int = monJoueur.age; var ville:String = monJoueur.ville; // affiche : Stevie trace( prenom ); // affiche : Wonder trace( nom ); // affiche : 57 trace( age ); // affiche : Michigan trace( ville ); Lorsqu?un objet Joueur est instancié nous passons des paramètres d?initialisation, chaque caractéristique est stockée au sein de propriétés. A l?inverse si nous rendons ces propriétés privées : package { public class Joueur { // définition des propriétés de la classe private var nom:String; private var prenom:String; private var age:int; private var ville:String; // fonction constructeur function Joueur ( pPrenom:String, pNom:String, pAge:int, pVille:String ) { prenom = pPrenom; nom = pNom; age = pAge; ville = pVille; } } } Chapitre 8 ? Programmation orientée objet ? version 0.1.2 20 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org En définissant l?attribut private, les propriétés deviennent inaccessibles depuis l?extérieur de la classe : // création d'un joueur connu :) var monJoueur:Joueur = new Joueur ("Stevie", "Wonder", 57, "Michigan" ); // affiche une erreur à la compilation : // 1178: Tentative d'accès à la propriété inaccessible nom, via la référence de type static Joueur. trace( monJoueur.nom ); Lorsque le compilateur est en mode strict, une erreur à la compilation est levée, il est impossible de compiler. Si nous passons le compilateur en mode non strict, à l?aide de la syntaxe crochet il devient possible de compiler : // création d'un joueur connu :) var monJoueur:Joueur = new Joueur ("Stevie", "Wonder", 57, "Michigan"); // provoque une erreur à l'exécution : trace( monJoueur['nom'] ); Mais la machine virtuelle 2 nous rattrape en levant une erreur à l?exécution : ReferenceError: Error #1069: La propriété nom est introuvable sur Joueur et il n'existe pas de valeur par défaut. Souvenons-nous que la machine virtuelle d?ActionScript 3 (VM2) conserve les types à l?exécution. L?astuce ActionScript 2 consistant à utiliser la syntaxe crochet pour éviter les erreurs de compilation n?est plus valable. Nous verrons tout au long des prochains chapitres, différents cas d?utilisation d?autres attributs de propriétés. Mais quel serait l?intérêt de définir des propriétés privées ? Quel est l?intérêt si nous ne pouvons pas y accéder ? Nous allons découvrir très vite d?autres moyens plus sécurisés d?accéder aux propriétés d?un objet. Nous allons traiter cela plus en détail dans quelques instants. Nous venons de découvrir comment définir des propriétés au sein d?une classe ActionScript 3, abordons maintenant la définition de méthodes. A retenir : Chapitre 8 ? Programmation orientée objet ? version 0.1.2 21 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org ? Le constructeur d?une classe sert à recevoir des paramètres afin d?initialiser l?objet. ? La présence de constructeur n?est pas obligatoire, si omis, le compilateur en définit un par défaut. Dans ce cas, aucun paramètre ne peut être passé lors de l?instanciation de la classe. Utilisation du mot clé this Afin de faire référence à l?objet courant nous pouvons utiliser le mot clé this au sein d?une classe. Nous aurions pu définir la classe Joueur de la manière suivante : package { public class Joueur { // définition des propriétés de la classe public var nom:String; public var prenom:String; public var age:int; public var ville:String; // fonction constructeur function Joueur ( pPrenom:String, pNom:String, pAge:int, pVille:String ) { // affectation de chaque propriété this.prenom = pPrenom; this.nom = pNom; this.age = pAge; this.ville = pVille; } } } Bien que son utilisation soit intéressante dans certaines situations comme pour passer une référence, son utilisation peut s?avérer redondante et rendre le code verbeux. Certains développeurs apprécient tout de même son utilisation afin de différencier facilement les propriétés d?occurrences des variables locales. En voyant une variable précédée du mot clé this, nous sommes assurés qu?il s?agit d?une propriété membre de la classe. En résumé, il s?agit d?un choix propre à chaque développeur, il n?existe pas de véritable règle d?utilisation. Chapitre 8 ? Programmation orientée objet ? version 0.1.2 22 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org Définition de méthodes Alors que les propriétés d?un objet définissent ses caractéristiques, les méthodes d?une classe définissent ses capacités. Nous parlons généralement de fonctions membres. Dans notre précédent exemple de télévision, les méthodes existantes étaient allumer, eteindre, changerChaine, baisserVolume, et monterVolume. En appliquant le même concept à notre classe Joueur il parait logique qu?un joueur puisse se présenter, nous allons donc définir une méthode sePresenter. Une méthode est un membre régit aussi par les attributs de propriétés. Nous allons rendre cette méthode publique car elle devra être appelée depuis l?extérieure : package { public class Joueur { // définition des propriétés de la classe public var nom:String; public var prenom:String; public var age:int; public var ville:String; // fonction constructeur function Joueur ( pPrenom:String, pNom:String, pAge:int, pVille:String ) { prenom = pPrenom; nom = pNom; age = pAge; ville = pVille; } // méthode permettant au joueur de se présenter public function sePresenter ( ):void { trace("Je m'appelle " + prenom + ", j'ai " + age + " ans." ); } } } Pour qu?un joueur se présente nous appelons la méthode sePresenter : // création d'un joueur connu :) var monJoueur:Joueur = new Joueur ("Stevie", "Wonder", 57, "Michigan"); Chapitre 8 ? Programmation orientée objet ? version 0.1.2 23 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org // Je m'appelle Stevie, j'ai 57 ans. monJoueur.sePresenter(); Ainsi, chaque instance de Joueur a la capacité de se présenter. La méthode sePresenter récupère les propriétés prenom et age et affiche leurs valeurs. Toutes les propriétés définies au sein de la classe sont accessibles par toutes les méthodes de celle-ci. Nous allons définir une deuxième méthode nous permettant d?afficher une représentation automatique de l?objet Joueur. Par défaut lorsque nous traçons un objet, le lecteur Flash représente l?objet sous une forme simplifiée, indiquant simplement le type de l?objet : // création d'un joueur connu :) var monJoueur:Joueur = new Joueur ("Stevie", "Wonder", 57, "Michigan" ); // affiche : [object Joueur] trace( monJoueur ); Notons qu?en ActionScript 2, le lecteur affichait simplement [objet Object] il fallait ensuite tester à l?aide de l?instruction instanceof afin de déterminer le type d?un objet. En ActionScript 3 l?affichage du type est automatique. En définissant une méthode toString au sein de la classe Joueur nous redéfinissons la description que le lecteur fait de l?objet : package { public class Joueur { // définition des propriétés de la classe public var nom:String; public var prenom:String; public var age:int; public var ville:String; // fonction constructeur function Joueur ( pPrenom:String, pNom:String, pAge:int, pVille:String ) { prenom = pPrenom; nom = pNom; age = pAge; ville = pVille; } // méthode permettant au joueur de se présenter public function sePresenter ( ):void { Chapitre 8 ? Programmation orientée objet ? version 0.1.2 24 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org trace("Je m'appelle " + prenom + ", j'ai " + age + " ans." ); } // affiche une description de l'objet Joueur public function toString ( ):String { return "[Joueur prenom : " + prenom +", nom : " + nom + ", age : " + age + ", ville : " + ville + "]"; } } } La méthode toString retourne une chaine de caractères utilisée par le lecteur Flash lorsque celui-ci affiche la description d?un objet. Une fois définie, lorsque nous traçons une instance de la classe Joueur, nous obtenons une représentation détaillée de l?objet. Cela rend notre code plus élégant, lorsqu?un développeur tiers cible une instance de la classe Joueur, la description suivante est faite : // création d'un joueur connu :) var monJoueur:Joueur = new Joueur ("Stevie", "Wonder", 57, "Michigan" ); // affiche : [Joueur prenom : Stevie, nom : Wonder, age : 57, ville : Michigan] trace( monJoueur ); Dans la quasi-totalité des classes que nous créeront, nous intégrerons une méthode toString afin d?assurer une description élégante de nos objets. Pour le moment nous accédons directement depuis l?extérieur aux propriétés de l?objet Joueur car nous les avons définies comme publique. Afin de rendre notre conception plus solide nous allons aborder à présent un nouveau point essentiel de la programmation orientée objet appelé encapsulation. A retenir : Chapitre 8 ? Programmation orientée objet ? version 0.1.2 25 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org ? Les méthodes de la classe ont accès aux propriétés définies au sein de la classe. ? En définissant une méthode toString sur nos classes nous modifions la représentation de notre objet faite par le lecteur Flash. L?encapsulation Lorsque nous utilisons une télévision nous utilisons des fonctionnalités sans savoir ce qui se passe en interne, et tant mieux. L?objet nous est livré mettant à disposition des fonctionnalités, nous ne savons rien de ce qui se passe en interne. Le terme d?encapsulation détermine la manière dont nous exposons les propriétés d?un objet envers le monde extérieur. Un objet bien pensé doit pouvoir être utilisé sans montrer les détails de son implémentation. En d?autres termes, un développeur tiers utilisant notre classe Joueur n?a pas à savoir que nous utilisons une propriété nommée age ou prenom ou autres. Il faut tout d?abord séparer les personnes utilisant les classes en deux catégories : ? Le ou les auteurs de la classe. ? Les personnes l?utilisant. Le code interne à une classe, est appelé implémentation. En tant qu?auteur de classes, nous devons impérativement nous assurer de ne pas montrer les détails de l?implémentation de notre classe. Pourquoi ? Afin d?éviter que la modification d?un détail de l?implémentation n?entraine de lourdes répercussions pour les développeurs utilisant nos classes. Prenons un scénario classique, Mathieu, Nicolas et Eric sont développeurs au sein d?une agence Web et développent différentes classes ActionScript 3 qu?ils réutilisent au cours des différents projets qu?ils doivent réaliser. Nicolas vient de développer une classe permettant de générer des PDF en ActionScript 3. Toute l?équipe de développement Flash utilise cette classe dans les nouveaux projets et il faut bien avouer que tout le monde est plutôt satisfait de cette nouvelle librairie. La figure 8-4 illustre la classe que Nicolas a développée, ainsi que les différentes fonctionnalités disponibles : Chapitre 8 ? Programmation orientée objet ? version 0.1.2 26 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 8-4. Schéma UML simplifié de la classe PDF. La propriété nbPages permet de savoir combien de pages comporte le PDF en cours de création. A chaque page créée, Nicolas incrémente la valeur de cette propriété. Eric vient justement de livrer un projet à un client qui souhaitait générer des PDF depuis Flash. Eric a utilisé la propriété nbPages afin de savoir combien de pages sont présentes dans le PDF en cours de création, voici une partie du code d?Eric : // récupération du nombre de pages var nombresPages:int = monPDF.nbPages; // affichage du nombre de pages legende_pages.text = String ( nombresPages ); Un matin, Nicolas, auteur de la classe se rend compte que la classe possède quelques faiblesses et décide de rajouter des fonctionnalités. Sa gestion des pages internes au document PDF ne lui parait pas optimisée, désormais Nicolas stocke les pages dans un tableau accessible par la propriété tableauPages contenant chaque page du PDF. Voici la nouvelle version de la classe PDF : Figure 8-5. Nouvelle version de la classe PDF. Chapitre 8 ? Programmation orientée objet ? version 0.1.2 27 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org Désormais Nicolas utilise un tableau pour stocker chaque page créée. Pour récupérer le nombre de pages, il faut cibler le tableau tableauPages et récupérer sa longueur pour connaitre le nombre de pages du PDF. Eric et Mathieu récupèrent la nouvelle version mais tous leurs projets ne compilent plus, car la propriété nbPages n?existe plus ! Eric et Mathieu auront beau tenter de convaincre Nicolas de remettre la propriété afin que tout fonctionne à nouveau, Nicolas, très content de sa nouvelle gestion interne des pages refuse de rajouter cette propriété nbPages qui n?aurait plus de sens désormais. Qui est le fautif dans ce scénario ? Nicolas est fautif d?avoir exposé à l?extérieur cette propriété nbPages. En modifiant l?implémentation de sa classe, chaque développeur utilisant la classe risque de voir son code obsolète. Nicolas aurait du définir une méthode d?accès getNbPages qui peut importe l?implémentation aurait toujours retourné le nombre de pages. Nicolas se rendant compte de sa maladresse, décide de sortir une nouvelle version prenant en compte l?encapsulation. La figure 8-6 illustre la nouvelle classe. Figure 8-6. Nouvelle version encapsulée de la classe PDF. Eric et Mathieu en appelant la méthode getNbPages ne savent pas ce qui se passe en interne et tant mieux ! Nicolas est tranquille et pourra modifier tout ce qu?il souhaite en interne, son seul soucis sera de toujours retourner le nombre de pages quelque soit l?implémentation. Ainsi nous pouvons modifier l?implémentation interne de l?objet sans modifier l?interface de programmation. Chapitre 8 ? Programmation orientée objet ? version 0.1.2 28 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org Nous allons mettre en pratique la notion d?encapsulation et découvrir d?autres avantages liés à ce nouveau concept. Mise en pratique de l?encapsulation Nous venons de voir qu?il est très important de cacher l?implémentation afin de rendre notre conception plus solide. Les parties cachées du code ne sont pas utilisées par les développeurs, ainsi nous sommes libres de le modifier sans entraîner de répercussions négatives. L?encapsulation des propriétés permet ainsi de rendre notre code plus intuitif, nous rendons visible uniquement ce qui est utile et utilisable. Dans le cas de notre classe Joueur nous allons conserver la plupart des propriétés accessibles mais sous une forme différente. Pour accéder à l?âge ou au nom d?un joueur nous accédons directement à des propriétés publiques : // instanciation d'un joueur var monJoueur:Joueur = new Joueur("Stevie", "Wonder", 57, "Michigan"); // affiche : Stevie trace( monJoueur.prenom ); // affiche : 57 trace( monJoueur.age ); Si un développeur tiers vient à utiliser notre classe Joueur, ce dernier pourra utiliser ces deux propriétés. Si nous changeons plus tard l?implémentation de notre classe, ces propriétés pourraient peut être disparaître, ou bien être renommées rendant le code du développeur caduc. Afin d?éviter tout risque, nous allons définir des méthodes d?accès qui tâcheront de renvoyer la valeur escomptée, quelque soit l?implémentation. Les méthodes d?accès Deux groupes de méthodes d?accès existent : ? Les méthodes récupérant la valeur d?une propriété, appelées méthodes de récupération. ? Les méthodes affectant une valeur à une propriété, appelées méthodes d?affectation. Prenons un exemple simple, au lieu de cibler directement la propriété age, le développeur tiers appellera une méthode getAge. Nous rendons dans un premier temps toutes nos propriétés privées afin que celles-ci soit cachées : package Chapitre 8 ? Programmation orientée objet ? version 0.1.2 29 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org { public class Joueur { // définition des propriétés de la classe private var nom:String; private var prenom:String; private var age:int; private var ville:String; // fonction constructeur function Joueur ( pPrenom:String, pNom:String, pAge:int, pVille:String ) { prenom = pPrenom; nom = pNom; age = pAge; ville = pVille; } // méthode permettant au joueur de se présenter public function sePresenter ( ):void { trace("Je m'appelle " + prenom + ", j'ai " + age + " ans." ); } // affiche une description de l'objet Joueur public function toString ( ):String { return "[Joueur prenom : " + prenom +", nom : " + nom + ", age : " + age + ", ville : " + ville + "]"; } } } En programmation orientée objet, il est fortement conseillé de protéger les propriétés en les rendant privées. Puis nous définissons une méthode getAge afin de récupérer l?âge du joueur : package { public class Joueur { // définition des propriétés de la classe private var nom:String; private var prenom:String; Chapitre 8 ? Programmation orientée objet ? version 0.1.2 30 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org private var age:int; private var ville:String; // fonction constructeur function Joueur ( pPrenom:String, pNom:String, pAge:int, pVille:String ) { prenom = pPrenom; nom = pNom; age = pAge; ville = pVille; } // récupère l'age du joueur public function getAge ( ):int { return age; } // méthode permettanrt au joueur de se presenter public function sePresenter ( ):void { trace("Je m'appelle " + prenom + ", j'ai " + age + " ans." ); } // affiche une description de l'objet Joueur public function toString ( ):String { return "[Joueur prenom : " + prenom +", nom : " + nom + ", age : " + age + ", ville : " + ville + "]"; } } } Si nous tentons d?accéder aux propriétés privées, le compilateur empêche de compiler : // instanciation d'un joueur var monJoueur:Joueur = new Joueur("Stevie", "Wonder", 57, "Michigan"); // affiche : 1178: Tentative d'accès à la propriété inaccessible prenom, via la référence de type static Joueur. trace( monJoueur.prenom ); // affiche : 1178: Tentative d'accès à la propriété inaccessible age, via la référence de type static Joueur. trace( monJoueur.age ); Chapitre 8 ? Programmation orientée objet ? version 0.1.2 31 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org Désormais, afin de récupérer l?âge d?un joueur nous appelons la méthode getAge : // instanciation d'un joueur var monJoueur:Joueur = new Joueur("Stevie", "Wonder", 57, "Michigan"); // affiche : 57 trace( monJoueur.getAge() ); En utilisant des méthodes d?accès nous cachons l?implémentation de la classe et rendons les modifications futures sécurisées et plus faciles. Les développeurs ne savent pas que la propriété age est utilisée en interne. Si nous décidons de changer l?implémentation pour des raisons d?optimisations ou de conception, aucun risque pour les développeurs utilisant la classe. Dans l?exemple suivant les propriétés du joueur sont désormais stockées au sein d?un objet : package { public class Joueur { // définition des propriétés de la classe private var infosJoueur:Object; // fonction constructeur function Joueur ( pPrenom:String, pNom:String, pAge:int, pVille:String ) { // les paramètres sont désormais stockés // dans un objet infosJoueur infosJoueur = new Object(); // chaque paramètre est stocké dans l'objet infoJoueurs infosJoueur.prenom = pPrenom; infosJoueur.nom = pNom; infosJoueur.age = pAge; infosJoueur.ville = pVille; } // récupère l'age du joueur public function getAge ( ):int { //récupère la propriété age au sein de l?objet infosJoueur return infosJoueur.age; } // méthode permettanrt au joueur de se presenter public function sePresenter ( ):void Chapitre 8 ? Programmation orientée objet ? version 0.1.2 32 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org { trace("Je m'appelle " + infosJoueur.prenom + ", j'ai " + infosJoueur.age + " ans." ); } // affiche une description de l'objet Joueur public function toString ( ):String { return "[Joueur prenom : " + infosJoueur.prenom +", nom : " + infosJoueur.nom + ", age : " + infosJoueur.age + ", ville : " + infosJoueur.ville + "]"; } } } L?accès aux propriétés ne change pas, notre code continue de fonctionner : // instanciation d'un joueur var monJoueur:Joueur = new Joueur("Stevie", "Wonder", 57, "Michigan"); // affiche : 57 trace( monJoueur.getAge() ); Ainsi, notre implémentation change sans intervenir sur l?interface de programmation. Il est important de signaler qu?une méthode de récupération peut porter n?importe quel nom, l?utilisation du mot get au sein de la méthode getAge n?est pas obligatoire. Nous aurions pu appeler cette même méthode recupAge. Nous définissons une méthode de récupération spécifique à chaque propriété : package { public class Joueur { // définition des propriétés de la classe private var nom:String; private var prenom:String; private var age:int; private var ville:String; // fonction constructeur function Joueur ( pPrenom:String, pNom:String, pAge:int, pVille:String ) Chapitre 8 ? Programmation orientée objet ? version 0.1.2 33 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org { prenom = pPrenom; nom = pNom; age = pAge; ville = pVille; } // récupère le prénom du joueur public function getPrenom ( ):String { return prenom; } // récupère le nom du joueur public function getNom ( ):String { return nom; } // récupère l'age du joueur public function getAge ( ):int { return age; } // récupère la ville du joueur public function getVille ( ):String { return ville; } // méthode permettant au joueur de se presenter public function sePresenter ( ):void { trace("Je m'appelle " + prenom + ", j'ai " + age + " ans." ); } // affiche une description de l'objet Joueur public function toString ( ):String { return "[Joueur prenom : " + prenom +", nom : " + nom + ", age : " + age + ", ville : " + ville + "]"; Chapitre 8 ? Programmation orientée objet ? version 0.1.2 34 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org } } } De cette manière, nous pouvons accéder à toutes les propriétés d?une instance de Joueur : // création d'un joueur connu :) var monJoueur:Joueur = new Joueur ("Stevie", "Wonder", 57, "Michigan" ); // affiche : Stevie trace( monJoueur.getPrenom() ); // affiche : Wonder trace( monJoueur.getNom() ); // affiche : 57 trace( monJoueur.getAge() ); // affiche : Michigan trace( monJoueur.getVille() ); Grace aux différentes méthodes de récupération les propriétés privées sont rendues accessibles de manière encapsulée. Pour l?instant il est impossible de les modifier, car nous n?avons pas encore intégré de méthodes d?affectation. Contrôle d?affectation Nous venons de voir comment rendre notre code encapsulé grâce aux méthodes de récupération et d?affectation, l?autre grand intérêt de l?encapsulation concerne le contrôle d?affectation des propriétés. Sans méthodes d?affectation, il est impossible de rendre l?affectation ou l?accès à nos propriétés intelligentes. Imaginons que le nom de la ville doive toujours être formaté correctement. Pour cela nous ajoutons une méthode d?accès setVille qui intègre une logique spécifique : package { public class Joueur { // définition des propriétés de la classe private var nom:String; private var prenom:String; private var age:Number; private var ville:String; // fonction constructeur function Joueur ( pPrenom:String, pNom:String, pAge:Number, pVille:String ) Chapitre 8 ? Programmation orientée objet ? version 0.1.2 35 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org { prenom = pPrenom; nom = pNom; age = pAge; ville = pVille; } // récupère le prénom du joueur public function getPrenom ( ):String { return prenom; } // récupère le nom du joueur public function getNom ( ):String { return nom; } // récupère l'age du joueur public function getAge ( ):Number { return age; } // récupère la ville du joueur public function getVille ( ):String { return ville; } // récupère la ville du joueur public function setVille ( pVille:String ):void { ville = pVille.charAt(0).toUpperCase()+pVille.substr ( 1 ).toLowerCase(); } // méthode permettant au joueur de se presenter public function sePresenter ( ):void { trace("Je m'appelle " + prenom + ", j'ai " + age + " ans." ); Chapitre 8 ? Programmation orientée objet ? version 0.1.2 36 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org } // affiche une description de l'objet Joueur public function toString ( ):String { return "[Joueur prenom : " + prenom +", nom : " + nom + ", age : " + age + ", ville : " + ville + "]"; } } } Le changement de ville est désormais contrôlé, si un nom de ville est passé, le formatage est automatique : // création d'un joueur connu :) var monJoueur:Joueur = new Joueur ("Stevie", "Wonder", 57, "Michigan" ); // affectation d'une ville ma formatée monJoueur.setVille ( "ClEEVELAnd"); // affiche : Cleeveland trace( monJoueur.getVille() ); La méthode setVille formate automatiquement la ville passée au format attendu. En définissant cette méthode, nous sommes assurés du formatage des villes associées à chaque joueur. Pratique n?est-ce pas ? Afin d?éviter les erreurs d?exécution, nous pouvons affecter les propriétés d?un objet en exerçant un contrôle. Nous rendons notre objet intelligent. Une autre situation pourrait être imaginée. Imaginons que l?application serveur gérant la connexion de nos joueurs ne puisse gérer des noms ou prénoms de plus de 30 caractères, nous intégrons ce contrôle au sein des méthodes setNom et setPrenom. Nous rajoutons au passage une méthode setAge arrondissant automatiquement l?âge passé : package { public class Joueur { // définition des propriétés de la classe private var nom:String; private var prenom:String; private var age:Number; private var ville:String; // fonction constructeur Chapitre 8 ? Programmation orientée objet ? version 0.1.2 37 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org function Joueur ( pPrenom:String, pNom:String, pAge:Number, pVille:String ) { prenom = pPrenom; nom = pNom; age = pAge; ville = pVille; } // récupère le prénom du joueur public function getPrenom ( ):String { return prenom; } // récupère le nom du joueur public function getNom ( ):String { return nom; } // récupère l'age du joueur public function getAge ( ):Number { return age; } // récupère la ville du joueur public function getVille ( ):String { return ville; } // permet de changer le prénom du joueur public function setPrenom ( pPrenom:String ):void { if ( pPrenom.length <= 30 ) prenom = pPrenom; else trace ("Le prénom spécifié est trop long"); } // permet de changer le nom du joueur public function setNom ( pNom:String ):void Chapitre 8 ? Programmation orientée objet ? version 0.1.2 38 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org { if ( pNom.length <= 30 ) nom = pNom; else trace ("Le nom spécifié est trop long"); } // permet de changer l?âge du joueur public function setAge ( pAge:Number ):void { // l'age passé est automatiquement arrondi age = Math.floor ( pAge ); } // récupère la ville du joueur public function setVille ( pVille:String ):void { ville = pVille.charAt(0).toUpperCase()+pVille.substr ( 1 ).toLowerCase(); } // méthode permettant au joueur de se présenter public function sePresenter ( ):void { trace("Je m'appelle " + prenom + ", j'ai " + age + " ans." ); } // affiche une description de l'objet Joueur public function toString ( ):String { return "[Joueur prenom : " + prenom +", nom : " + nom + ", age : " + age + ", ville : " + ville + "]"; } } } Si un nom ou un prénom trop long est passé nous pouvons afficher un message avertissant le développeur tiers : // création d'un joueur connu :) var monJoueur:Joueur = new Joueur("Stevie", "Wonder", 57, "Michigan"); // affectation du nouveau nom // affiche : Le nom spécifié est trop long monJoueur.setNom ( "UnNomTresTresTresTresTresTresLong" ); // affiche : Wonder Chapitre 8 ? Programmation orientée objet ? version 0.1.2 39 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org trace( monJoueur.getNom() ); // affectation d'un nouvel age monJoueur.setAge ( 24.8 ); // affiche : 24 trace( monJoueur.getAge() ); En effectuant ce contrôle, nous sommes assurés que la propriété nom ne contiendra aucune chaîne de caractères de plus de 30 caractères. Sans cette vérification faite au moment de l?affectation, nous serions obligés de tester la propriété nom avant de faire quoi que ce soit. Les méthodes d?affectation et de récupération s?avèrent très pratiques, elles offrent la possibilité de délivrer un code sécurisé, portable et élégant. Pourtant certains développeurs préfèrent utiliser les méthodes en lecture/écriture qu?ils considèrent comme plus pratiques. L?utilisation d?une méthode pour récupérer ou affecter une propriété ne leur convient pas. ActionScript 3 propose une solution alternative, appelées méthodes en lecture/écriture. Méthodes en lecture/écriture Les méthodes de lecture et d?écriture plus communément appelées getter/setter sont la prolongation des méthodes d?accès. Leur intérêt reste le même, elles permettent d?encapsuler notre code en gérant l?affectation et la récupération de propriétés d?un objet mais à l?aide d?une syntaxe différente. Les méthodes de lecture et d?écriture permettent au développeur d?appeler de manière transparente ces méthodes comme si ce dernier ciblait une propriété. Afin de bien comprendre ce concept nous allons intégrer des méthodes de lecture et d?écriture dans notre classe Joueur. Afin de définir ces méthodes nous utilisons deux mots clés : ? get : Définit une méthode de lecture ? set : Définit une méthode d?écriture Une méthode de lecture se définit de la manière suivante : // affecte une propriété public function get maPropriete ( ):type { return proprieteInterne; } Chapitre 8 ? Programmation orientée objet ? version 0.1.2 40 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org Celle-ci doit obligatoirement retourner une valeur, et ne posséder aucun paramètre au sein de sa signature. Si ce n?est pas le cas, une erreur à la compilation est générée. Une méthode d?écriture se définit de la manière suivante : // affecte une propriété public function set maPropriete ( pNouvelleValeur:type ):void { proprieteInterne = pNouvelleValeur; } Celle-ci doit obligatoirement posséder au moins un paramètre au sein de sa signature et ne retourner aucune valeur. Si ce n?est pas le cas, une erreur à la compilation est générée. Nous allons modifier les méthodes de récupération et d?affectation setNom et getNom par des méthodes en lecture/écriture. Afin d?éviter un conflit de définition de variables, nous devons nous assurer que les méthodes en lecture/écriture ne possèdent pas le même nom que les propriétés que nous souhaitons externaliser : package { public class Joueur { // définition des propriétés de la classe private var _nom:String; private var prenom:String; private var age:Number; private var ville:String; // fonction constructeur function Joueur ( pPrenom:String, pNom:String, pAge:Number, pVille:String ) { prenom = pPrenom; _nom = pNom; age = pAge; ville = pVille; } // récupère le prénom du joueur public function getPrenom ( ):String { return prenom; Chapitre 8 ? Programmation orientée objet ? version 0.1.2 41 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org } // récupère l'age du joueur public function getAge ( ):Number { return age; } // récupère la ville du joueur public function getVille ( ):String { return ville; } // permet de changer le prénom du joueur public function setPrenom ( pPrenom:String ):void { if ( pPrenom.length <= 30 ) prenom = pPrenom; else trace ("Le prénom spécifié est trop long"); } // récupère le nom du joueur public function get nom ( ):String { return _nom; } // permet de changer le nom du joueur public function set nom ( pNom:String ):void { if ( pNom.length <= 30 ) _nom = pNom; else trace ("Le nom spécifié est trop long"); } // permet de changer l'age du joueur public function setAge ( pAge:Number ):void { // l'age passé est automatiquement arrondi age = Math.floor ( pAge ); } Chapitre 8 ? Programmation orientée objet ? version 0.1.2 42 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org // récupère la ville du joueur public function setVille ( pVille:String ):void { ville = pVille.charAt(0).toUpperCase()+pVille.substr ( 1 ).toLowerCase(); } // méthode permettant au joueur de se présenter public function sePresenter ( ):void { trace("Je m'appelle " + prenom + ", j'ai " + age + " ans." ); } // affiche une description de l'objet Joueur public function toString ( ):String { return "[Joueur prenom : " + prenom +", nom : " + nom + ", age : " + age + ", ville : " + ville + "]"; } } } En utilisant le mot clé set nous avons défini une méthode d?écriture. Ainsi, le développeur à l?impression d?affecter une propriété, en réalité notre méthode d?écriture est déclenchée : // instanciation d'un joueur var monJoueur:Joueur = new Joueur("Stevie", "Wonder", 57, "Michigan"); // affectation du nouveau nom monJoueur.nom = "Womack"; Cela permet de conserver une affectation des données, dans la même écriture que les propriétés, en conservant le contrôle des données passées : // instanciation d'un joueur var monJoueur:Joueur = new Joueur("Stevie", "Wonder", 57, "Michigan"); // affectation du nouveau nom // affiche : Le nom spécifié est trop long monJoueur.nom = "UnNomTresTresTresTresTresTresLong"; En utilisant le mot clé get nous avons défini une méthode de lecture. Ainsi, le développeur à l?impression de cibler une simple propriété, en réalité notre méthode de lecture est déclenchée : // instanciation d'un joueur Chapitre 8 ? Programmation orientée objet ? version 0.1.2 43 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org var monJoueur:Joueur = new Joueur("Stevie", "Wonder", 57, "Michigan"); // affectation du nouveau nom // affiche : Le nom spécifié est trop long monJoueur.nom = "UnNomTresTresTresTresTresTresLong"; // récupération du nom // affiche : Wonder trace( monJoueur.nom ) ; Voici le code final de notre classe Joueur : package { public class Joueur { // définition des propriétés de la classe private var _nom:String; private var _prenom:String; private var _age:Number; private var _ville:String; // fonction constructeur function Joueur ( pPrenom:String, pNom:String, pAge:Number, pVille:String ) { _prenom = pPrenom; _nom = pNom; _age = pAge; _ville = pVille; } // récupère le nom du joueur public function get nom ( ):String { return _nom; } // permet de changer le nom du joueur public function set nom ( pNom:String ):void { if ( pNom.length <= 30 ) _nom = pNom; else trace ("Le nom spécifié est trop long"); } // récupère le prénom du joueur public function get prenom ( ):String { Chapitre 8 ? Programmation orientée objet ? version 0.1.2 44 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org return _prenom; } // permet de changer le prénom du joueur public function set prenom ( pPrenom:String ):void { if ( pPrenom.length <= 30 ) _prenom = pPrenom; else trace ("Le prénom spécifié est trop long"); } // permet de changer l'age du joueur public function set age ( pAge:Number ):void { // l'age passé est automatiquement arrondi _age = Math.floor ( pAge ); } // récupère l'age du joueur public function get age ( ):Number { return _age; } // récupère la ville du joueur public function set ville ( pVille:String ):void { _ville = pVille.charAt(0).toUpperCase()+pVille.substr ( 1 ).toLowerCase(); } // récupère la ville du joueur public function get ville ( ):String { return _ville; } // méthode permettant au joueur de se présenter public function sePresenter ( ):void { trace("Je m'appelle " + prenom + ", j'ai " + age + " ans." ); } Chapitre 8 ? Programmation orientée objet ? version 0.1.2 45 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org // affiche une description de l'objet Joueur public function toString ( ):String { return "[Joueur prenom : " + prenom +", nom : " + nom + ", age : " + age + ", ville : " + ville + "]"; } } } L?encapsulation fait partie des points essentiels de la programmation orientée objet, les méthodes de récupération et d?affectation permettent de cacher l?implémentation et d?exercer un contrôle lors de l?affectation de propriétés. Les méthodes de lecture/écriture étendent ce concept en proposant une syntaxe alternative. Libre à vous de choisir ce que vous préférez, de nombreux débats existent quand au choix d?utilisateur de méthodes de récupération et d?affectation et de lecture/écriture. A retenir ? Dans la plupart des cas, pensez à rendre privées les propriétés d?un objet. ? Afin d?encapsuler notre code nous pouvons utiliser des méthodes de récupération ou d?affectation ou bien des méthodes de lecture et d?écriture. ? L?intérêt est d?exercer un contrôle sur la lecture et l?écriture de propriétés. ? Un code encapsulé rend la classe évolutive et solide. Cas d?utilisation de l?attribut static L?attribut de propriété static permet de rendre un membre utilisable dans un contexte de classe et non d?occurrence. En d?autres termes, lorsqu?une méthode ou une propriété est définie avec l?attribut static celle-ci ne peut être appelée que depuis le constructeur de la classe. Les propriétés et méthodes statiques s?opposent aux méthodes et propriétés d?occurrences qui ne peuvent être appelées que sur des instances de classes. L?intérêt d?une méthode ou propriété statique est d?être globale à la classe, si nous définissons une propriété statique nVitesse au sein d?une classe Vaisseau, lors de sa modification tout les vaisseaux voient leur vitesse modifiée. Chapitre 8 ? Programmation orientée objet ? version 0.1.2 46 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org Parmi les méthodes statiques les plus connues nous pouvons citer : ? Math.abs ? Math.round ? Math.floor ? ExternalInterface.call Nous utilisons très souvent ses propriétés statiques au sein de classes comme Math ou System : ? Math.PI ? System.totalMemory ? Security.sandboxType Voici un exemple canonique d?utilisation d?une propriété statique. Afin de savoir combien d?instances de classes ont été créées, nous définissons une propriété statique i au sein de la classe Joueur. A chaque instanciation nous incrémentons cette propriété et affectons la valeur à la propriété d?occurrence id : package { public class Joueur { // définition des propriétés de la classe private var nom:String; private var prenom:String; private var age:Number; private var ville:String; // propriété statique private static var i:int = 0; // propriété id private var id:int; // fonction constructeur function Joueur ( pPrenom:String, pNom:String, pAge:int, pVille:String ) { prenom = pPrenom; nom = pNom; age = pAge; ville = pVille; // à chaque objet Joueur créé, nous incrémentons // la propriété statique i id = i++; Chapitre 8 ? Programmation orientée objet ? version 0.1.2 47 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org } // reste du code de la classe non montré } } A chaque instanciation d?un objet Joeur, le constructeur est déclenché et incrémente la propriété statique i aussitôt affectée à la propriété d?occurrence id. Pour pouvoir récupérer le nombre de joueurs créés, nous allons créer une méthode statique nous renvoyant la valeur de la propriété statique i : // renvoie le nombre de joueurs créés public static function getNombreJoueurs ( ):int { return i; } Afin de cibler une propriété statique au sein d?une classe nous précisons par convention toujours le constructeur de la classe avant de cibler la propriété : // fonction constructeur function Joueur ( pPrenom:String, pNom:String, pAge:int, pVille:String ) { prenom = pPrenom; nom = pNom; age = pAge; ville = pVille; id = Joueur.i++; } // renvoie le nombre de joueurs créés public static function getNombreJoueurs ( ):int { return Joueur.i; } En spécifiant le constructeur avant la propriété, un développeur tiers serait tout de suite averti de la nature statique d?une propriété. Voici le code final de la classe Joueur : Chapitre 8 ? Programmation orientée objet ? version 0.1.2 48 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org package { public class Joueur { // fonction constructeur function Joueur ( pPrenom:String, pNom:String, pAge:int, pVille:String ) { prenom = pPrenom; nom = pNom; age = pAge; ville = pVille; id = Joueur.i++; } // renvoie le nombre de joueurs créés public static function getNombreJoueurs ( ):int { return Joueur.i; } // récupère le prénom du joueur public function getPrenom ( ):String { return prenom; } // récupère le nom du joueur public function getNom ( ):String { return nom; } // récupère l'age du joueur public function getAge ( ):int { return age; } // récupère la ville du joueur public function getVille ( ):String { Chapitre 8 ? Programmation orientée objet ? version 0.1.2 49 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org return ville; } // récupère la ville du joueur public function setVille ( pVille:String ):void { ville = pVille.charAt(0).toUpperCase()+pVille.substr ( 1 ).toLowerCase(); } // permet de changer le nom du joueur public function setNom ( pNom:String ):void { if ( pNom.length <= 30 ) nom = pNom; else trace ("Le nom spécifié est trop long"); } // permet de changer le prénom du joueur public function setPrenom ( pPrenom:String ):void { if ( pPrenom.length <= 30 ) prenom = pPrenom; else trace ("Le prénom spécifié est trop long"); } // méthode permettant au joueur de se presenter public function sePresenter ( ):void { trace("Je m'appelle " + prenom + ", j'ai " + age + " ans." ); } // affiche une description de l'objet Joueur public function toString ( ):String { return "[Joueur prenom : " + prenom +", nom : " + nom + ", age : " + age + ", ville : " + ville + "]"; } } } Afin de savoir combien de joueurs ont été créés, nous appelons la méthode getNombreJoueurs directement depuis la classe Joueur : Chapitre 8 ? Programmation orientée objet ? version 0.1.2 50 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org // instanciation d'un joueur var monJoueur:Joueur = new Joueur("Stevie", "Wonder", 57, "Michigan"); // affiche : 1 trace( Joueur.getNombreJoueurs() ); // instanciation d'un joueur var monSecondJoueur:Joueur = new Joueur("Bobby", "Womack", 57, "Detroit"); // affiche : 2 trace( Joueur.getNombreJoueurs() ); Si nous tentons d?appeler une méthode de classe sur une occurrence de classe : // création d'un joueur connu :) var monJoueur:Joueur = new Joueur ("Stevie", "Wonder", 57, "Michigan"); // appel d'une méthode statique sur une instance de classe monJoueur.getNombreJoueurs(); Le compilateur nous renvoie l?erreur suivante : 1061: Appel à la méthode getNombreJoueurs peut-être non définie, via la référence de type static Joueur. Contrairement aux propriétés d?occurrences de classes, les propriétés de classes peuvent être initialisées directement après leur définition. Ceci est du au fait qu?une méthode statique n?existe qu?au sein d?une classes et non des occurrences, ainsi si nous créons dix joueurs le constructeur de la classe Joueur sera déclenchée dix fois, mais l?initialisation de la classe et de ses propriétés statiques une seule fois. Ainsi notre tableau tableauJoueurs n?est pas recréé à chaque déclenchement du constructeur. Il est impossible d?utiliser le mot clé this au sein d?une méthode de classe, seules les méthodes d?instances le permettent. Une méthode statique évolue dans le contexte d?une classe et non d?une instance. Si nous tentons de référencer this au sein d?une méthode statique, la compilation est impossible : // renvoie le nombre de joueurs créés public static function getNombreJoueurs ( ):int { return this.i; } Le message d?erreur suivant s?affiche : 1042: Il est impossible d'utiliser le mot-clé this dans une méthode statique. Il ne peut être utilisé que dans les méthodes d'une instance, la fermeture d'une fonction et le code global. Chapitre 8 ? Programmation orientée objet ? version 0.1.2 51 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org A l?inverse les méthodes d?instances peuvent cibler une propriété ou une méthode statique. Le constructeur de la classe Joueur incrémente la propriété statique i : // fonction constructeur function Joueur ( pPrenom:String, pNom:String, pAge:int, pVille:String ) { prenom = pPrenom; nom = pNom; age = pAge; ville = pVille; // A chaque objet Joueur créé, nous incrémentons la propriété statique i id = Joueur.i++; } Lorsque nous avons besoin de définir une propriété ou une méthode ayant une signification globale à toutes les instances de classes nous utilisons l?attribut de propriétés static. Certaines classes ne contiennent que des méthodes statiques, nous pourrions imaginer une classe VerifFormulaire disposant de différentes méthodes permettant de valider un formulaire. Au lieu de créer une instance de classe puis d?appeler la méthode sur celle-ci, nous appelons directement la méthode verifEmail sur la classe : // vérification du mail directement à l'aide d'une méthode statique var mailValide:Boolean = OutilsFormulaire.verifEmail( "bobby@groove.com" ); Cette technique permet par exemple la création de classes utilitaires, rendant l?accès à des fonctionnalités sans instancier d?objet spécifique. Nous piochons directement sur la classe la fonctionnalité qui nous intéresse. A retenir : ? L?utilisation du mot clé this est interdite au sein d?une méthode statique. ? A l?inverse, une méthode d?instance de classe peut cibler ou appeler une méthode ou propriété de classe. ? Les méthodes ou propriétés statiques ont un sens global à la classe. La classe JoueurManager La programmation objet prend tout son sens lorsque nous faisons travailler plusieurs objets ensemble, nous allons maintenant créer une Chapitre 8 ? Programmation orientée objet ? version 0.1.2 52 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org classe qui aura comme simple tâche d?ajouter ou supprimer nos joueurs au sein de l?application, d?autres fonctionnalités comme le tri ou autres pourront être ajoutées plus tard. Le but de la classe JoueurManager sera de centraliser la gestion des joueurs de notre application. A coté de notre classe Joueur nous définissons une nouvelle classe appelée JoueurManager : package { public class JoueurManager { public function JoueurManager ( ) { } } } Nous définissons une propriété d?occurrence tableauJoueurs afin de contenir chaque joueur : package { public class JoueurManager { // tableau contenant les références de joueurs private var tableauJoueurs:Array = new Array(); public function JoueurManager ( ) { } } } La méthode ajouteJoueur ajoute la référence au joueur passé en paramètre au tableau interne tableauJoueurs et appelle la méthode sePresenter sur chaque joueur entrant : package Chapitre 8 ? Programmation orientée objet ? version 0.1.2 53 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org { public class JoueurManager { // tableau contenant les références de joueurs private var tableauJoueurs:Array = new Array(); public function JoueurManager ( ) { } public function ajouteJoueur ( pJoueur:Joueur ):void { pJoueur.sePresenter(); tableauJoueurs.push ( pJoueur ); } } } La méthode ajouteJoueur accepte un paramètre de type Joueur, seuls des objets de type Joueur pourront donc être passés. Lorsqu?un joueur est ajouté, la méthode sePresenter est appelée sur celui-ci. Ainsi, pour ajouter des joueurs nous créons chaque instance puis les ajoutons au gestionnaire de joueurs, la classe JoueurManager : // instanciation d'un joueur var monJoueur:Joueur = new Joueur ("Stevie", "Wonder", 57, "Michigan"); var monDeuxiemeJoueur:Joueur = new Joueur ("Bobby", "Womack", 66, "Detroit"); var monTroisiemeJoueur:Joueur = new Joueur ("Michael", "Jackson", 48, "Los Angeles"); // gestion des joueurs var monManager:JoueurManager = new JoueurManager(); // ajout des joueurs // affiche : /* Je m'appelle Stevie, j'ai 57 ans. Je m'appelle Bobby, j'ai 66 ans. Je m'appelle Michael, j'ai 48 ans. */ monManager.ajouteJoueur ( monJoueur ); monManager.ajouteJoueur ( monDeuxiemeJoueur ); monManager.ajouteJoueur ( monTroisiemeJoueur ); Chapitre 8 ? Programmation orientée objet ? version 0.1.2 54 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org Lorsqu?un joueur est ajouté, il se présente automatiquement. Au cas où un joueur serait supprimé par le système, nous devons le supprimer du gestionnaire, nous définissons une méthode supprimeJoueur. Celle-ci recherche le joueur dans le tableau interne grâce à la méthode indexOf et le supprime si celui-ci est trouvé. Autrement, nous affichons un message indiquant que le joueur n?est plus présent dans le gestionnaire : package { public class JoueurManager { // tableau contenant les références de joueurs private var tableauJoueurs:Array = new Array(); public function JoueurManager ( ) { } public function ajouteJoueur ( pJoueur:Joueur ):void { pJoueur.sePresenter(); tableauJoueurs.push ( pJoueur ); } public function supprimeJoueur ( pJoueur:Joueur ):void { var positionJoueur:int = tableauJoueurs.indexOf ( pJoueur ); if ( positionJoueur != -1 ) tableauJoueurs.splice ( positionJoueur, 1 ); else trace("Joueur non présent !"); } } } Nous pouvons supprimer un joueur en passant sa référence. Afin d?externaliser le tableau contenant tout les joueurs, nous définissons une méthode d?accès getJoueurs : Chapitre 8 ? Programmation orientée objet ? version 0.1.2 55 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org package { public class JoueurManager { // tableau contenant les références de joueurs private var tableauJoueurs:Array; public function JoueurManager ( ) { tableauJoueurs = new Array(); } public function ajouteJoueur ( pJoueur:Joueur ):void { pJoueur.sePresenter(); tableauJoueurs.push ( pJoueur ); } public function supprimeJoueur ( pJoueur:Joueur ):void { var positionJoueur:int = tableauJoueurs.indexOf ( pJoueur ); if ( positionJoueur != -1 ) tableauJoueurs.splice ( positionJoueur, 1 ); else trace("Joueur non présent !"); } public function getJoueurs ( ):Array { return tableauJoueurs; } } } Afin de récupérer l?ensemble des joueurs, nous appelons la méthode getJoueurs sur l?instance de classe JoueurManager : // instanciation d'un joueur var monJoueur:Joueur = new Joueur ("Stevie", "Wonder", 57, "Michigan"); var monDeuxiemeJoueur:Joueur = new Joueur ("Bobby", "Womack", 66, "Detroit"); var monTroisiemeJoueur:Joueur = new Joueur ("Michael", "Jackson", 48, "Los Angeles"); // gestion des joueurs Chapitre 8 ? Programmation orientée objet ? version 0.1.2 56 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org var monManager:JoueurManager = new JoueurManager(); // ajout des joueurs monManager.ajouteJoueur ( monJoueur ); monManager.ajouteJoueur ( monDeuxiemeJoueur ); monManager.ajouteJoueur ( monTroisiemeJoueur ); // récupération de l'ensemble des joueurs // affiche : [Joueur prenom : Stevie, nom : Wonder, age : 57, ville : Michigan],[Joueur prenom : Bobby, nom : Womack, age : 66, ville : Detroit],[Joueur prenom : Michael, nom : Jackson, age : 48, ville : Los Angeles] trace( monManager.getJoueurs() ); Afin de supprimer des joueurs nous appelons la méthode supprimeJoueur en passant le joueur à supprimer en référence : // instanciation d'un joueur var monJoueur:Joueur = new Joueur ("Stevie", "Wonder", 57, "Michigan"); var monDeuxiemeJoueur:Joueur = new Joueur ("Bobby", "Womack", 66, "Detroit"); var monTroisiemeJoueur:Joueur = new Joueur ("Michael", "Jackson", 48, "Los Angeles"); // gestion des joueurs var monManager:JoueurManager = new JoueurManager(); // ajout des joueurs monManager.ajouteJoueur ( monJoueur ); monManager.ajouteJoueur ( monDeuxiemeJoueur ); monManager.ajouteJoueur ( monTroisiemeJoueur ); // suppression de deux joueurs monManager.supprimeJoueur ( monJoueur ); monManager.supprimeJoueur ( monTroisiemeJoueur ); // récupération de l'ensemble des joueurs // affiche : [Joueur prenom : Bobby, nom : Womack, age : 66, ville : Detroit] trace( monManager.getJoueurs() ); La classe JoueurManager nous permet de gérer les différents joueurs créés, nous pourrions ajouter de nombreuses fonctionnalités. En programmation orientée objet il n?existe pas une seule et unique manière de concevoir chaque application. C?est justement la richesse du développement orienté objet, nous pouvons discuter des heures durant, de la manière dont nous avons développé une application, certains seront d?accord avec votre raisonnement d?autres ne le seront pas. L?intérêt est d?échanger idées et point de vue sur une conception. Lorsque nous devons développer une application, nous sommes souvent confrontés à des problèmes qu?un autre développeur a surement rencontrés avant nous. Pour apporter des solutions concrètes à des problèmes récurrents nous pouvons utiliser des modèles de conceptions. Ces derniers définissent la manière dont nous devons séparer et concevoir notre application toujours dans un objectif d?optimisation, de portabilité et de réutilisation. Nous reviendrons bientôt sur les modèles de conception les plus utilisés. Chapitre 8 ? Programmation orientée objet ? version 0.1.2 57 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org Nos classes Joueur et JoueurManager sont désormais utilisées dans nos applications, mais le client souhaite introduire la notion de modérateurs. En y réfléchissant quelques instants nous pouvons facilement admettre qu?un modérateur est une sorte de joueur, mais disposant de droits spécifiques comme le fait d?exclure un autre joueur ou de stopper une partie en cours. Un administrateur possède donc toutes les capacités d?un joueur. Il serait redondant dans ce cas précis, de redéfinir toutes les méthodes et propriétés déjà définies au sein de la classe Joueur au sein de la classe Administrateur, pour réutiliser notre code nous allons utiliser l?héritage. L?héritage La notion d?héritage est un concept clé de la programmation orientée objet tiré directement du monde qui nous entoure. Tout élément du monde réel hérite d?un autre en le spécialisant, ainsi en tant qu?être humain nous héritons de toutes les caractéristiques d?un mammifère, au même titre qu?une pomme hérite des caractéristiques du fruit. L?héritage est utilisé lorsqu?une relation de type « est-un » est possible. Nous pouvons considérer bien qu?un administrateur est un type de joueur et possède toutes ses capacités, et d?autres qui font de lui un modérateur. Lorsque cette relation n?est pas vérifiée, l?héritage ne doit pas être considéré, dans ce cas nous préférerons généralement la composition. Nous traiterons au cours du chapitre 10 intitulé Héritage versus composition les différences entre les deux approches, nous verrons que l?héritage peut s?avérer rigide et difficile à maintenir dans certaines situations. Dans un contexte d?héritage, des classes filles héritent automatiquement des fonctionnalités des classes mères. Dans notre cas, nous allons créer une classe Administrateur héritant de toutes les fonctionnalités de la classe Joueur. La classe mère est généralement appelée super-classe, tandis que la classe fille est appelée sous-classe. Nous allons définir une classe Administrateur à coté de la classe Joueur, afin de traduire l?héritage nous utilisons le mot clé extends : package { // l'héritage est traduit par le mot clé extends public class Administrateur extends Joueur { Chapitre 8 ? Programmation orientée objet ? version 0.1.2 58 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org public function Administrateur ( ) { } } } Puis nous instancions un objet administrateur : var monModo:Administrateur = new Administrateur(); A la compilation, l?erreur suivante est générée : 1203: Aucun constructeur par défaut n'a été défini dans la classe de base Joueur. Lorsqu?une classe fille étend une classe mère, nous devons obligatoirement passer au constructeur parent, les paramètres nécessaires à l?initialisation de la classe mère, pour déclencher le constructeur parent nous utilisons le mot clé super : package { // l'héritage est traduit par le mot clé extends public class Administrateur extends Joueur { public function Administrateur ( pPrenom:String, pNom:String, pAge:int, pVille:String ) { super ( pPrenom, pNom, pAge, pVille ); } } } L?intérêt de l?héritage réside dans la réutilisation du code. La figure 8- 5 illustre le concept d?héritage de classes : Chapitre 8 ? Programmation orientée objet ? version 0.1.2 59 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 8-5. Héritage de classes. Sans l?héritage nous aurions du redéfinir l?ensemble des méthodes de la classe Joueur au sein de la classe Administrateur. Grâce à l?héritage exprimé par le mot clé extends, la classe fille Administrateur hérite de toutes les fonctionnalités de la classe mère Joueur : // nous créons un objet administrateur var monModo:Administrateur = new Administrateur("Michael", "Jackson", 48, "Los Angeles"); // le modérateur possède toutes les capacités d'un joueur // affiche : Je m'appelle Michael, j'ai 48 ans. monModo.sePresenter(); // affiche : Jackson trace( monModo.nom ); // affiche : Michael trace( monModo.prenom ); // affiche : 48 trace( monModo.age ); // affiche : Los Angeles trace( monModo.ville ); Il existe quelques exceptions liées à l?héritage, les méthodes et propriétés statiques ne sont pas héritées. La méthode statique Chapitre 8 ? Programmation orientée objet ? version 0.1.2 60 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org getNombreJoueurs définie au sein de la classe Joueur n?est pas disponible sur la classe Administrateur : // instanciation d?un administrateur var monModo:Administrateur = new Administrateur("Michael", "Jackson", 48, "Los Angeles"); // la méthode statique Joueur.getNombreJoueurs n'est pas héritée monModo.getNombreJoueurs(); L?erreur à la compilation suivante est générée : 1061: Appel à la méthode getNombreJoueurs peut-être non définie, via la référence de type static Administrateur. Notons que l?héritage multiple n?est pas géré en ActionScript 3, une classe ne peut hériter de plusieurs classes directes. L?héritage ne se limite pas aux classes personnalisées, nous verrons au cours du chapitre 9 intitulé Etendre Flash comment étendre des classes natives de Flash afin d?augmenter les capacités de classes telles Array, BitmapData ou MovieClip et bien d?autres. A retenir : ? L?héritage permet de réutiliser facilement les fonctionnalités d?une classe existante. ? La classe mère est appelée super-classe, la classe fille est appelée sous-classe. On parle alors de super-type et de sous-type. ? Afin d?hériter d?une classe nous utilisons le mot clé extends. Sous-types et super-type Grâce à l?héritage, la classe Administrateur possède désormais deux types, Joueur considéré comme le super-type et Administrateur comme sous-type : // la classe Administrateur est aussi de type Joueur var monModo:Administrateur = new Administrateur("Michael", "Jackson", 48, "Los Angeles"); // un modérateur est un joueur // affiche : true trace( monModo is Joueur ); // un modérateur est aussi un administrateur // affiche : true trace( monModo is Administrateur ); Partout où le super-type est attendu nous pouvons passer une instance de sous-type, ainsi une variable de type Joueur peut stocker un objet de type Administrateur : // la classe Administrateur est aussi de type Joueur Chapitre 8 ? Programmation orientée objet ? version 0.1.2 61 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org var monModo:Joueur = new Administrateur("Michael", "Jackson", 48, "Los Angeles"); // un modérateur est un joueur // affiche : true trace( monModo is Joueur ); // un modérateur est aussi un administrateur // affiche : true trace( monModo is Administrateur ); Tous les administrateurs sont des joueurs, le compilateur sait que toutes les fonctionnalités du type Joueur sont présentes au sein de la classe Administrateur et nous permet de compiler. L?inverse n?est pas vrai, les joueurs ne sont pas forcément des administrateurs, il est donc impossible d?affecter un super-type à une variable de sous-type. Le code suivant ne peut être compilé : // la classe Joueur n'est pas de type Administrateur var monModo:Administrateur = new Joueur("Michael", "Jackson", 48, "Los Angeles"); L?erreur à la compilation suivante est générée : 1118: Contrainte implicite d'une valeur du type statique Joueur vers un type peut-être sans rapport Administrateur. Le compilateur ne peut nous garantir que les fonctionnalités de la classe Administrateur seront présentes sur l?objet Joueur et donc interdit la compilation. Nous retrouvons le même concept en utilisant les classes graphiques natives comme flash.display.Sprite et flash.display.MovieClip, le code suivant peut être compilé sans problème : // une instance de MovieClip est aussi de type Sprite var monClip:Sprite = new MovieClip(); La classe MovieClip hérite de la classe Sprite ainsi une instance de MovieClip est aussi de type Sprite. A l?inverse, un Sprite n?est pas de type MovieClip : // une instance de Sprite n'est pas de type MovieClip var monClip:MovieClip = new Sprite(); Si nous testons le code précédant, l?erreur à la compilation suivante est générée : 1118: Contrainte implicite d'une valeur du type statique flash.display:Sprite vers un type peut-être sans rapport flash.display:MovieClip. La question que nous pouvons nous poser est la suivante, quel est l?intérêt de stocker un objet correspondant à un sous-type au sein Chapitre 8 ? Programmation orientée objet ? version 0.1.2 62 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org d?une variable de super-type ? Pourquoi ne pas simplement utiliser le type correspondant ? Afin de bien comprendre ce concept, imaginons qu?une méthode nous renvoie des objets de différents types, prenons un cas très simple comme la méthode getChildAt de la classe DisplayObjectContainer. Si nous regardons sa signature, nous voyons que celle-ci renvoie un objet de type DisplayObject : public function getChildAt(index:int):DisplayObject En effet, la méthode getChildAt peut renvoyer toutes sortes d?objets graphiques tels Shape, MovieClip, Sprite ou bien SimpleButton. Tous ces types on quelque chose qui les lient, le type DisplayObject est le type commun à tous ces objets graphiques, ainsi lorsque plusieurs types sont attendus, nous utilisons un type commun aux différentes classes. Nous allons justement mettre cela en application au sein de notre classe JoueurManager, si nous regardons la signature de la méthode ajouterJoueur nous voyons que celle-ci accepte un paramètre de type Joueur : public function ajouteJoueur ( pJoueur:Joueur ):void { pJoueur.sePresenter(); tableauJoueurs.push ( pJoueur ); } Joueur est le type commun aux deux classes Joueur et Administrateur, nous pouvons donc sans problème lui passer des instances de type Administrateur : // création des joueurs et du modérateur var premierJoueur:Joueur = new Joueur ("Bobby", "Womack", 66, "Detroit"); var deuxiemeJoueur:Joueur = new Joueur ("Michael", "Jackson", 48, "Michigan"); var troisiemeJoueur:Joueur = new Joueur ("Lenny", "Williams", 50, "New York"); var monModo:Administrateur = new Administrateur ("Stevie", "Wonder", 48, "Los Angeles"); // gestion des joueurs var monManager:JoueurManager = new JoueurManager(); // ajout des joueurs /* affiche : Je m'appelle Bobby, j'ai 66 ans. Je m'appelle Michael, j'ai 48 ans. Chapitre 8 ? Programmation orientée objet ? version 0.1.2 63 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org Je m'appelle Bobby, j'ai 30 ans. Je m'appelle Stevie, j'ai 48 ans. Je suis modérateur */ monManager.ajouteJoueur ( premierJoueur ); monManager.ajouteJoueur ( deuxiemeJoueur ); monManager.ajouteJoueur ( troisiemeJoueur ); monManager.ajouteJoueur ( monModo ); Si nous devons plus tard créer de nouveaux types de joueurs en étendant la classe Joueur, aucune modification ne sera nécessaire au sein de la fonction ajouteJoueur. A retenir : ? Grâce à l?héritage, une classe peut avoir plusieurs types. ? Partout où une classe mère est attendue nous pouvons utiliser une classe fille. C?est ce que nous appelons le polymorphisme. Spécialiser une classe Pour le moment, la classe Administrateur possède les mêmes fonctionnalités que la classe Joueur. Il est inutile d?hériter simplement d?une classe mère sans ajouter de nouvelles fonctionnalités, en définissant de nouvelles méthodes au sein de la classe Administrateur nous spécialisons la classe Joueur. Nous allons définir une nouvelle méthode appelée kickJoueur qui aura pour but de supprimer un joueur de la partie en cours : package { // l'héritage est traduit par le mot clé extends public class Administrateur extends Joueur { public function Administrateur ( pPrenom:String, pNom:String, pAge:int, pVille:String ) { super ( pPrenom, pNom, pAge, pVille ); } // méthode permettant de supprimer un joueur de la partie public function kickJoueur ( pJoueur:Joueur ):void { trace ("Kick " + pJoueur ); } Chapitre 8 ? Programmation orientée objet ? version 0.1.2 64 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org } } Désormais la classe Administrateur possède une méthode kickJoueur en plus des fonctionnalités de la classe Joueur. Nous avons spécialisé la classe Joueur à travers la classe Administrateur. La figure 8-6 illustre les deux classes : Figure 8-6. Spécialisation de la classe Joueur. Lorsque la méthode kickJoueur est exécutée, nous devons appeler la méthode supprimeJoueur de la classe JoueurManager car c?est celle qui centralise et gère les joueurs connectés. Chaque administrateur pourrait avoir une référence à la classe JoueurManager mais cela serait rigide, dans ce cas nous préférerons une approche événementielle en utilisant dans nos classes personnalisées le modèle événementiel d?ActionScript 3 à l?aide de la classe flash.events.EventDispatcher. La notion de diffusion d?événements personnalisés est traitée en détail lors du chapitre 12 intitulé Diffusion d?événements personnalisés. Une Chapitre 8 ? Programmation orientée objet ? version 0.1.2 65 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org fois achevé, n?hésitez pas à revenir sur cet exemple pour ajouter le code nécessaire. L?idée est que la classe Administrateur diffuse un événement indiquant à la classe JoueurManager de supprimer le joueur en question. Nous retrouverons ici le concept de diffuseur écouteur traité dans les chapitres précédents. La figure 8-6 illustre le concept : Figure 8-6. L?objet JoueurManager est souscrit à un événement auprès d?un administrateur. Lorsqu?un administrateur diffuse l?événement approprié, l?objet JoueurManager supprime le joueur de la partie en cours. Nous allons nous intéresser maintenant à un processus appelé transtypage très utile dans un contexte d?héritage. A retenir : ? Il ne faut pas confondre héritage et spécialisation. ? Même si cela est déconseillé, une classe peut hériter d?une autre sans ajouter de nouvelles fonctionnalités. ? Lorsqu?une sous-classe ajoute de nouvelles fonctionnalités, on dit que celle-ci spécialise la super-classe. Le transtypage Le transtypage est un processus très simple qui consiste à faire passer un objet pour un autre auprès du compilateur. Afin de comprendre le transtypage prenons la situation suivante, comme nous l?avons vu précédemment il peut arriver qu?une variable d?un type parent stocke des instances de types enfants. Chapitre 8 ? Programmation orientée objet ? version 0.1.2 66 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org Dans le code suivant nous stockons au sein d?une variable de type Joueur une instance de classe Administrateur : // création d'un joueur et d'un administrateur var premierJoueur:Joueur = new Joueur ("Bobby", "Womack", 66, "Detroit"); var monModo:Joueur = new Administrateur("Michael", "Jackson", 48, "Los Angeles"); // la méthode kickJoueur n'existe pas pour le compilateur, car la classe Joueur ne la définit pas monModo.kickJoueur(premierJoueur); Cela ne pose aucun problème, sauf lorsque nous tentons d?appeler la méthode kickJoueur sur la variable monModo, le compilateur nous génère l?erreur suivante : 1061: Appel à la méthode kickJoueur peut-être non définie, via la référence de type static Joueur. Pour le compilateur, la classe Joueur ne possède pas de méthode kickJoueur, la compilation est donc impossible. Pourtant nous savons qu?à l?exécution la variable monModo contiendra une instance de la classe Administrateur qui possède bien la méthode kickJoueur. Afin de faire taire le compilateur nous allons utiliser le transtypage en disant en quelque sorte au compilateur « Fais moi confiance, il y?a bien la méthode kickJoueur sur l?objet, laisse moi compiler ». La syntaxe du transtypage est simple, nous précisions le type puis nous plaçons deux parenthèses comme pour une fonction traditionnelle : Type ( monObjet ) ; Ainsi, nous transtypons la variable monModo vers le type Administrateur afin de pouvoir compiler : // création d'un joueur et d'un administrateur var premierJoueur:Joueur = new Joueur ("Bobby", "Womack", 66, "Detroit"); var monModo:Joueur = new Administrateur("Michael", "Jackson", 48, "Los Angeles"); // affiche : Kick [Joueur prenom : Bobby, nom : Womack, age : 66, ville : Detroit] Administrateur ( monModo ).kickJoueur(premierJoueur); A l?exécution, la méthode est bien exécutée. Là encore, nous pouvons nous demander dans quel cas concret nous devrions avoir recourt au transtypage ? Nous allons définir une nouvelle méthode jouerSon sur la classe Administrateur qui sera automatiquement appelée au sein de la méthode ajouteJoueur. Lorsqu?un administrateur sera ajouté à Chapitre 8 ? Programmation orientée objet ? version 0.1.2 67 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org l?application un son sera joué afin de notifier de l?arrivée du modérateur : package { // l'héritage est traduit par le mot clé extends public class Administrateur extends Joueur { public function Administrateur ( pPrenom:String, pNom:String, pAge:int, pVille:String ) { super ( pPrenom, pNom, pAge, pVille ); } // méthode permettant de supprimer un joueur de la partie public function kickJoueur ( pJoueur:Joueur ):void { trace ("Kick " + pJoueur ); } // méthode permettant de jouer un son public function jouerSon ( ):void { trace("Joue un son"); } } } Au sein de la classe JoueurManager nous devons appeler la méthode jouerSon lorsqu?un objet de type Administrateur est passé, pour cela nous testons si le type correspond puis nous appelons la méthode voulue : package { public class JoueurManager { // tableau contenant les références de joueurs private var tableauJoueurs:Array; public function JoueurManager ( ) { Chapitre 8 ? Programmation orientée objet ? version 0.1.2 68 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org tableauJoueurs = new Array(); } public function ajouteJoueur ( pJoueur:Joueur ):void { pJoueur.sePresenter(); if ( pJoueur is Administrateur ) pJoueur.jouerSon(); tableauJoueurs.push ( pJoueur ); } public function supprimeJoueur ( pJoueur:Joueur ):void { var positionJoueur:int = tableauJoueurs.indexOf ( pJoueur ); if ( positionJoueur != -1 ) tableauJoueurs.splice ( positionJoueur, 1 ); else throw new Error ("Joueur non présent !"); } public function getJoueurs ( ):Array { return tableauJoueurs; } } } Une fois nos classes modifiées, nous pouvons initialiser notre application : // création d'un joueur et d'un administrateur var premierJoueur:Joueur = new Joueur ("Bobby", "Womack", 66, "Detroit"); var deuxiemeJoueur:Joueur = new Joueur ("Michael", "Jackson", 48, "Michigan"); var troisiemeJoueur:Joueur = new Joueur ("Lenny", "Williams", 50, "New York"); var monModo:Administrateur = new Administrateur("Michael", "Jackson", 48, "Los Angeles"); // gestion des joueurs var monManager:JoueurManager = new JoueurManager(); // ajout des joueurs et administrateurs monManager.ajouteJoueur ( premierJoueur ); monManager.ajouteJoueur ( deuxiemeJoueur ); monManager.ajouteJoueur ( troisiemeJoueur ); monManager.ajouteJoueur ( monModo ); Chapitre 8 ? Programmation orientée objet ? version 0.1.2 69 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org A la compilation l?erreur suivante est générée : 1061: Appel à la méthode jouerSon peut-être non définie, via la référence de type static Joueur. Le compilateur se plaint car pour lui nous tentons d?appeler une méthode inexistante sur la classe Joueur. Nous savons qu?à l?exécution si la condition est validée, nous serons assurés que l?objet possède bien la méthode jouerSon, pour nous permettre de compiler nous transtypons vers le type Administrateur: package { public class JoueurManager { // tableau contenant les références de joueurs private var tableauJoueurs:Array; public function JoueurManager ( ) { tableauJoueurs = new Array(); } public function ajouteJoueur ( pJoueur:Joueur ):void { pJoueur.sePresenter(); if ( pJoueur is Administrateur ) Administrateur ( pJoueur ).jouerSon(); tableauJoueurs.push ( pJoueur ); } public function supprimeJoueur ( pJoueur:Joueur ):void { var positionJoueur:int = tableauJoueurs.indexOf ( pJoueur ); if ( positionJoueur != -1 ) tableauJoueurs.splice ( positionJoueur, 1 ); else throw new Error ("Joueur non présent !"); } public function getJoueurs ( ):Array { return tableauJoueurs; Chapitre 8 ? Programmation orientée objet ? version 0.1.2 70 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org } } } Nous pouvons initialiser notre application : // création d'un joueur et d'un administrateur var premierJoueur:Joueur = new Joueur ("Bobby", "Womack", 66, "Detroit"); var deuxiemeJoueur:Joueur = new Joueur ("Michael", "Jackson", 48, "Michigan"); var troisiemeJoueur:Joueur = new Joueur ("Lenny", "Williams", 50, "New York"); var monModo:Administrateur = new Administrateur("Michael", "Jackson", 48, "Los Angeles"); // gestion des joueurs var monManager:JoueurManager = new JoueurManager(); // ajout des joueurs et administrateurs /* affiche : Je m'appelle Bobby, j'ai 66 ans. Je m'appelle Michael, j'ai 48 ans. Je m'appelle Lenny, j'ai 50 ans. Je m'appelle Michael, j'ai 48 ans. Joue un son */ monManager.ajouteJoueur ( premierJoueur ); monManager.ajouteJoueur ( deuxiemeJoueur ); monManager.ajouteJoueur ( troisiemeJoueur ); monManager.ajouteJoueur ( monModo ); Nous reviendrons très souvent sur la notion de transtypage, couramment utilisée au sein la liste d?affichage. A retenir : ? Il ne faut pas confondre conversion et transtypage. ? Le transtypage ne convertit pas un objet en un autre, mais permet de faire passer un objet pour un autre. Surcharge Le concept de surcharge (override en anglais) intervient lorsque nous avons besoin de modifier certaines fonctionnalités héritées. Imaginons qu?une méthode héritée ne soit pas assez complète, nous pouvons surcharger celle-ci dans la sous-classe afin d?intégrer notre nouvelle version. Nous allons définir au sein de notre classe Administrateur une méthode sePresenter, afin de surcharger la version héritée, pour cela nous utilisons le mot clé override : package Chapitre 8 ? Programmation orientée objet ? version 0.1.2 71 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org { // l'héritage est traduit par le mot clé extends public class Administrateur extends Joueur { public function Administrateur ( pPrenom:String, pNom:String, pAge:int, pVille:String ) { super ( pPrenom, pNom, pAge, pVille ); } // méthode permettant à l?administrateur de se présenter override public function sePresenter ( ):void { trace("Je suis modérateur"); } } } En appelant la méthode sePresenter sur notre instance de modérateur nous déclenchons la version redéfinie : // nous instancions un modérateur var monModo:Administrateur = new Administrateur("Michael", "Jackson", 48, "Los Angeles"); // nous lui demandons de se présenter // affiche : Je suis modérateur monModo.sePresenter(); En ActionScript 2, il n?existait pas de mot clé pour surcharger une méthode, le simple fait de définir une méthode du même nom au sein de la sous-classe surchargeait la méthode héritée. Grâce au mot clé override introduit par ActionScript 3 il est beaucoup plus simple pour un développeur de savoir si une méthode est une méthode surchargeante ou non. Attention, la méthode surchargeante doit avoir le même nom et la même signature que la méthode surchargée, sinon la surcharge est dite non compatible. Dans l?exemple suivant nous retournons une valeur lorsque la méthode sePresenter est exécutée : package { // l'héritage est traduit par le mot clé extends public class Administrateur extends Joueur Chapitre 8 ? Programmation orientée objet ? version 0.1.2 72 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org { public function Administrateur ( pPrenom:String, pNom:String, pAge:int, pVille:String ) { super ( pPrenom, pNom, pAge, pVille ); } // méthode permettant à l'administrateur de se présenter override public function sePresenter ( ):String { // déclenche la méthode surchargée super.sePresenter(); return "Je suis modérateur"; } // méthode permettant de supprimer un joueur de la partie public function kickJoueur ( pJoueur:Joueur ):void { trace ("Kick " + pJoueur ); } // méthode permettant de jouer un son public function jouerSon ( ):void { trace("Joue un son"); } } } La méthode surchargeante n?a pas la même signature que la méthode surchargée, la compilation est impossible, si nous testons le code suivant : var monModo:Administrateur = new Administrateur("Michael", "Jackson", 48, "Los Angeles"); L?erreur suivante est générée à la compilation : 1023: override non compatible. Dans un contexte de surcharge, la méthode surchargée n?est pas perdue. Si nous souhaitons au sein de notre méthode surchargeante déclencher la méthode surchargée, nous utilisons le mot clé super : package Chapitre 8 ? Programmation orientée objet ? version 0.1.2 73 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org { // l'héritage est traduit par le mot clé extends public class Administrateur extends Joueur { public function Administrateur ( pPrenom:String, pNom:String, pAge:int, pVille:String ) { super ( pPrenom, pNom, pAge, pVille ); } // méthode permettant au joueur de se présenter override public function sePresenter ( ):void { // déclenche la méthode sePresenter surchargée super.sePresenter(); trace("Je suis modérateur"); } } } Ainsi, lorsque la méthode sePresenter est déclenchée, celle-ci déclenche aussi la version surchargée : // nous instancions un modérateur var monModo:Administrateur = new Administrateur("Michael", "Jackson", 48, "Los Angeles"); // nous lui demandons de se présenter // affiche : /* Je m'appelle Michael, j'ai 48 ans. Je suis modérateur */ monModo.sePresenter(); Dans le code précédent nous augmentons les capacités de la méthode sePresenter en ajoutant le message « Je suis modérateur ». Grâce au mot clé super nous pouvons exécuter la méthode surchargée, celle-ci n?est pas perdue. En surchargeant à l?aide d?une méthode vide, nous pouvons supprimer une fonctionnalité héritée : package { Chapitre 8 ? Programmation orientée objet ? version 0.1.2 74 / 74 Thibault Imbert pratiqueactionscript3.bytearray.org // l'héritage est traduit par le mot clé extends public class Administrateur extends Joueur { public function Administrateur ( pPrenom:String, pNom:String, pAge:int, pVille:String ) { super ( pPrenom, pNom, pAge, pVille ); } // méthode surchargeante vide override public function sePresenter ( ):void { } } } Lorsque la méthode sePresenter est appelée sur une instance de la classe Administrateur, la version vide est exécutée. Celle-ci ne contenant aucune logique, nous avons annulé l?héritage de la méthode sePresenter. A retenir : ? La surcharge permet de redéfinir une fonctionnalité héritée. ? Afin que la surcharge soit possible, la méthode surchargée et surchargeante doivent avoir la même signature. ? Afin de surcharger une méthode nous utilisons le mot clé override. Chapitre 9 ? Etendre les classes natives ? version 0.1.2 1 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org 9 Etendre les classes natives INTERETS ............................................................................................................... 1 LE VIEIL AMI PROTOTYPE ........................................................................................ 2 ETENDRE LES CLASSES NON GRAPHIQUES................................................ 5 ETENDRE LES CLASSES GRAPHIQUES........................................................ 11 ACCÉDER À L?OBJET STAGE DE MANIÈRE SÉCURISÉE ............................................ 19 AJOUTER DES FONCTIONNALITÉS.......................................................................... 23 RÉUTILISER LE CODE............................................................................................. 47 CLASSE DYNAMIQUE............................................................................................. 49 UN VRAI CONSTRUCTEUR...................................................................................... 51 CRÉER DES BOUTONS DYNAMIQUES ...................................................................... 52 Intérêts Nous avons découvert au cours du chapitre précédent les principaux concepts clé de la programmation orientée objet. Cependant, une des principales difficultés réside souvent dans la mise en application de ces notions dans un projet concret ActionScript. La grande puissance de Flash réside dans sa capacité à lier graphisme et programmation. Nous allons profiter de cette force pour appliquer ce que nous avons appris au cours du chapitre précédent à travers différents cas pratiques. La notion d?héritage ne se limite pas aux classes personnalisées et peut être appliquée à n?importe quelle classe de l?API du lecteur Flash. Il est par exemple possible d?étendre la classe native MovieClip, en définissant de nouvelles méthodes afin d?obtenir un MovieClip amélioré. Chapitre 9 ? Etendre les classes natives ? version 0.1.2 2 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org Le but de ce chapitre est d?apprendre à étendre les classes natives telles MovieClip, Sprite, BitmapData mais aussi des classes non graphiques comme Array afin d?augmenter les fonctionnalités offertes par celle-ci. Attention, nous verrons que certaines classes natives sont considérées comme scellées et ne peuvent être étendues. Le vieil ami prototype En ActionScript 1, les développeurs avaient pour habitude d?utiliser le prototype d?une classe afin d?augmenter ses capacités. En ActionScript 3, cette pratique fonctionne toujours mais n?est pas officiellement recommandée. Dans le code suivant nous définissons une méthode hello sur le prototype de la classe MovieClip : // ajout d'une méthode hello MovieClip.prototype.hello = function () { // affiche : [object MovieClip] trace( this ); } // création d'un clip var monClip:MovieClip = new MovieClip(); // le clip possède automatiquement la méthode ajoutée au prototype monClip.hello(); Automatiquement, le clip créé possède la méthode hello. Bien qu?efficace, cette technique pollue les autres instances de la même classe, car la méthode hello est alors disponible sur tous les MovieClip de l?animation. Dans le code suivant nous appelons la méthode hello sur le scénario principal : // ajout d'une méthode hello MovieClip.prototype.hello = function () { // affiche : [object MainTimeline] trace( this ); } // le scénario possède automatiquement la méthode ajoutée au prototype this.hello(); Chapitre 9 ? Etendre les classes natives ? version 0.1.2 3 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org Dans un concept d?héritage, l?idée est d?obtenir un nouveau type d?objet, doté de nouvelles fonctionnalités. En utilisant cette technique nous ne créons aucune nouvelle variété d?objet, nous ajoutons simplement des fonctionnalités à une classe existante. Imaginons que nous devions créer une balle ayant la capacité de rebondir de manière élastique. Nous pourrions définir sur le prototype de la classe MovieClip toutes les méthodes de collision nécessaires à notre balle, mais serait-ce réellement optimisé ? En utilisant cette approche, tous les MovieClip de notre application seraient dotés de capacités de rebond. Pourtant, seule la balle a véritablement besoin de telles capacités. Il serait plus intéressant d?étendre la classe MovieClip et de lier notre balle à la sous-classe. Le prototypage possède en revanche un intérêt majeur lié à sa simplicité et son efficacité. Prenons le cas de la classe DisplayObjectContainer. Comme nous l?avons vu lors du chapitre 4 intitulé Liste d?affichage, la classe DisplayObjectContainer ne définit pas de méthode permettant de supprimer la totalité des objets enfants. A l?aide d?une méthode ajoutée au prototype de la classe DisplayObjectContainer nous pouvons ajouter très facilement cette fonctionnalité : DisplayObjectContainer.prototype.supprimeEnfants = function ( ) { var nbEnfants:int = this.numChildren; while ( this.numChildren > 0 ) this.removeChildAt ( 0 ); return nbEnfants; } Ainsi, toutes les instances de la classe DisplayObjectContainer diposent désormais d?une méthode supprimeEnfants. En plus de permettre la suppression de tous les enfants, celle-ci retourne le nombre d?enfants supprimés. Si nous disposons plusieurs objets graphiques sur le scénario principal, nous pouvons les supprimer en appelant la méthode supprimeEnfants : DisplayObjectContainer.prototype.supprimeEnfants = function ( ) { Chapitre 9 ? Etendre les classes natives ? version 0.1.2 4 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org var nbEnfants:int = this.numChildren; while ( this.numChildren > 0 ) this.removeChildAt ( 0 ); return nbEnfants; } // supprime tous les objets enfants du scénario principal this.supprimeEnfants(); De la même manière nous pouvons supprimer tous les objets enfants d?une instance de la classe Sprite : // création d'un conteneur de type Sprite var monSprite:Sprite = new Sprite (); // création d'un objet Shape enfant var maForme:Shape = new Shape(); maForme.graphics.lineStyle ( 1, 0x990000, 1 ); maForme.graphics.beginFill ( 0x990000, .2 ); maForme.graphics.drawCircle ( 50, 50, 50 ); monSprite.addChild( maForme ); addChild ( monSprite ); // suppression des objets enfants var nbEnfantsSupprimes:int = monSprite.supprimeEnfants(); En testant le code précédent, une erreur à la compilation est générée : 1061: Appel à la méthode supprimeEnfants peut-être non définie, via la référence de type static flash.display:Sprite. Le compilateur empêche la compilation car aucune méthode du nom de supprimeEnfants n?est trouvée. Afin de faire taire le compilateur nous pouvons exceptionellement transtyper vers la classe dynamique Object non soumise à la vérification de type à la compilation : // création d'un conteneur de type Sprite var monSprite:Sprite = new Sprite (); // création d'un objet Shape enfant var maForme:Shape = new Shape(); maForme.graphics.lineStyle ( 1, 0x990000, 1 ); maForme.graphics.beginFill ( 0x990000, .2 ); maForme.graphics.drawCircle ( 50, 50, 50 ); monSprite.addChild( maForme ); addChild ( monSprite ); // suppression des objets enfants var nbEnfantsSupprimes:int = Object(monSprite).supprimeEnfants(); // affiche : 1 trace( nbEnfantsSupprimes ); Chapitre 9 ? Etendre les classes natives ? version 0.1.2 5 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org Nous reviendrons sur l?intérêt du prototypage au cours du chapitre 16 intitulé Le texte. A retenir ? L?utilisation du prototype est toujours possible en ActionScript 3. ? Son utilisation est officiellement déconseillée, mais offre une souplesse intéressante dans certains cas précis. ? Nous préférerons dans la majorité des cas l?utilisation de sous- classes afin d?étendre les capacités. Etendre les classes non graphiques D?autres classes natives peuvent aussi être étendues afin d?augmenter leurs capacités, c?est le cas de la classe Array, dont les différentes méthodes ne sont quelquefois pas suffisantes. Ne vous est-il jamais arrivé de vouloir rapidement mélanger les données d?un tableau ? En étendant la classe Array nous allons ajouter un ensemble de méthodes pratiques, qui seront disponibles pour toute instance de sous-classe. A côté d?un nouveau document Flash CS3 nous définissons une classe MonTableau au sein du paquetage org.bytearray.outils. Voici le code de la sous-classe MonTableau : package org.bytearray.outils { dynamic public class MonTableau extends Array { public function MonTableau ( ...rest ) { } } } La sous-classe MonTableau est une classe dynamique car l?accès aux données par l?écriture crochet requiert l?utilisation d?une classe dynamique. Chapitre 9 ? Etendre les classes natives ? version 0.1.2 6 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org Etendre la classe Array nécessite une petite astuce qui n?a pas été corrigée avec ActionScript 3. Au sein du constructeur de la sous- classe nous devons ajouter la ligne suivante : package org.bytearray.outils { dynamic public class MonTableau extends Array { public function MonTableau ( ...rest ) { splice.apply(this, [0, 0].concat(rest)); } } } Cette astuce permet d?initialiser correctement le tableau lorsque des paramètres sont passés au constructeur. A ce stade, nous bénéficions d?une sous-classe opérationnelle, qui possède toutes les capacités d?un tableau standard : // import de la classe MonTableau import org.bytearray.outils.MonTableau; // création d'un tableau de nombres var premierTableau:MonTableau = new MonTableau (58, 48, 10); // affiche : 48 trace( premierTableau[1] ); // ajout d'une valeur premierTableau.push ( 25 ); // affiche : 4 trace( premierTableau.length ); // affiche : 25 trace( premierTableau.pop() ) ; // affiche : 3 trace( premierTableau.length ); Pour l?instant, l?utilisation de la sous-classe MonTableau n?est pas vraiment justifiée, celle-ci ne possède aucune fonctionnalité en plus de la classe Array. Afin de copier un tableau, nous pourrions être tentés d?écrire le code suivant : // création d'un premier tableau Chapitre 9 ? Etendre les classes natives ? version 0.1.2 7 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org var monTableau:Array = new Array (58, 48, 10); // création d'une copie ? var maCopie:Array = monTableau; Cette erreur fait référence à la notion de variables composites et primitives que nous avons traité lors du chapitre 2 intitulé Langage et API du lecteur Flash. Souvenez-vous, lors de la copie de données composites, nous copions les variables par référence et non par valeur. Afin de créer une vraie copie de tableau nous définissons une nouvelle méthode copie au sein de la classe MonTableau : package org.bytearray.outils { dynamic public class MonTableau extends Array { public function MonTableau ( ...rest ) { splice.apply(this, [0, 0].concat(rest)); } public function copie ( ):MonTableau { var maCopie:MonTableau = new MonTableau(); var lng:int = length; for ( var i:int = 0; i< lng; i++ ) maCopie[i] = this[i]; return maCopie; } } } En testant le code suivant, nous voyons qu?une réelle copie du tableau est retournée par la méthode copie : // import de la classe MonTableau import org.bytearray.outils.MonTableau; // création d'un tableau de nombres var tableau:MonTableau = new MonTableau (58, 48, 10); // création d'une copie Chapitre 9 ? Etendre les classes natives ? version 0.1.2 8 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org var copieTableau:MonTableau = tableau.copie(); // modification d'une valeur copieTableau[0] = 100; // affiche : 58,48,10 100,48,10 trace ( tableau, copieTableau ); Nous aurions pu utiliser la méthode Array.slice mais celle-ci nous aurait renvoyé un tableau de type Array et non pas une nouvelle instance de MonTableau. En modifiant une valeur du tableau retourné nous voyons que les deux tableaux sont bien distincts. Bien entendu, cette méthode ne fonctionne que pour la copie de tableaux contenant des données de types primitifs. Si nous souhaitons copier des tableaux contenant des données de types composites nous utiliserons la méthode writeObject de la classe flash.utils.ByteArray. Nous reviendrons sur cette fonctionnalité au cours du chapitre 20 intitulé ByteArray. Nous allons maintenant ajouter une nouvelle méthode melange permettant de mélanger les données du tableau : public function melange ( ):void { var i:int = length; var aleatoire:int; var actuel:*; while ( i-- ) { aleatoire = Math.floor ( Math.random()*length ); actuel = this[i]; this[i] = this[aleatoire]; this[aleatoire] = actuel; } } Dans le code suivant nous mélangeons différentes valeurs, nous pourrions utiliser cette méthode dans un jeu. Nous pourrions imaginer une série de questions stockées dans un tableau, à chaque lancement le tableau est mélangé afin que les joueurs n?aient pas les questions dans le même ordre : // import de la classe MonTableau import org.bytearray.outils.MonTableau; // création d'un tableau de nombres var tableau:MonTableau = new MonTableau(58, 48, 10); // mélange les valeurs Chapitre 9 ? Etendre les classes natives ? version 0.1.2 9 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org tableau.melange(); // affiche : 85,12,58 trace( tableau ); La méthode egal nous permet de tester si deux tableaux contiennent les mêmes valeurs : public function egal ( pTableau:Array ):Boolean { if ( this == pTableau ) return true; var i:int = this.length; if ( i != pTableau.length ) return false; while( i-- ) { if ( this[i] != pTableau[i] ) return false; } return true; } Dans le code suivant nous comparons deux tableaux : // import de la classe MonTableau import org.bytearray.outils.MonTableau; // création d'un tableau de nombres var premierTableau:MonTableau = new MonTableau(12, 58, 85); // création d'un autre tableau de nombres var secondTableau:MonTableau = new MonTableau(12, 58, 85); // affiche : true trace( premierTableau.egal ( secondTableau ) ); Lorsque le tableau passé contient les mêmes valeurs, la méthode egal renvoie true. A l?inverse si nous modifions les données du premier tableau, la méthode egal renvoie false : // import de la classe MonTableau import org.bytearray.outils.MonTableau; // création d'un tableau de nombres var premierTableau:MonTableau = new MonTableau(100, 58, 85); // création d'un autre tableau de nombres var secondTableau:MonTableau = new MonTableau(12, 58, 85); // affiche : false trace( premierTableau.egal ( secondTableau ) ); Dans cet exemple nous ne prenons pas en charge les tableaux à plusieurs dimensions, différentes approches pourraient être utilisées, à vous de jouer ! Il serait pratique d?avoir une méthode qui se chargerait de calculer la moyenne des valeurs contenues dans le tableau. Avant de démarrer le Chapitre 9 ? Etendre les classes natives ? version 0.1.2 10 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org calcul nous devons nous assurer que le tableau ne contienne que des nombres. Pour cela nous utilisons la nouvelle méthode every de la classe Array : public function moyenne ( ):Number { if ( ! every ( filtre ) ) throw new TypeError ("Le tableau actuel ne contient pas que des nombres"); var i:int = length; var somme:Number = 0; while ( i-- ) somme += this[i]; return somme/length; } private function filtre ( pElement:*, pIndex:int, pTableau:Array ):Boolean { return pElement is Number; } Lorsque la méthode moyenne est exécutée, celle-ci vérifie dans un premier temps si la totalité des données du tableau sont bien des nombres. Pour cela la méthode filtre est exécutée sur chaque élément du tableau, et renvoie true tant que l?élément parcouru est un nombre. Si ce n?est pas le cas, celle-ci renvoie false et nous levons une erreur de type TypeError. Si aucune erreur n?est levée, nous pouvons entamer le calcul de la moyenne. Dans le code suivant, le calcul est possible : // import de la classe MonTableau import org.bytearray.outils.MonTableau; // création d'un tableau de nombres var premierTableau:MonTableau = new MonTableau(100, 58, 85); // affiche : 81 trace( premierTableau.moyenne() ); A l?inverse, si une valeur du tableau n?est pas un nombre : // import de la classe MonTableau import org.bytearray.outils.MonTableau; // création d'un tableau de nombres var premierTableau:MonTableau = new MonTableau(this, 58, false); trace( premierTableau.moyenne() ); Chapitre 9 ? Etendre les classes natives ? version 0.1.2 11 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org Une erreur à l?exécution est levée : TypeError: Le tableau actuel ne contient pas que des nombres Afin de gérer l?erreur, nous pouvons placer l?appel de la méthode moyenne au sein d?un bloc try catch : // import de la classe MonTableau import org.bytearray.outils.MonTableau; // création d'un tableau de nombres var premierTableau:MonTableau = new MonTableau(this, 58, false); try { trace( premierTableau.moyenne() ); } catch ( pError:Error ) { trace("une erreur de calcul est survenue !"); } Nous pourrions ajouter autant de méthodes que nous souhaitons au sein de la classe MonTableau, libre à vous d?ajouter les fonctionnalités dont vous avez besoin. Cela nous permet de substituer la classe MonTableau à la classe Array afin de toujours avoir à disposition ces fonctionnalités. A retenir ? Toutes les classes natives ne sont pas sous-classables. ? La composition peut être utilisée afin d?augmenter les capacités d?une classe qui n?est pas sous-classable. ? Etendre une classe native est un moyen élégant d?étendre les capacités d?ActionScript 3 ou du lecteur. Etendre les classes graphiques L?extension de classes natives prend tout son sens dans le cas de sous- classes graphiques. Nous avons développé une application de dessin au cours du chapitre 7 intitulé Intéractivité, nous allons à présent l?enrichir en ajoutant un objet graphique tel un stylo. Le graphisme a été réalisé sous 3D Studio Max puis ensuite exporté en image pour être utilisé dans Flash. Dans un nouveau document Flash CS3 nous importons le graphique puis nous le transformons en symbole clip. Chapitre 9 ? Etendre les classes natives ? version 0.1.2 12 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 9-1. Graphique du stylo converti en symbole clip. Attention à bien modifier le point d?enregistrement, de manière à ce que la mine du stylo soit en coordonnée 0,0. Ce clip va nous servir de graphisme afin de représenter le stylo de l?application. Nous devons pour cela le rendre disponible par programmation. Au sein du panneau Propriétés de liaison nous cochons la case Exporter pour ActionScript et renseignons Stylo comme nom de classe, nous laissons flash.display.MovieClip comme classe de base. La classe Stylo est donc une sous-classe de MovieClip : Figure 9-2. Panneau propriétés de liaison. Chapitre 9 ? Etendre les classes natives ? version 0.1.2 13 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org Lorsque nous cliquons sur le bouton OK, Flash tente de trouver une classe du même nom qui aurait pu être définie. Si il n?en trouve aucune, Flash affiche le message illustré en figure 9-3 : Figure 9-3. Génération automatique de classe. Comme nous l?avons vu au cours du chapitre 5 intitulé Les symboles, Flash génère automatiquement une classe interne utilisée pour instancier le symbole. Dans notre cas, une classe Stylo est générée par Flash, afin de pouvoir instancier notre stylo puis l?afficher de la manière suivante : // instanciation du stylo var monStylo:Stylo = new Stylo(); // ajout à la liste d'affichage addChild ( monStylo ); Flash génère automatiquement une classe Stylo qui hérite de la classe MovieClip, rappelez-vous que cette classe est inaccessible. Si nous avions accès à celle-ci nous pourrions lire le code suivant : package { import flash.display.MovieClip; public class Stylo extends MovieClip { public function Stylo () { } } Chapitre 9 ? Etendre les classes natives ? version 0.1.2 14 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org } La classe Stylo possède donc toutes les capacités d?un MovieClip : // instanciation du stylo var monStylo:Stylo = new Stylo(); // ajout à la liste d'affichage addChild ( monStylo ); // la classe stylo possède toutes les capacités d'un clip monStylo.stop(); monStylo.play(); monStylo.gotoAndStop (2); monStylo.prevFrame (); Cette génération automatique de classe s?avère très pratique lorsque nous ne souhaitons pas définir de classe manuellement pour chaque objet, Flash s?en charge et cela nous permet de gagner un temps précieux. En revanche Flash nous laisse aussi la possibilité de définir manuellement les sous classe graphiques, et d?y ajouter tout ce que nous souhaitons. A coté de notre document Flash CS3 nous créons une classe Stylo héritant de MovieClip : package { import flash.display.MovieClip; public class Stylo extends MovieClip { public function Stylo () { trace( this ); } } } Nous sauvons la classe sous le nom Stylo.as, attention, le nom de la classe doit être le même que le fichier .as. L?organisation de nos fichiers de travail doit être la suivante : Chapitre 9 ? Etendre les classes natives ? version 0.1.2 15 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 9-4. Organisation des fichiers. Flash va désormais utiliser notre définition de classe et non celle générée automatiquement. Pour s?en assurer, nous utilisons le panneau Propriétés de liaison. A droite du champ Classe sont situées deux icônes. En cliquant sur la première, nous pouvons valider la définition de la classe afin de vérifier que Flash utilise bien notre définition de classe. La figure 9-5 illustre les deux icônes : Figure 9-5. Organisation des fichiers. Une fois la définition de classe validée, un message nous indiquant que la classe a bien été détectée s?affiche. Si nous cliquons sur l?icône d?édition représentant un stylo située à droite de l?icône de validation, la classe s?ouvre au sein de Flash afin d?être éditée : Chapitre 9 ? Etendre les classes natives ? version 0.1.2 16 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 9-6. Edition de la classe au sein de Flash CS3. Le symbole est correctement lié à notre classe, nous pouvons dès à présent l?instancier : // affiche : [object Stylo] var monStylo:Stylo = new Stylo(); // ajout à la liste d'affichage addChild ( monStylo ); // positionnement en x et y monStylo.x = 250; monStylo.y = 200; Lorsque le stylo est créé le constructeur est déclenché, l?instruction trace que nous avions placé affiche : [object Stylo]. Notre symbole est affiché comme l?illustre la figure 9-7 : Chapitre 9 ? Etendre les classes natives ? version 0.1.2 17 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 9-7. Symbole Stylo affiché. Nous n?avons défini pour le moment aucune nouvelle fonctionnalité au sein de la classe Stylo, celle-ci étend simplement la classe flash.display.MovieClip. Toutes les fonctionnalités que nous ajouterons au sein de la classe Stylo seront automatiquement disponibles au sein du symbole, celui-ci est désormais lié à la classe. En ajoutant une méthode test au sein de la classe Stylo : package { import flash.display.MovieClip; public class Stylo extends MovieClip { public function Stylo () { trace( this ); } public function test ( ):void Chapitre 9 ? Etendre les classes natives ? version 0.1.2 18 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org { trace("ma position dans l'axe des x est de : " + x ); trace("ma position dans l'axe des y est de : " + y ); } } } Celle-ci est automatiquement disponible auprès de l?instance : // affiche : [object Stylo] var monStylo:Stylo = new Stylo(); // ajout à la liste d'affichage addChild ( monStylo ); // positionnement en x et y monStylo.x = 250; monStylo.y = 200; /* affiche : ma position dans l'axe des x est de : 250 ma position dans l'axe des y est de : 200 */ monStylo.test(); La première chose que notre stylo doit savoir faire est de suivre la souris. Nous n?allons pas utiliser l?instruction startDrag car nous verrons que nous devons travailler sur le mouvement du stylo, l?instruction startDrag ne nous le permettrait pas. Pour cela nous allons utiliser l?événement MouseEvent.MOUSE_MOVE. Notre classe est une sous-classe de MovieClip et possède donc tous les événements interactifs nécessaires dont nous avons besoin, au sein du constructeur nous écoutons l?événement MouseEvent.MOUSE_MOVE : package { import flash.display.MovieClip; import flash.events.MouseEvent; public class Stylo extends MovieClip { public function Stylo () { trace( this ); addEventListener ( MouseEvent.MOUSE_MOVE, bougeSouris ); Chapitre 9 ? Etendre les classes natives ? version 0.1.2 19 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org } private function bougeSouris ( pEvt:MouseEvent ):void { trace( pEvt ); } } } Dans le code précédent la méthode bougeSouris est à l?écoute de l?événement MouseEvent.MOUSE_MOVE. Pourquoi préférons-nous l?évenement MouseEvent.MOUSE_MOVE à l?évenement Event.ENTER_FRAME ? Ce dernier se déclenche en continu indépendamment du mouvement de la souris. Ainsi, même si le stylo est immobile l?événement Event.ENTER_FRAME est diffusé. A terme, cela pourrait ralentir les performances de notre application. Souvenez-vous que l?événement MouseEvent.MOUSE_MOVE n?est plus global comme en ActionScript 1 et 2, et n?est diffusé que lors du survol du stylo. Si nous avons besoin d?écouter le mouvement de la souris sur toute la scène nous devons accéder à l?objet Stage. A retenir ? Il est possible de lier un symbole existant à une sous-classe graphique définie manuellement. ? Le symbole hérite de toutes les fonctionnalités de la sous-classe. Accéder à l?objet Stage de manière sécurisée Comme nous l?avons vu lors du chapitre 7 intitulé Interactivité, seul l?objet Stage permet une écoute globale de la souris ou du clavier. Nous devons donc modifier notre code afin d?écouter l?événement MouseEvent.MOUSE_MOVE auprès de l?objet Stage : package { import flash.display.MovieClip; import flash.events.MouseEvent; public class Stylo extends MovieClip { Chapitre 9 ? Etendre les classes natives ? version 0.1.2 20 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org public function Stylo () { trace( this ); stage.addEventListener ( MouseEvent.MOUSE_MOVE, bougeSouris ); } private function bougeSouris ( pEvt:MouseEvent ):void { trace( pEvt ); } } } En testant le code précédent, nous obtenons l?erreur suivante à l?exécution : TypeError: Error #1009: Il est impossible d'accéder à la propriété ou à la méthode d'une référence d'objet nul. Souvenez-vous, lors du chapitre 4 intitulé La liste d?affichage nous avons vu que la propriété stage propre à tout objet de type flash.display.DisplayObject renvoyait null tant que l?objet graphique n?était pas ajouté à la liste d?affichage. Ainsi, lorsque nous créons le symbole : var monStylo:Stylo = new Stylo(); Le constructeur est déclenché et nous tentons alors d?accéder à l?objet Stage. A ce moment là, notre symbole n?est pas encore présent au sein de la liste d?affichage, la propriété stage renvoie donc null et l?appel à la méthode addEventListener échoue. Comment allons-nous procéder ? Lors du chapitre 4 intitulé La liste d?affichage nous avons découvert deux événements importants liés à l?activation et à la désactivation des objets graphiques. Voici un rappel de ces deux événements fondamentaux : ? Event.ADDED_TO_STAGE : cet événement est diffusé lorsque l?objet graphique est placé au sein d?un DisplayObjectContainer présent au sein de la liste d?affichage. ? Event.REMOVED_FROM_STAGE : cet événement est diffusé lorsque l?objet graphique est supprimé de la liste d?affichage. Chapitre 9 ? Etendre les classes natives ? version 0.1.2 21 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org Nous devons attendre que le symbole soit ajouté à la liste d?affichage pour pouvoir accéder à l?objet Stage. Le symbole va se souscrire lui- même auprès de l?événement Event.ADDED_TO_STAGE qu?il diffusera lorsqu?il sera ajouté à la liste d?affichage. Cela peut paraître étrange, mais dans ce cas l?objet s?écoute lui-même. Nous modifions la classe Stylo afin d?intégrer ce mécanisme : package { import flash.display.MovieClip; import flash.events.Event; public class Stylo extends MovieClip { public function Stylo () { trace( this ); // le stylo écoute l'événement Event.ADDED_TO_STAGE, diffusé lorsque celui-ci est ajouté à la liste d'affichage addEventListener ( Event.ADDED_TO_STAGE, activation ); } private function activation ( pEvt:Event ):void { trace( pEvt ); } } } Lors d?instanciation du symbole Stylo, le constructeur est déclenché : // affiche : [object Stylo] var monStylo:Stylo = new Stylo(); Lorsque l?instance est ajoutée à la liste d?affichage, l?événement Event.ADDED_TO_STAGE est diffusé : // affiche : [object Stylo] var monStylo:Stylo = new Stylo(); // ajout à la liste d'affichage // affiche : [Event type="addedToStage" bubbles=false cancelable=false eventPhase=2] addChild ( monStylo ); Chapitre 9 ? Etendre les classes natives ? version 0.1.2 22 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org Nous définissons la méthode écouteur activation comme privée car nous n?y accèderons pas depuis l?extérieur. Gardez bien en tête de rendre privées toutes les méthodes et propriétés qui ne seront pas utilisées depuis l?extérieur de la classe. L?événement Event.ADDED_TO_STAGE est diffusé, nous remarquons au passage que nous écoutons la phase cible et qu?il s?agit d?un événement qui ne participe pas à la phase de remontée. Lorsque la méthode activation est déclenchée nous pouvons cibler en toute sécurité la propriété stage et ainsi écouter l?événement MouseEvent.MOUSE_MOVE : package { import flash.display.MovieClip; import flash.events.MouseEvent; import flash.events.Event; public class Stylo extends MovieClip { public function Stylo () { // le stylo écoute l'événement Event.ADDED_TO_STAGE // diffusé lorsque celui-ci est ajouté à la liste d'affichage addEventListener ( Event.ADDED_TO_STAGE, activation ); } private function activation ( pEvt:Event ):void { // écoute de l'événement MouseEvent.MOUSE_MOVE stage.addEventListener ( MouseEvent.MOUSE_MOVE, bougeSouris ); } private function bougeSouris ( pEvt:MouseEvent ):void { trace( pEvt ); } } } Si nous testons le code précédent, nous remarquons que la méthode bougeSouris est bien déclenchée lorsque la souris est déplacée sur la totalité de la scène. Chapitre 9 ? Etendre les classes natives ? version 0.1.2 23 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org Ajouter des fonctionnalités Nous allons à présent nous intéresser au mouvement du stylo en récupérant les coordonnées de la souris afin de le positionner. Nous définissons deux propriétés positionX et positionY : // position en cours de la souris private var positionX:Number; private var positionY:Number; Celles-ci permettent de stocker la position de la souris : private function bougeSouris ( pEvt:MouseEvent ):void { // récupération des coordonnées de la souris en x et y positionX = pEvt.stageX; positionY = pEvt.stageY; // affectation de la position x = positionX; y = positionY; } Le stylo suit désormais la souris comme l?illustre la figure 9-8 : Figure 9-8. Stylo attaché au curseur. En testant l?application nous remarquons que le mouvement n?est pas totalement fluide, nous allons utiliser la méthode Chapitre 9 ? Etendre les classes natives ? version 0.1.2 24 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org updateAfterEvent que nous avons étudié au cours du chapitre 7 intitulé Interactivité. Souvenez-vous, cette méthode de la classe MouseEvent nous permet de forcer le rafraîchissement du lecteur : private function bougeSouris ( pEvt:MouseEvent ):void { // récupération des coordonnées de la souris en x et y positionX = pEvt.stageX; positionY = pEvt.stageY; // affectation de la position x = positionX; y = positionY; // force le rafraîchissement pEvt.updateAfterEvent(); } Le mouvement est maintenant fluide mais le curseur de la souris demeure affiché, nous le masquons à l?aide de la méthode statique hide de la classe Mouse lors de l?activation de l?objet graphique. Veillez à ne pas placer cet appel en continu au sein de la méthode bougeSouris cela serait redondant. Nous importons la classe Mouse : import flash.ui.Mouse; Puis nous modifions la méthode activation : private function activation ( pEvt:Event ):void { // cache le curseur Mouse.hide(); // écoute de l'événement MouseEvent.MOUSE_MOVE stage.addEventListener ( MouseEvent.MOUSE_MOVE, bougeSouris ); } A ce stade, nous avons étendu les capacités de la classe MovieClip, les symboles liés à la classe Stylo peuvent désormais suivrent la souris. Nous pouvons à tout moment lier notre classe Stylo à n?importe quel autre symbole celui-ci bénéficiera aussitôt de toutes les fonctionnalités définies par celle-ci. Nous avons pourtant oublié un élément essentiel, voyez-vous de quoi il s?agit ? Chapitre 9 ? Etendre les classes natives ? version 0.1.2 25 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org Nous devons gérer la désactivation de l?objet graphique afin de libérer les ressources. Si nous supprimons le stylo de la liste d?affichage, l?événement MouseEvent.MOUSE_MOVE est toujours diffusé, le curseur souris demeure masqué : var monStylo:Stylo = new Stylo(); // ajout à la liste d'affichage // affiche : [Event type="addedToStage" bubbles=false cancelable=false eventPhase=2] addChild ( monStylo ); // suppression du stylo, celui ci n'intègre aucune logique de désactivation removeChild ( monStylo ); Pour gérer proprement cela nous écoutons l?événement Event.REMOVED_FROM_STAGE : public function Stylo () { // le stylo écoute l'événement Event.ADDED_TO_STAGE // diffusé lorsque celui-ci est ajouté à la liste d'affichage addEventListener ( Event.ADDED_TO_STAGE, activation ); // le stylo écoute l'événement Event.REMOVED_FROM_STAGE // diffusé lorsque celui-ci est supprimé de la liste d'affichage addEventListener ( Event.REMOVED_FROM_STAGE, desactivation ); } Puis nous ajoutons une méthode écouteur desactivation afin d?intégrer la logique de désactivation nécessaire : private function desactivation ( pEvt:Event ):void { // affiche le curseur Mouse.show(); // arrête l'écoute de l'événement MouseEvent.MOUSE_MOVE stage.removeEventListener ( MouseEvent.MOUSE_MOVE, bougeSouris ); } Il serait intéressant d?ajouter un effet de rotation au stylo lorsque celui-ci arrive en haut de la scène afin de simuler une rotation du poignet. La figure 9-9 illustre l?idée : Chapitre 9 ? Etendre les classes natives ? version 0.1.2 26 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 9-9. Rotation du stylo. En intégrant une simple condition nous allons pouvoir donner plus de réalisme au mouvement du stylo. Nous allons dans un premier temps définir la position dans l?axe des y à partir de laquelle le stylo commence à s?incliner, pour cela nous définissons une propriété constante car celle-ci ne changera pas à l?exécution : // limite pour l'axe des y private static const LIMIT_Y:int = 140; Puis nous intégrons la condition au sein de la méthode bougeSouris : private function bougeSouris ( pEvt:MouseEvent ):void { // récupération des coordonnées de la souris en x et y positionX = pEvt.stageX; positionY = pEvt.stageY; // affectation de la position x = positionX; y = positionY; // si le stylo passe dans la zone supérieure alors nous inclinons le stylo if ( positionY < Stylo.LIMIT_Y ) { rotation = ( Stylo.LIMIT_Y - positionY ); Chapitre 9 ? Etendre les classes natives ? version 0.1.2 27 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org } // force le rafraîchissement pEvt.updateAfterEvent(); } A ce stade, voici le code complet de notre classe Stylo : package { import flash.display.MovieClip; import flash.events.MouseEvent; import flash.events.Event; import flash.ui.Mouse; public class Stylo extends MovieClip { // limite pour l'axe des y private static const LIMIT_Y:int = 140; // position en cours de la souris private var positionX:Number; private var positionY:Number; public function Stylo () { // le stylo écoute l'événement Event.ADDED_TO_STAGE // diffusé lorsque celui-ci est ajouté à la liste d'affichage addEventListener ( Event.ADDED_TO_STAGE, activation ); // le stylo écoute l'événement Event.REMOVED_FROM_STAGE // diffusé lorsque celui-ci est supprimé de la liste d'affichage addEventListener ( Event.REMOVED_FROM_STAGE, desactivation ); } private function activation ( pEvt:Event ):void { // cache le curseur Mouse.hide(); // écoute de l'événement MouseEvent.MOUSE_MOVE stage.addEventListener ( MouseEvent.MOUSE_MOVE, bougeSouris ); } private function desactivation ( pEvt:Event ):void { // affiche le curseur Mouse.show(); Chapitre 9 ? Etendre les classes natives ? version 0.1.2 28 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org // arrête l'écoute de l'événement MouseEvent.MOUSE_MOVE stage.removeEventListener ( MouseEvent.MOUSE_MOVE, bougeSouris ); } private function bougeSouris ( pEvt:MouseEvent ):void { // récupération des coordonnées de la souris en x et y positionX = pEvt.stageX; positionY = pEvt.stageY; // affectation de la position x = positionX; y = positionY; // si le stylo passe dans la zone supérieure alors nous inclinons le stylo if ( positionY < Stylo.LIMIT_Y ) { rotation = ( Stylo.LIMIT_Y - positionY ); } // force le rafraîchissement pEvt.updateAfterEvent(); } } } Si nous testons l?application, lorsque nous arrivons en zone supérieure le stylo s?incline comme l?illustre la figure 9-10 : Chapitre 9 ? Etendre les classes natives ? version 0.1.2 29 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 9-10. Inclinaison du stylo. Afin de rendre plus naturel le mouvement du stylo, nous ajoutons une formule d?inertie : private function bougeSouris ( pEvt:MouseEvent ):void { // récupération des coordonnées de la souris en x et y positionX = pEvt.stageX; positionY = pEvt.stageY; // affectation de la position x = positionX; y = positionY; // si le stylo passe dans la zone supérieure alors nous inclinons le stylo if ( positionY < Stylo.LIMIT_Y ) { rotation -= ( rotation - ( Stylo.LIMIT_Y - positionY ) ) *.2; // sinon, le stylo reprend son inclinaison d'origine } else rotation -= ( rotation - 0 ) *.2; // force le rafraîchissement pEvt.updateAfterEvent(); } Chapitre 9 ? Etendre les classes natives ? version 0.1.2 30 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org Il est temps d?ajouter à présent la notion de dessin, cette partie a déjà été abordée lors du chapitre 7, nous allons donc réutiliser ce code dans notre application. Si nous pensons en termes de séparation des tâches, il paraît logique qu?un stylo se charge de son mouvement et du dessin, et non pas à la création de la toile où dessiner. Nous allons donc définir une méthode affecteToile qui aura pour mission d?indiquer au stylo dans quel conteneur dessiner. Nous définissons dans la classe une propriété permettant de référencer le conteneur : // stocke une référence au conteneur de dessin private var conteneur:DisplayObjectContainer; Puis nous importons la classe flash.display.DisplayObjectContainer : import flash.display.DisplayObjectContainer; Et ajoutons la méthode affecteToile : // méthode permettant de spécifier le conteneur du dessin public function affecteToile ( pToile:DisplayObjectContainer ):void { conteneur = pToile; } Tout type de conteneur peut être passé, à condition que celui-ci soit de type DisplayObjectContainer : // création du conteneur de tracés vectoriels var toile:Sprite = new Sprite(); // ajout du conteneur à la liste d'affichage addChild ( toile ); // création du symbole var monStylo:Stylo = new Stylo(); // affectation du conteneur de tracés monStylo.affecteToile ( toile ); // ajout du symbole à la liste d'affichage addChild ( monStylo ); // positionnement en x et y monStylo.x = 250; monStylo.y = 200; Ainsi, différents types d?objets graphiques peuvent servir de toile. Souvenez-vous grâce à l?héritage un sous type peut être passé partout où un super-type est attendu. Chapitre 9 ? Etendre les classes natives ? version 0.1.2 31 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org En plus de réutiliser les fonctionnalités de dessin que nous avions développé durant le chapitre 7 intitulé Interactivité nous allons aussi réutiliser la notion d?historique combinés aux raccourcis clavier : CTRL+Z et CTRL+Y. Nous définissons trois nouvelles propriétés permettant de stocker les formes créées et supprimées, puis le tracé en cours : // tableaux référençant les formes tracées et supprimées private var tableauTraces:Array = new Array(); private var tableauAncienTraces:Array = new Array(); // référence le tracé en cours private var monNouveauTrace:Shape; Attention, la classe flash.display.Shape doit être importée : import flash.display.Shape; Au sein de la méthode activation, nous devons tout d?abord écouter le clic souris, afin de savoir quand est-ce que l?utilisateur souhaite commencer à dessiner : private function activation ( pEvt:Event ):void { // cache le curseur Mouse.hide(); // écoute des différents événements stage.addEventListener ( MouseEvent.MOUSE_MOVE, bougeSouris ); stage.addEventListener ( MouseEvent.MOUSE_DOWN, clicSouris ); } Nous ajoutons la méthode écouteur clicSouris, qui se charge de créer les objets et d?initialiser le style du tracé : private function clicSouris ( pEvt:MouseEvent ):void { if ( conteneur == null ) throw new Error ( "Veuillez appeler au préalable la méthode affecteToile()" ); // récupération des coordonnées de la souris en x et y positionX = pEvt.stageX; positionY = pEvt.stageY; // un nouvel objet Shape est créé pour chaque tracé monNouveauTrace = new Shape(); // nous ajoutons le conteneur de tracé au conteneur principal conteneur.addChild ( monNouveauTrace ); // puis nous référençons le tracé au sein du tableau // référençant les tracés affichés tableauTraces.push ( monNouveauTrace ); Chapitre 9 ? Etendre les classes natives ? version 0.1.2 32 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org // nous définissons un style de tracé monNouveauTrace.graphics.lineStyle ( 1, 0x990000, 1 ); // la mine est déplacée à cette position // pour commencer à dessiner à partir de cette position monNouveauTrace.graphics.moveTo ( positionX, positionY ); // si un nouveau tracé intervient alors que nous sommes // repartis en arrière nous repartons de cet état if ( tableauAncienTraces.length ) tableauAncienTraces = new Array; // écoute du mouvement de la souris stage.addEventListener ( MouseEvent.MOUSE_MOVE, dessine ); } Puis nous définissons la méthode dessine afin de gérer le tracé : private function dessine ( pEvt:MouseEvent ):void { if ( monNouveauTrace != null ) { // la mine est déplaçée à cette position // pour commencer à dessiner à partir de cette position monNouveauTrace.graphics.lineTo ( positionX, positionY ); } } Afin d?arrêter de dessiner, nous écoutons le relâchement de la souris grâce à l?événement MouseEvent.MOUSE_UP : private function activation ( pEvt:Event ):void { // cache le curseur Mouse.hide(); // écoute des différents événements stage.addEventListener ( MouseEvent.MOUSE_MOVE, bougeSouris ); stage.addEventListener ( MouseEvent.MOUSE_DOWN, clicSouris ); stage.addEventListener ( MouseEvent.MOUSE_UP, relacheSouris ); } La méthode écouteur relacheSouris est déclenchée lors du relâchement de la souris et interrompt l?écoute de l?événement MouseEvent.MOUSE_MOVE : private function relacheSouris ( pEvt:MouseEvent ):void { stage.removeEventListener ( MouseEvent.MOUSE_MOVE, dessine ); Chapitre 9 ? Etendre les classes natives ? version 0.1.2 33 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org } Si nous testons notre application, tout fonctionne correctement, nous pouvons à présent dessiner, lorsque la souris est relâchée, le tracé est stoppé. Figure 9-11. Application de dessin. Notre application est bientôt terminée, nous devons ajouter la notion d?historique pour cela nous importons la classe KeyboardEvent : import flash.events.KeyboardEvent; Puis nous ajoutons l?écoute du clavier au sein de la méthode activation : private function activation ( pEvt:Event ):void { // cache le curseur Mouse.hide(); // écoute de l'événement MouseEvent.MOUSE_MOVE stage.addEventListener ( MouseEvent.MOUSE_MOVE, bougeSouris ); stage.addEventListener ( MouseEvent.MOUSE_DOWN, clicSouris ); stage.addEventListener ( MouseEvent.MOUSE_UP, relacheSouris ); stage.addEventListener ( KeyboardEvent.KEY_DOWN, ecouteClavier ); } Chapitre 9 ? Etendre les classes natives ? version 0.1.2 34 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org La méthode écouteur ecouteClavier se charge de gérer l?historique, nous importons la classe Keyboard : import flash.ui.Keyboard; Et définissons deux propriétés constantes stockant le code de chaque touche : // code des touches Y et Z private static const codeToucheY:int = 89; private static const codeToucheZ:int = 90; Puis nous ajoutons le code déjà développé durant le chapitre 7 pour la précédente application de dessin : private function ecouteClavier ( pEvt:KeyboardEvent ):void { // si la barre espace est enfoncée if ( pEvt.keyCode == Keyboard.SPACE ) { // nombre d'objets Shape contenant des tracés var lng:int = tableauTraces.length; // suppression des tracés de la liste d'affichage while ( lng-- ) conteneur.removeChild ( tableauTraces[lng] ); // les tableaux d'historiques sont reinitialisés // les références supprimées tableauTraces = new Array(); tableauAncienTraces = new Array(); monNouveauTrace = null; } if ( pEvt.ctrlKey ) { // si retour en arrière (CTRL+Z) if( pEvt.keyCode == Stylo.codeToucheZ && tableauTraces.length ) { // nous supprimons le dernier tracé var aSupprimer:Shape = tableauTraces.pop(); // nous stockons chaque tracé supprimé // dans le tableau spécifique tableauAncienTraces.push ( aSupprimer ); // nous supprimons le tracé de la liste d'affichage conteneur.removeChild( aSupprimer ); // si retour en avant (CTRL+Y) } else if ( pEvt.keyCode == Stylo.codeToucheY && tableauAncienTraces.length ) Chapitre 9 ? Etendre les classes natives ? version 0.1.2 35 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org { // nous récupérons le dernier tracé ajouté var aAfficher:Shape = tableauAncienTraces.pop(); // nous le replaçons dans le tableau de // tracés à l'affichage tableauTraces.push ( aAfficher ); // puis nous l'affichons conteneur.addChild ( aAfficher ); } } } Nous obtenons un symbole stylo intelligent doté de différentes fonctionnalités que nous avons ajoutées. Un des principaux avantages des sous-classes graphiques réside dans leur facilité de réutilisation. Nous pouvons à tout moment lier un autre symbole à notre sous-classe graphique, ce dernier héritera automatiquement des fonctionnalités de la classe Stylo et deviendra opérationnel. Avant cela, nous allons ajouter une autre fonctionnalité liée à la molette souris. Il serait élégant de pouvoir choisir la couleur du tracé à travers l?utilisation de la molette souris. Lorsque l?utilisateur s?en servira, la couleur du tracé et du stylo sera modifiée. N?oublions pas qu?au sein de la sous-classe nous sommes sur le scénario du symbole. Nous modifions le symbole stylo en ajoutant plusieurs images clés : Chapitre 9 ? Etendre les classes natives ? version 0.1.2 36 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 9-12. Ajout des différentes couleurs. Lorsque l?utilisateur fait usage de la molette, nous déplaçons la tête de lecture du scénario du symbole. Nous ajoutons deux nouvelles propriétés qui nous permettront de positionner la tête de lecture lorsque la souris sera glissée : // position de la tête de lecture private var image:int; private var index:int; Nous initialisons au sein du constructeur la propriété index qui sera plus tard incrémentée, nous l?initialisons donc à 0 : public function Stylo () { // le stylo écoute l'événement Event.ADDED_TO_STAGE // diffusé lorsque celui-ci est ajouté à la liste d'affichage addEventListener ( Event.ADDED_TO_STAGE, activation ); // le stylo écoute l'événement Event.REMOVED_FROM_STAGE // diffusé lorsque celui-ci est supprimé de la liste d'affichage addEventListener ( Event.REMOVED_FROM_STAGE, desactivation ); Chapitre 9 ? Etendre les classes natives ? version 0.1.2 37 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org // initialisation des propriétés utilisées pour // le changement de couleurs image = 0; index = 1; } Au sein de la méthode activation nous ajoutons un écouteur de l?événement MouseEvent.MOUSE_WHEEL auprès de l?objet Stage : private function activation ( pEvt:Event ):void { // cache le curseur Mouse.hide(); // écoute des différents événements stage.addEventListener ( MouseEvent.MOUSE_MOVE, bougeSouris ); stage.addEventListener ( MouseEvent.MOUSE_DOWN, clicSouris ); stage.addEventListener ( MouseEvent.MOUSE_UP, lacheSouris ); stage.addEventListener ( KeyboardEvent.KEY_DOWN, ecouteClavier ); stage.addEventListener ( MouseEvent.MOUSE_WHEEL, moletteSouris ); } La méthode écouteur moletteSouris se charge de déplacer la tête de lecture sous forme de boucle, à l?aide de l?opérateur modulo %, la propriété index stocke l?index de la couleur sélectionnée : // déplace la tête de lecture lors de l?utilisation de la molette private function moletteSouris ( pEvt:MouseEvent ):void { gotoAndStop ( index = (++image%totalFrames)+1 ); } Si nous testons l?application, lorsque la molette de la souris est utilisée, le stylo change de couleur. Grâce au modulo, les couleurs défilent en boucle. Il reste cependant à changer la couleur du tracé correspondant à la couleur du stylo choisie par l?utilisateur. Pour cela nous allons stocker toutes les couleurs disponibles dans un tableau au sein d?une propriété statique. Pourquoi utiliser ici une propriété statique ? Car les couleurs sont globales à toutes les occurrences du stylo qui pourraient être créées, ayant un sens global nous créons donc un tableau au sein d?une propriété couleurs statique : // tableau contenant les couleurs disponibles private static var couleurs:Array = [ 0x5BBA48, 0xEA312F, 0x00B7F1, 0xFFF035, 0xD86EA3, 0xFBAE34 ]; Chapitre 9 ? Etendre les classes natives ? version 0.1.2 38 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org Il ne nous reste plus qu?à modifier au sein de la méthode clicSouris la couleur du tracé, pour cela nous utilisons la propriété index qui représente l?index de la couleur au sein du tableau : private function clicSouris ( pEvt:MouseEvent ):void { if ( conteneur == null ) throw new Error ( "Veuillez appeler au préalable la méthode affecteToile()" ); // récupération des coordonnées de la souris en x et y positionX = pEvt.stageX; positionY = pEvt.stageY; // un nouvel objet Shape est créé pour chaque tracé monNouveauTrace = new Shape(); // nous ajoutons le conteneur de tracé au conteneur principal conteneur.addChild ( monNouveauTrace ); // puis nous référençons le tracé au sein du tableau // référençant les tracés affichés tableauTraces.push ( monNouveauTrace ); // nous définissons un style de tracé monNouveauTrace.graphics.lineStyle ( 2, Stylo.couleurs[index-1], 1 ); // la mine est déplacée à cette position // pour commencer à dessiner à partir de cette position monNouveauTrace.graphics.moveTo ( positionX, positionY ); // si un nouveau tracé intervient alors que nous sommes // repartis en arrière nous repartons de cet état if ( tableauAncienTraces.length ) tableauAncienTraces = new Array; // écoute du mouvement de la souris stage.addEventListener ( MouseEvent.MOUSE_MOVE, dessine ); } Lorsque la souris est enfoncée, nous pointons grâce à la propriété index au sein du tableau couleurs afin de choisir la couleur correspondante. Nous ajoutons un petit détail final rendant notre classe plus souple, il serait intéressant de pouvoir en instanciant le stylo lui passer une vitesse permettant d?influencer sa vitesse de rotation. Nous ajoutons une propriété friction de type Number : // stocke la friction du stylo private var friction:Number; Puis nous modifions le constructeur afin d?accueillir la vitesse : public function Stylo ( pFriction:Number=.1 ) { Chapitre 9 ? Etendre les classes natives ? version 0.1.2 39 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org // le stylo écoute l'événement Event.ADDED_TO_STAGE // diffusé lorsque celui-ci est ajouté à la liste d'affichage addEventListener ( Event.ADDED_TO_STAGE, activation ); // le stylo écoute l'événement Event.REMOVED_FROM_STAGE // diffusé lorsque celui-ci est supprimé de la liste d'affichage addEventListener ( Event.REMOVED_FROM_STAGE, desactivation ); // initialisation des propriétés utilisées pour // le changement de couleurs image = 0; index = 1; // affecte la friction friction = pFriction; } Enfin, nous modifions la méthode bougeSouris afin d?utiliser la valeur passée, stockée au sein de la propriété friction : private function bougeSouris ( pEvt:MouseEvent ):void { // récupération des coordonnées de la souris en x et y positionX = pEvt.stageX; positionY = pEvt.stageY; // affectation de la position x = positionX; y = positionY; // si le stylo passe dans la zone supérieure // alors nous inclinons le stylo if ( positionY < Stylo.LIMIT_Y ) { rotation -= ( rotation - ( Stylo.LIMIT_Y - positionY ) ) * friction; // sinon, le stylo reprend son inclinaison d'origine } else rotation -= ( rotation - 0 ) * friction; // force le rafraîchissement pEvt.updateAfterEvent(); } Souvenons-nous d?un concept important de la programmation orientée objet : l?encapsulation ! Dans notre classe Stylo les propriétés sont toutes privées, car il n?y aucune raison que celles-ci soient modifiables depuis l?extérieur. Bien entendu, il existe des situations dans lesquelles l?utilisation de propriétés privées n?a pas de sens. Chapitre 9 ? Etendre les classes natives ? version 0.1.2 40 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org Dans le cas d?une classe géométrique Point, il convient que les propriétés x, y et z soient publiques et accessibles. Il convient de cacher les propriétés qui ne sont pas utiles à l?utilisateur de la classe ou bien celles qui ont de fortes chances d?évoluer dans le temps. Souvenez-vous, l?intérêt est de pouvoir changer l?implémentation sans altérer l?interface de programmation. En utilisant des propriétés privées au sein de notre classe Stylo, nous garantissons qu?aucun code extérieur à la classe ne peut accéder et ainsi altérer le fonctionnement de l?application. La friction qui est passée à l?initialisation d?un stylo doit obligatoirement être comprise entre 0 exclu et 1. Si ce n?est pas le cas, l?inclinaison du stylo échoue et le développeur assiste au disfonctionnement de l?application. Dans le code suivant le développeur ne connaît pas les valeurs possibles, et passe 10 comme vitesse d?inclinaison : // création du symbole var monStylo:Stylo = new Stylo( 10 ); En testant l?application, le développeur se rend compte que le mécanisme d?inclinaison du stylo ne fonctionne plus. Cela est dû au fait que la valeur de friction exprime une force de frottement et doit être comprise entre 0 et 1. N?oublions pas que nous sommes en train de développer un objet intelligent, autonome qui doit pouvoir évoluer dans différentes situations et pouvoir indiquer au développeur ce qui ne va pas. Cela ne vous rappelle rien ? Souvenez-vous d?un point essentiel que nous avons traité lors du précédent chapitre, le contrôle d?affectation. Nous allons ajouter un test au sein du constructeur afin de vérifier si la valeur passée est acceptable, si ce n?est pas le cas nous passerons une valeur par défaut qui assurera le bon fonctionnement du stylo, à l?inverse si la valeur passée est incorrecte nous afficherons un message d?erreur. Nous rajoutons la condition au sein du constructeur : public function Stylo ( pFriction:Number=.1 ) { // le stylo écoute l'événement Event.ADDED_TO_STAGE Chapitre 9 ? Etendre les classes natives ? version 0.1.2 41 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org // diffusé lorsque celui-ci est ajouté à la liste d'affichage addEventListener ( Event.ADDED_TO_STAGE, activation ); // le stylo écoute l'événement Event.REMOVED_FROM_STAGE // diffusé lorsque celui-ci est supprimé de la liste d'affichage addEventListener ( Event.REMOVED_FROM_STAGE, desactivation ); // initialisation des propriétés utilisées pour // le changement de couleurs image = 0; index = 1; // affecte la friction if ( pFriction > 0 && pFriction <= 1 ) friction = pFriction; else { trace("Erreur : Friction non correcte, la valeur doit être comprise entre 0 et 1"); friction = .1; } } Grâce à ce test, nous contrôlons l?affectation afin d?être sur de la bonne exécution de l?application. Cette fois le développeur a passé une valeur supérieure à 1, l?affectation est contrôlée, un message d?information indique ce qui ne va pas : /* affiche : Erreur : Friction non correcte la valeur doit être comprise entre 0 et 1 */ var monStylo:Stylo = new Stylo( 50 ); De cette manière, le développeur tiers possède toutes les informations pour s?assurer du bon fonctionnement du stylo. Elégant n?est ce pas ? Il faut cependant prévoir la possibilité de changer la vitesse du mouvement une fois l?objet créé, pour cela nous allons définir une méthode appelée affecteVitesse qui s?occupera d?affecter la propriété friction. Afin de bien encapsuler notre classe nous allons contrôler l?affectation des propriétés grâce à une méthode spécifique : public function affecteVitesse ( pFriction:Number ):void { // affecte la friction if ( pFriction > 0 && pFriction <= 1 ) friction = pFriction; else { trace("Erreur : Friction non correcte, la valeur doit être comprise entre 0 et 1"); Chapitre 9 ? Etendre les classes natives ? version 0.1.2 42 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org friction = .1; } } Nous intégrons le code défini précédemment au sein du constructeur, afin de contrôler l?affectation à la propriété friction. Nous pouvons donc remplacer le code du constructeur par un appel à la méthode affecteVitesse : public function Stylo ( pFriction:Number=.1 ) { // le stylo écoute l'événement Event.ADDED_TO_STAGE // diffusé lorsque celui-ci est ajouté à la liste d'affichage addEventListener ( Event.ADDED_TO_STAGE, activation ); // le stylo écoute l'événement Event.REMOVED_FROM_STAGE // diffusé lorsque celui-ci est supprimé de la liste d'affichage addEventListener ( Event.REMOVED_FROM_STAGE, desactivation ); // initialisation des propriétés utilisées pour // le changement de couleurs image = 0; index = 1; affecteVitesse ( pFriction ); } En ajoutant une méthode affecteVitesse, l?affectation de la propriété friction est contrôlée. Voici le code final de la classe Stylo : package { import flash.display.MovieClip; import flash.display.Shape; import flash.display.DisplayObjectContainer; import flash.events.MouseEvent; import flash.events.KeyboardEvent; import flash.events.Event; import flash.ui.Mouse; import flash.ui.Keyboard; public class Stylo extends MovieClip { // limite pour l'axe des y private static const LIMIT_Y:int = 140; // position en cours de la souris private var positionX:Number; private var positionY:Number; Chapitre 9 ? Etendre les classes natives ? version 0.1.2 43 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org // stocke une référence au conteneur de dessin private var conteneur:DisplayObjectContainer; // tableaux référençant les formes tracées et supprimées private var tableauTraces:Array = new Array(); private var tableauAncienTraces:Array = new Array(); // référence le tracé en cours private var monNouveauTrace:Shape; // code des touches Y et Z private static const codeToucheY:int = 89; private static const codeToucheZ:int = 90; // position de la tête de lecture private var image:int; private var index:int; // tableau contenant les couleurs disponibles private static var couleurs:Array = [ 0x5BBA48, 0xEA312F, 0x00B7F1, 0xFFF035, 0xD86EA3, 0xFBAE34 ]; // stocke la friction du stylo private var friction:Number; public function Stylo ( pFriction:Number=.1 ) { // le stylo écoute l'événement Event.ADDED_TO_STAGE // diffusé lorsque celui-ci est ajouté à la liste d'affichage addEventListener ( Event.ADDED_TO_STAGE, activation ); // le stylo écoute l'événement Event.REMOVED_FROM_STAGE // diffusé lorsque celui-ci est supprimé de la liste d'affichage addEventListener ( Event.REMOVED_FROM_STAGE, desactivation ); // initialisation des propriétés utilisées pour // le changement de couleurs image = 0; index = 1; // affectation contrôlée de la vitesse affecteVitesse ( pFriction ); } private function activation ( pEvt:Event ):void { // cache le curseur Mouse.hide(); // écoute des différents événements stage.addEventListener ( MouseEvent.MOUSE_MOVE, bougeSouris ); stage.addEventListener ( MouseEvent.MOUSE_DOWN, clicSouris ); stage.addEventListener ( MouseEvent.MOUSE_UP, relacheSouris ); stage.addEventListener ( KeyboardEvent.KEY_DOWN, ecouteClavier ); stage.addEventListener ( MouseEvent.MOUSE_WHEEL, moletteSouris ); Chapitre 9 ? Etendre les classes natives ? version 0.1.2 44 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org } private function desactivation ( pEvt:Event ):void { // affiche le curseur Mouse.show(); // arrête l?écoute des différents événements stage.removeEventListener ( MouseEvent.MOUSE_MOVE, bougeSouris ); stage.removeEventListener ( MouseEvent.MOUSE_DOWN, clicSouris ); stage.removeEventListener ( MouseEvent.MOUSE_UP, relacheSouris ); stage.removeEventListener ( KeyboardEvent.KEY_DOWN, ecouteClavier ); stage.removeEventListener ( MouseEvent.MOUSE_WHEEL, moletteSouris ); } // déplace la tête de lecture au scroll souris private function moletteSouris ( pEvt:MouseEvent ):void { gotoAndStop ( index = (++image%totalFrames)+1 ); } private function ecouteClavier ( pEvt:KeyboardEvent ):void { // si la barre espace est enfoncée if ( pEvt.keyCode == Keyboard.SPACE ) { // nombre d'objets Shape contenant des tracés var lng:int = tableauTraces.length; // suppression des tracés de la liste d'affichage while ( lng-- ) conteneur.removeChild ( tableauTraces[lng] ); // les tableaux d'historiques sont reinitialisés // les références supprimées tableauTraces = new Array(); tableauAncienTraces = new Array(); monNouveauTrace = null; } if ( pEvt.ctrlKey ) { // si retour en arrière (CTRL+Z) if( pEvt.keyCode == Stylo.codeToucheZ && tableauTraces.length ) { Chapitre 9 ? Etendre les classes natives ? version 0.1.2 45 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org // nous supprimons le dernier tracé var aSupprimer:Shape = tableauTraces.pop(); // nous stockons chaque tracé supprimé // dans le tableau spécifique tableauAncienTraces.push ( aSupprimer ); // nous supprimons le tracé de la liste d'affichage conteneur.removeChild( aSupprimer ); // si retour en avant (CTRL+Y) } else if ( pEvt.keyCode == Stylo.codeToucheY && tableauAncienTraces.length ) { // nous récupérons le dernier tracé ajouté var aAfficher:Shape = tableauAncienTraces.pop(); // nous le replaçons dans le tableau de tracés à l'affichage tableauTraces.push ( aAfficher ); // puis nous l'affichons conteneur.addChild ( aAfficher ); } } } private function clicSouris ( pEvt:MouseEvent ):void { if ( conteneur == null ) throw new Error ( "Veuillez appeler au préalable la méthode affecteToile()" ); // récupération des coordonnées de la souris en x et y positionX = pEvt.stageX; positionY = pEvt.stageY; // un nouvel objet Shape est créé pour chaque tracé monNouveauTrace = new Shape(); // nous ajoutons le conteneur de tracé au conteneur principal conteneur.addChild ( monNouveauTrace ); // puis nous référençons le tracé au sein du tableau // référençant les tracés affichés tableauTraces.push ( monNouveauTrace ); // nous définissons un style de tracé monNouveauTrace.graphics.lineStyle ( 2, Stylo.couleurs[index-1], 1 ); // la mine est déplacée à cette position // pour commencer à dessiner à partir de cette position monNouveauTrace.graphics.moveTo ( positionX, positionY ); Chapitre 9 ? Etendre les classes natives ? version 0.1.2 46 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org // si un nouveau tracé intervient alors que nous sommes // repartis en arrière nous repartons de cet état if ( tableauAncienTraces.length ) tableauAncienTraces = new Array; // écoute du mouvement de la souris stage.addEventListener ( MouseEvent.MOUSE_MOVE, dessine ); } private function bougeSouris ( pEvt:MouseEvent ):void { // récupération des coordonnées de la souris en x et y positionX = pEvt.stageX; positionY = pEvt.stageY; // affectation de la position x = positionX; y = positionY; // si le stylo passe dans la zone supérieure // alors nous inclinons le stylo if ( positionY < Stylo.LIMIT_Y ) { rotation -= ( rotation - ( Stylo.LIMIT_Y - positionY ) ) * friction; // sinon, le stylo reprend son inclinaison d'origine } else rotation -= ( rotation - 0 ) * friction; // force le rafraîchissement pEvt.updateAfterEvent(); } private function relacheSouris ( pEvt:MouseEvent ):void { stage.removeEventListener ( MouseEvent.MOUSE_MOVE, dessine ); } private function dessine ( pEvt:MouseEvent ):void { if ( monNouveauTrace != null ) { // la mine est déplacée à cette position // pour commencer à dessiner à partir de cette position monNouveauTrace.graphics.lineTo ( positionX, positionY ); } } Chapitre 9 ? Etendre les classes natives ? version 0.1.2 47 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org // méthode permettant de spécifier le conteneur du dessin public function affecteToile ( pToile:DisplayObjectContainer ):void { conteneur = pToile; } public function affecteVitesse ( pFriction:Number ):void { // affecte la friction if ( pFriction > 0 && pFriction <= 1 ) friction = pFriction; else { trace("Erreur : Friction non correcte, la valeur doit être comprise entre 0 et 1"); friction = .1; } } } } A retenir ? Etendre les classes graphiques natives de Flash permet de créer des objets interactifs puissants et réutilisables. ? Au sein d?une sous-classe graphique, l?utilisation du mot clé this fait directement référence au scénario du symbole. Réutiliser le code Notre application de dessin plaît beaucoup, une agence vient de nous contacter afin de décliner l?application pour un nouveau client. Grâce à notre structure actuelle, la déclinaison graphique ne va nécessiter aucune modification du code. Dans un nouveau document Flash CS3 nous importons un nouveau graphisme relatif au stylo comme l?illustre la figure 9-13 : Chapitre 9 ? Etendre les classes natives ? version 0.1.2 48 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 9-13. Nouveau graphisme. Au sein du panneau Propriétés de liaison nous spécifions la classe Stylo développée précédemment. Voilà, nous pouvons compiler, la figure 9-14 illustre le résultat : Chapitre 9 ? Etendre les classes natives ? version 0.1.2 49 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 9-14. Réutilisation du code de la classe Stylo. La déclinaison de notre projet a pris quelques secondes seulement, tout symbole de type MovieClip peut être lié à la classe Stylo, et bénéficier de toutes ses fonctionnalités et comportements. Classe dynamique Comme nous l?avons vu lors des précédents chapitres, il existe en ActionScript 3 deux types de classes : dynamiques et non dynamiques. Dans le cas de sous-classes graphiques, quelque soit la nature de super-classe, la sous-classe graphique est par défaut toujours non dynamique. Il est donc impossible à ce stade d?ajouter une propriété ou une méthode à l?exécution à une instance de la classe Stylo : // création du symbole var monStylo:Stylo = new Stylo( .1 ); // ajout d'une propriété à l'exécution monStylo.maProp = 12; Le code précédent génère l?erreur suivante à la compilation : 1119: Accès à la propriété maProp peut-être non définie, via la référence de type static Stylo. Ce comportement par défaut est de bon augure, car il est généralement déconseillé d?avoir recours à des classes dynamiques. En donnant la possibilité de modifier l?implémentation à l?exécution, le développeur Chapitre 9 ? Etendre les classes natives ? version 0.1.2 50 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org utilisant la classe doit parcourir le code d?un projet afin de découvrir les comportements qui peuvent être ajoutés à l?exécution. En lisant la classe, ce dernier n?est pas renseigné de toutes les capacités et caractéristiques de celle-ci. Dans de rares situations, il peut toutefois être nécessaire de rendre la sous-classe graphique dynamique, pour cela nous ajoutons l?attribut dynamic devant le mot clé class : dynamic public class Stylo extends MovieClip Une fois la classe modifiée, le code suivant fonctionne : // création du symbole var monStylo:Stylo = new Stylo( .1 ); // ajout d'une propriété à l'exécution monStylo.maProp = 12; // affiche : 12 trace( monStylo.maProp); Bien que l?attribut dynamic existe, seule la classe MovieClip en profite en ActionScript 3, toutes les autres classes ne permettent pas l?ajout de propriétés ou méthodes à l?exécution et sont dites non dynamiques. Il est fortement déconseillé de s?appuyer sur des classes dynamiques dans vos projets. En rendant une classe dynamique aucune vérification de type n?est faite à la compilation, nous pouvons donc même si celle- ci nous renvoie undefined, accéder à des propriétés privées : // création du symbole var monStylo:Stylo = new Stylo( .1 ); // aucune vérification de type à la compilation // heureusement, la machine virtuelle (VM2) conserve les types // à l'exécution et empêche son accès en renvoyant undefined // affiche : undefined trace( monStylo.nFriction ); La machine virtuelle 2 (VM2) conserve les types à l?exécution, ainsi lorsque nous accédons à une propriété privée au sein d?une classe dynamique, celle-ci renvoie undefined. Dans le cas d?une méthode privée, une erreur à l?exécution de type TypeError est levée. A retenir Chapitre 9 ? Etendre les classes natives ? version 0.1.2 51 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org ? L?attribut dynamic permet de rendre une classe dynamique. ? Aucune vérification de type n?est faite à la compilation sur une classe dynamique. ? Il est déconseillé d?utiliser des classes dynamiques dans des projets ActionScript. ? Dans le cas de sous-classes graphiques, quelque soit la nature de super-classe, la sous-classe graphique est par défaut toujours non dynamique. Un vrai constructeur En ActionScript 1 et 2, il était aussi possible d?étendre la classe MovieClip. Le principe était quasiment le même, un symbole était défini dans la librairie, puis une classe était liée au symbole. Attention, il est important de noter qu?en ActionScript 2 la sous-classe graphique que nous pouvions définir était liée au symbole, cela signifie que seul l?appel de la méthode attachMovie instanciait la classe. En ActionScript 3 grâce au nouveau modèle d?instanciation des objets graphiques, c?est le symbole qui est lié à la classe. Cela signifie que l?instanciation de la sous-classe entraîne l?affichage du symbole et non l?inverse. Le seul moyen d?instancier notre sous-classe graphique en ActionScript 2 était d?appeler la méthode attachMovie. ActionScript 2, contrairement à ActionScript 3 souffrait d?un lourd héritage, ce processus d?instanciation des objets graphiques était archaïque et ne collait pas au modèle objet. En interne le lecteur déclenchait le constructeur de la sous-classe graphique. Il était impossible de passer des paramètres d?initialisation, seul l?objet d?initialisation (initObject) permettait de renseigner des propriétés avant même que le constructeur ne soit déclenché. En instanciant la sous-classe, le symbole n?était pas affiché, le code suivant ne fonctionnait pas car le seul moyen d?afficher le clip pour le lecteur était la méthode attachMovie : // la sous-classe était instanciée mais le symbole n'était pas affiché var monSymbole:SousClasseMC = new SousClasseMC(); De plus, la liaison entre le symbole et la classe se faisait uniquement à travers le panneau Propriétés de Liaison, ainsi il était impossible de lier une classe à un objet graphique créé par programmation. Chapitre 9 ? Etendre les classes natives ? version 0.1.2 52 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org ActionScript 3 corrige cela. Dans l?exemple suivant nous allons créer une sous classe de flash.display.Sprite sans créer de symbole au sein de la bibliothèque, tout sera réalisé dynamiquement. A retenir ? En ActionScript 2, il était impossible de passer des paramètres au constructeur d?une sous classe graphique. ? Afin de palier à ce problème, nous utilisions l?objet d?initialisation disponible au sein de la méthode attachMovie. ? ActionScript 3 règle tout cela, grâce au nouveau modèle d?instanciation des objets graphiques. ? En ActionScript 2, la classe était liée au symbole. Seule la méthode attachMovie permettait d?instancier la sous-classe graphique. ? En ActionScript 3, le symbole est lié à la classe. L?instanciation de la sous-classe par le mot clé new entraîne la création du symbole. Créer des boutons dynamiques Nous allons étendre la classe Sprite afin de créer des boutons dynamiques. Ces derniers nous permettront de créer un menu qui pourra être modifié plus tard afin de donner vie à d?autres types de menus. Lors du chapitre 7 nous avions développé un menu composé de boutons de type Sprite. Tous les comportements étaient définis à l?extérieur de celui-ci, nous devions d?abord créer le symbole correspondant, puis au sein de la boucle ajouter les comportements boutons, les différents effets de survol, puis ajouter le texte. Il était donc impossible de recréer rapidement un bouton identique au sein d?une autre application. En utilisant une approche orientée objet à l?aide d?une sous-classe graphique, nous allons pouvoir définir une classe Bouton qui pourra être réutilisée dans chaque application nécessitant un bouton fonctionnel. Nous allons à présent organiser nos classes en les plaçant dans des paquetages spécifiques, au cours du chapitre précédent nous avons traité la notion de paquetages sans véritablement l?utiliser. Il est temps d?utiliser cette notion, cela va nous permettre d?organiser nos classes proprement et éviter dans certaines situations les conflits entre les classes. Nous créons un nouveau document Flash CS3, à coté de celui-ci nous créons un répertoire org contenant un répertoire bytearray. Puis Chapitre 9 ? Etendre les classes natives ? version 0.1.2 53 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org au sein même de ce répertoire nous créons un autre répertoire appelé ui. Au sein du répertoire ui nous créons une sous-classe de Sprite appelée Bouton contenant le code suivant : package org.bytearray.ui { import flash.display.Sprite; public class Bouton extends Sprite { public function Bouton () { trace ( this ); } } } Nous remarquons que le paquetage reflète l?emplacement de la classe au sein des répertoires, si le chemin n?est pas correct le compilateur ne trouve pas la classe et génère une erreur. Pour instancier la classe Bouton dans notre document Flash nous devons obligatoirement l?importer, car le compilateur recherche par défaut les classes situées à coté du fichier .fla mais ne parcours pas tous les répertoires voisins afin de trouver la définition de classe. Nous lui indiquons où elle se trouve à l?aide du mot clé import : // import de la classe Bouton import org.bytearray.ui.Bouton; // affiche : [object Bouton] var monBouton:Bouton = new Bouton(); Notre bouton est bien créé mais pour l?instant cela ne nous apporte pas beaucoup plus qu?une instanciation directe de la classe Sprite, nous allons tout de suite ajouter quelques fonctionnalités. Nous allons dessiner le bouton dynamiquement à l?aide de l?API de dessin. Grâce à l?héritage, la classe Bouton possède toutes les capacités d?un Sprite et hérite donc d?une propriété graphics propre à l?API de dessin. Nous pourrions dessiner directement au sein de la classe Bouton mais nous préfèrerons l?utilisation d?un objet Shape dédié à cela. Si nous devons plus tard ajouter un effet de survol nous déformerons celui-ci Chapitre 9 ? Etendre les classes natives ? version 0.1.2 54 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org afin de ne pas étirer l?enveloppe principale du bouton qui provoquerait un étirement global de tous les enfants du bouton. Au sein du constructeur de la classe Bouton nous dessinons le bouton : package org.bytearray.ui { import flash.display.Shape; import flash.display.Sprite; public class Bouton extends Sprite { // référence le fond du bouton private var fondBouton:Shape; public function Bouton () { // création du fond du bouton fondBouton = new Shape(); // ajout à la liste d'affichage addChild ( fondBouton ); // dessine le bouton fondBouton.graphics.beginFill ( Math.random()*0xFFFFFF, 1 ); fondBouton.graphics.drawRect ( 0, 0, 40, 110 ); } } } Nous choisissons une couleur aléatoire, puis nous dessinons une forme rectangulaire, plus tard nous pourrons choisir la couleur du bouton lors de sa création ou encore choisir sa dimension. Il ne nous reste plus qu?à l?afficher : // import de la classe Bouton import org.bytearray.ui.Bouton; // création d'un conteneur pour le menu var conteneurMenu:Sprite = new Sprite(); // instanciation var monBouton:Bouton = new Bouton(); // ajout au sein du conteneur conteneurMenu.addChild ( monBouton ); // affichage des boutons addChild ( conteneurMenu ); Chapitre 9 ? Etendre les classes natives ? version 0.1.2 55 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org Nous obtenons le résultat illustré en figure 9-15 : Figure 9-15. Instance de la classe Bouton. Afin de rendre notre bouton cliquable, nous devons activer une propriété ? Vous souvenez-vous de laquelle ? La propriété buttonMode permet d?activer le comportement bouton auprès de n?importe quel DisplayObject : package org.bytearray.ui { import flash.display.Shape; import flash.display.Sprite; public class Bouton extends Sprite { // stocke le fond du bouton private var fondBouton:Shape; public function Bouton () { // création du fond du bouton fondBouton = new Shape(); Chapitre 9 ? Etendre les classes natives ? version 0.1.2 56 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org // ajout à la liste d'affichage addChild ( fondBouton ); // dessine le bouton fondBouton.graphics.beginFill ( Math.random()*0xFFFFFF, 1 ); fondBouton.graphics.drawRect ( 0, 0, 40, 110 ); // activation du mode bouton buttonMode = true; } } } Le curseur main s?affiche alors au survol du bouton : Figure 9-16. Activation du mode bouton. Nous allons donner un peu de mouvement à ce dernier, en utilisant la classe Tween déjà abordée lors du chapitre 7. Nous importons la classe Tween puis nous créons un objet du même type afin de gérer le survol : package org.bytearray.ui { import flash.display.Shape; import flash.display.Sprite; // import des classes Tween liées au mouvement Chapitre 9 ? Etendre les classes natives ? version 0.1.2 57 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org import fl.transitions.Tween; import fl.transitions.easing.Bounce; public class Bouton extends Sprite { // stocke le fond du bouton private var fondBouton:Shape; // stocke l'objet Tween pour les différents états du bouton private var interpolation:Tween; public function Bouton () { // création du fond du bouton fondBouton = new Shape(); // ajout à la liste d'affichage addChild ( fondBouton ); // dessine le bouton fondBouton.graphics.beginFill ( Math.random()*0xFFFFFF, 1 ); fondBouton.graphics.drawRect ( 0, 0, 40, 110 ); // activation du mode bouton buttonMode = true; // création de l'objet Tween interpolation = new Tween (fondBouton, "scaleX", Bounce.easeOut, 1, 1, 1, true ); } } } Nous demandons à l?objet Tween de s?occuper de la propriété scaleX pour donner un effet d?étirement à l?objet Shape servant de fond à notre bouton. Nous allons donner un effet de rebond exprimé par la classe Bounce. Lorsque le bouton est survolé nous démarrons l?animation. Nous écoutons l?événement MouseEvent.ROLL_OVER : package org.bytearray.ui { import flash.display.Shape; import flash.display.Sprite; // import des classes Tween liées au mouvement import fl.transitions.Tween; import fl.transitions.easing.Bounce; // import de la classe MouseEvent import flash.events.MouseEvent; public class Bouton extends Sprite Chapitre 9 ? Etendre les classes natives ? version 0.1.2 58 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org { // stocke le fond du bouton private var fondBouton:Shape; // stocke l'objet Tween pour les différents états du bouton private var interpolation:Tween; public function Bouton () { // création du fond du bouton fondBouton = new Shape(); // ajout à la liste d'affichage addChild ( fondBouton ); // dessine le bouton fondBouton.graphics.beginFill ( Math.random()*0xFFFFFF, 1 ); fondBouton.graphics.drawRect ( 0, 0, 40, 110 ); // activation du mode bouton buttonMode = true; // création de l'objet Tween interpolation = new Tween ( fondBouton, "scaleX", Bounce.easeOut, 1, 1, 1, true ); // écoute de l'événement MouseEvent.CLICK addEventListener ( MouseEvent.ROLL_OVER, survolSouris ); } // déclenché lors du survol du bouton private function survolSouris ( pEvt:MouseEvent ):void { // démarrage de l'animation interpolation.continueTo ( 2, 2 ); } } } Lors du survol, le bouton s?étire avec un effet de rebond, la figure 9- 17 illustre l?effet : Chapitre 9 ? Etendre les classes natives ? version 0.1.2 59 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 9-17. Etirement du bouton. En ajoutant d?autres boutons nous obtenons un premier menu : // import de la classe Bouton import org.bytearray.ui.Bouton; // création d'un conteneur pour le menu var conteneurMenu:Sprite = new Sprite(); var lng:int = 5; var monBouton:Bouton; for (var i:int = 0; i< lng; i++ ) { // instanciation monBouton = new Bouton(); //positionnement monBouton.y = 50 * i; // ajout au sein du conteneur conteneurMenu.addChild ( monBouton ); } // affichage des boutons addChild ( conteneurMenu ); Le résultat est illustré en figure 9-18 : Chapitre 9 ? Etendre les classes natives ? version 0.1.2 60 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 9-18. Création du menu. La classe Bouton peut ainsi être réutilisée dans n?importe quel projet nécessitant un seul bouton, ou un menu. Il manque pour le moment une fonctionnalité permettant de refermer le bouton cliqué lorsqu?un autre est sélectionné. Pour cela nous devons obligatoirement posséder une référence envers tous les boutons du menu, et pouvoir décider quels boutons refermer. Vous souvenez-vous de la classe Joueur créée au cours du chapitre 8 ? Nous avions défini un tableau stockant les références de chaque joueur créé, cela nous permettait à tout moment de savoir combien de joueurs étaient créés et de pouvoir y accéder. Nous allons reproduire le même mécanisme à l?aide d?un tableau statique. Le tableau tableauBoutons stocke chaque référence de boutons : package org.bytearray.ui { import flash.display.Shape; import flash.display.Sprite; // import des classes Tween liées au mouvement import fl.transitions.Tween; import fl.transitions.easing.Bounce; // import de la classe MouseEvent Chapitre 9 ? Etendre les classes natives ? version 0.1.2 61 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org import flash.events.MouseEvent; public class Bouton extends Sprite { // stocke le fond du bouton private var fondBouton:Shape; // stocke l'objet Tween pour les différents états du bouton private var interpolation:Tween; // stocke les références aux boutons private static var tableauBoutons:Array = new Array(); public function Bouton () { // ajoute chaque instance au tableau Bouton.tableauBoutons.push ( this ); // création du fond du bouton fondBouton = new Shape(); // ajout à la liste d'affichage addChild ( fondBouton ); // dessine le bouton fondBouton.graphics.beginFill ( Math.random()*0xFFFFFF, 1 ); fondBouton.graphics.drawRect ( 0, 0, 40, 110 ); // activation du mode bouton buttonMode = true; // création de l'objet Tween interpolation = new Tween ( fondBouton, "scaleX", Bounce.easeOut, 1, 1, 1, true ); // écoute de l'événement MouseEvent.CLICK addEventListener ( MouseEvent.ROLL_OVER, survolSouris ); } // déclenché lors du survol du bouton private function survolSouris ( pEvt:MouseEvent ):void { // démarrage de l'animation interpolation.continueTo ( 2, 2 ); } } } A tout moment un bouton peut parcourir le tableau statique et accéder à ses frères et décider de les refermer. Nous modifions la méthode survolSouris afin d?appeler sur chaque bouton la méthode fermer : Chapitre 9 ? Etendre les classes natives ? version 0.1.2 62 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org // déclenché lors du survol du bouton private function survolSouris ( pEvt:MouseEvent ):void { // stocke la longueur du tableau var lng:int = Bouton.tableauBoutons.length; for (var i:int = 0; i<lng; i++ ) Bouton.tableauBoutons[i].fermer(); // démarrage de l'animation interpolation.continueTo ( 2, 1 ); } La méthode fermer est privée car celle ne sera appelée qu?au sein de la classe Bouton : // méthode permettant de refermer le bouton private function fermer ():void { // referme le bouton interpolation.continueTo ( 1, 1 ); } Si nous testons l?animation nous remarquons que lors du survol les autres boutons ouverts se referment. Bien entendu dans la plupart des projets ActionScript nous n?allons pas seulement créer des menus constitués de couleurs aléatoires. Il serait intéressant de pouvoir choisir la couleur de chaque bouton, pour cela nous ajoutons un paramètre afin de recevoir la couleur du bouton au sein du constructeur : package org.bytearray.ui { import flash.display.Shape; import flash.display.Sprite; // import des classes Tween liées au mouvement import fl.transitions.Tween; import fl.transitions.easing.Bounce; // import de la classe MouseEvent import flash.events.MouseEvent; public class Bouton extends Sprite { // stocke le fond du bouton private var fondBouton:Shape; // stocke l'objet Tween pour les différents états du bouton private var interpolation:Tween; // stocke les références aux boutons private static var tableauBoutons:Array = new Array(); // stocke la couleur en cours du bouton private var couleur:Number; Chapitre 9 ? Etendre les classes natives ? version 0.1.2 63 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org public function Bouton ( pCouleur:Number ) { // ajoute chaque instance au tableau Bouton.tableauBoutons.push ( this ); // création du fond du bouton fondBouton = new Shape(); // ajout à la liste d'affichage addChild ( fondBouton ); // stocke la couleur passée en paramètre couleur = pCouleur; // dessine le bouton fondBouton.graphics.beginFill ( couleur, 1 ); fondBouton.graphics.drawRect ( 0, 0, 40, 110 ); // activation du mode bouton buttonMode = true; // création de l'objet Tween interpolation = new Tween ( fondBouton, "scaleX", Bounce.easeOut, 1, 1, 1, true ); // écoute de l'événement MouseEvent.CLICK addEventListener ( MouseEvent.ROLL_OVER, survolSouris ); } // déclenché lors du survol du bouton private function survolSouris ( pEvt:MouseEvent ):void { // stocke la longueur du tableau var lng:int = Bouton.tableauBoutons.length; for (var i:int = 0; i<lng; i++ ) Bouton.tableauBoutons[i].fermer(); // démarrage de l'animation interpolation.continueTo ( 2, 2 ); } // méthode permettant de refermer le bouton private function fermer ():void { // referme le bouton interpolation.continueTo ( 1, 1 ); } } } Chapitre 9 ? Etendre les classes natives ? version 0.1.2 64 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org Désormais la classe Bouton accepte un paramètre pour la couleur, le code suivant crée un menu constitué de boutons rouges seulement : // import de la classe Bouton import org.bytearray.ui.Bouton; // création d'un conteneur pour le menu var conteneurMenu:Sprite = new Sprite(); var lng:int = 5; var monBouton:Bouton; for (var i:int = 0; i< lng; i++ ) { // instanciation // création de boutons rouge monBouton = new Bouton( 0x990000 ); //positionnement monBouton.y = 50 * i; // ajout au sein du conteneur conteneurMenu.addChild ( monBouton ); } // affichage des boutons addChild ( conteneurMenu ); Cela n?a pas grand intérêt, nous allons donc modifier le code actuel afin d?associer à chaque bouton, une couleur précise. Pour cela nous stockons les couleurs au sein d?un tableau qui sera parcouru, le nombre de boutons du menu sera lié à la longueur du tableau : // import de la classe Bouton import org.bytearray.ui.Bouton; // création d'un conteneur pour le menu var conteneurMenu:Sprite = new Sprite(); // tableau de couleurs var couleurs:Array = [0x999900, 0x881122, 0x995471, 0x332100, 0x977821]; // nombre de couleurs var lng:int = couleurs.length; var monBouton:Bouton; for (var i:int = 0; i< lng; i++ ) { // instanciation monBouton = new Bouton( couleurs[i] ); Chapitre 9 ? Etendre les classes natives ? version 0.1.2 65 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org //positionnement monBouton.y = 50 * i; // ajout au sein du conteneur conteneurMenu.addChild ( monBouton ); } // affichage des boutons addChild ( conteneurMenu ); Nous obtenons le menu illustré en figure 9-19 : Figure 9-19. Menu constitué de couleurs prédéfinies. Chaque bouton accepte une couleur lors de sa création, nous pourrions imaginer une application dans laquelle l?utilisateur choisit la couleur des boutons de son menu au sein d?une administration. Les couleurs sont alors récupérées d?une base de données puis utilisées pour créer ce menu. Nous verrons au cours du chapitre 19 intitulé Flash Remoting comment mettre en place cette application. Notre menu n?est pas encore complet il serait judicieux de pouvoir spécifier la vitesse d?ouverture de chaque bouton. Souvenez-vous nous avons déjà intégré ce contrôle pour le mouvement du stylo de l?application précédente. Nos objets sont tous de la même famille mais possèdent des caractéristiques différentes, comme la couleur ou bien la vitesse d?ouverture. Chapitre 9 ? Etendre les classes natives ? version 0.1.2 66 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org Pour cela nous allons définir une propriété privée vitesse qui se chargera de stocker la vitesse passée à chaque bouton : // stocke la vitesse d'ouverture de chaque bouton private var vitesse:Number; Puis nous définissons une méthode affecteVitesse qui se charge d?affecter la vitesse de manière contrôlée : // gère l'affectation de la vitesse public function affecteVitesse ( pVitesse:Number ):void { // affecte la vitesse if ( pVitesse >= 1 && pVitesse <= 10 ) vitesse = pVitesse; else { trace("Erreur : Vitesse non correcte, la valeur doit être comprise entre 1 et 10"); vitesse = 1; } } Nous modifions le constructeur en ajoutant un paramètre appelé pVitesse afin d?appeler la méthode affecteVitesse avant la création de l?objet Tween pour initialiser la propriété vitesse : package org.bytearray.ui { import flash.display.Shape; import flash.display.Sprite; // import des classes Tween liées au mouvement import fl.transitions.Tween; import fl.transitions.easing.Bounce; // import de la classe MouseEvent import flash.events.MouseEvent; public class Bouton extends Sprite { // stocke le fond du bouton private var fondBouton:Shape; // stocke l'objet Tween pour les différents états du bouton private var interpolation:Tween; // stocke les références aux boutons private static var tableauBoutons:Array = new Array(); // stocke la couleur en cours du bouton private var couleur:Number; // stocke la vitesse d'ouverture de chaque bouton private var vitesse:Number; public function Bouton ( pCouleur:Number, pVitesse:Number=1 ) Chapitre 9 ? Etendre les classes natives ? version 0.1.2 67 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org { // ajoute chaque instance au tableau Bouton.tableauBoutons.push ( this ); // création du fond du bouton fondBouton = new Shape(); // ajout à la liste d'affichage addChild ( fondBouton ); // stocke la couleur passée en paramètre couleur = pCouleur; // dessine le bouton fondBouton.graphics.beginFill ( couleur, 1 ); fondBouton.graphics.drawRect ( 0, 0, 40, 110 ); // activation du mode bouton buttonMode = true; // affectation de la vitesse contrôlée affecteVitesse ( pVitesse ); // création de l'objet Tween interpolation = new Tween ( fondBouton, "scaleX", Bounce.easeOut, 1, 1, vitesse, true ); // écoute de l'événement MouseEvent.CLICK addEventListener ( MouseEvent.ROLL_OVER, survolSouris ); } // déclenché lors du survol du bouton private function survolSouris ( pEvt:MouseEvent ):void { // stocke la longueur du tableau var lng:int = Bouton.tableauBoutons.length; for (var i:int = 0; i<lng; i++ ) Bouton.tableauBoutons[i].fermer(); // démarrage de l'animation interpolation.continueTo ( 2, vitesse ); } // méthode permettant de refermer le bouton private function fermer ():void { // referme le bouton interpolation.continueTo ( 1, vitesse ); } // gère l'affectation de la vitesse public function affecteVitesse ( pVitesse:Number ):void Chapitre 9 ? Etendre les classes natives ? version 0.1.2 68 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org { // affecte la vitesse if ( pVitesse >= 1 && pVitesse <= 10 ) vitesse = pVitesse; else { trace("Erreur : Vitesse non correcte, la valeur doit être comprise entre 1 et 10"); vitesse = 1; } } } } Dans le code suivant, nous créons un menu composé de boutons dont nous pouvons choisir la vitesse d?ouverture : // import de la classe Bouton import org.bytearray.ui.Bouton; // création d'un conteneur pour le menu var conteneurMenu:Sprite = new Sprite(); // tableau de couleurs var couleurs:Array = [0x999900, 0x881122, 0x995471, 0x332100, 0x977821]; // nombre de couleurs var lng:int = couleurs.length; var monBouton:Bouton; for (var i:int = 0; i< lng; i++ ) { // instanciation monBouton = new Bouton( couleurs[i], 1 ); //positionnement monBouton.y = 50 * i; // ajout au sein du conteneur conteneurMenu.addChild ( monBouton ); } // affichage du menu addChild ( conteneurMenu ); Au cas où la vitesse passée ne serait pas correcte, le menu continue de fonctionner et un message d?erreur est affiché : // affiche : Erreur : Vitesse non correcte, la valeur // doit être comprise entre 1 et 10 var monBouton:Bouton = new Bouton( couleurs[i], 0 ); Chapitre 9 ? Etendre les classes natives ? version 0.1.2 69 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org Généralement, les boutons d?un menu contiennent une légende, nous allons donc ajouter un champ texte à chaque bouton. Pour cela nous importons les classes flash.text.TextField et flash.text.TextFieldAutoSize : // import de la classe TextField et TextFieldAutoSize import flash.text.TextField; import flash.text.TextFieldAutoSize; Nous créons une propriété legende afin de référencer la légende : // légende du bouton private var legende:TextField; Puis nous ajoutons le champ au bouton au sein du constructeur : public function Bouton ( pCouleur:Number, pVitesse:Number=1 ) { // ajoute chaque instance au tableau Bouton.tableauBoutons.push ( this ); // création du fond du bouton fondBouton = new Shape(); // ajout à la liste d'affichage addChild ( fondBouton ); // crée le champ texte legende = new TextField(); // redimensionnement automatique du champ texte legende.autoSize = TextFieldAutoSize.LEFT; // rend le champ texte non sélectionnable legende.selectable = false; // ajout à la liste d'affichage addChild ( legende ); // stocke la couleur passée en paramètre couleur = pCouleur; // dessine le bouton fondBouton.graphics.beginFill ( couleur, 1 ); fondBouton.graphics.drawRect ( 0, 0, 40, 110 ); // activation du mode bouton buttonMode = true; // affectation de la vitesse contrôlée affecteVitesse ( pVitesse ); // création de l'objet Tween interpolation = new Tween ( fondBouton, "scaleX", Bounce.easeOut, 1, 1, vitesse, true ); // écoute de l'événement MouseEvent.CLICK addEventListener ( MouseEvent.ROLL_OVER, survolSouris ); Chapitre 9 ? Etendre les classes natives ? version 0.1.2 70 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org } Si nous testons notre menu, nous remarquons que le champ texte imbriqué dans le bouton reçoit les entrées souris et entre en conflit avec l?enveloppe principale du bouton. Afin de désactiver les objets enfants du bouton nous passons la valeur false à la propriété mouseChildren du bouton : // désactivation des objets enfants mouseChildren = false; Souvenez-vous, lors du chapitre 7, nous avons vu que la propriété mouseChildren permettait de désactiver les événements souris auprès des objets enfants. Nous ajoutons un paramètre au constructeur de la classe Bouton afin d?accueillir le texte affiché par la légende : public function Bouton ( pCouleur:Number, pVitesse:Number=1, pLegende:String="Légende" ) { // ajoute chaque instance au tableau Bouton.tableauBoutons.push ( this ); // création du fond du bouton fondBouton = new Shape(); // ajout à la liste d'affichage addChild ( fondBouton ); // crée le champ texte legende = new TextField(); // redimensionnement automatique du champ texte legende.autoSize = TextFieldAutoSize.LEFT; // rend le champ texte non sélectionnable legende.selectable = false; // ajout à la liste d'affichage addChild ( legende ); // affecte la légende legende.text = pLegende; // stocke la couleur passée en paramètre couleur = pCouleur; // dessine le bouton fondBouton.graphics.beginFill ( couleur, 1 ); fondBouton.graphics.drawRect ( 0, 0, 40, 110 ); // activation du mode bouton buttonMode = true; // désactivation des objets enfants Chapitre 9 ? Etendre les classes natives ? version 0.1.2 71 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org mouseChildren = false; // affectation de la vitesse contrôlée affecteVitesse ( pVitesse ); // création de l'objet Tween interpolation = new Tween ( fondBouton, "scaleX", Bounce.easeOut, 1, 1, vitesse, true ); // écoute de l'événement MouseEvent.CLICK addEventListener ( MouseEvent.ROLL_OVER, survolSouris ); } Les données utilisées afin de générer un menu proviennent généralement d?un flux XML ou de Flash Remoting et sont très souvent formatées sous forme de tableau associatif. Nous allons réorganiser nos données afin d?utiliser un tableau associatif plutôt que plusieurs tableaux séparés : // import de la classe Bouton import org.bytearray.ui.Bouton; // création d'un conteneur pour le menu var conteneurMenu:Sprite = new Sprite(); // tableau associatif contenant les données var donnees:Array = new Array(); donnees.push ( { legende : "Accueil", vitesse : 1, couleur : 0x999900 } ); donnees.push ( { legende : "Photos", vitesse : 1, couleur : 0x881122 } ); donnees.push ( { legende : "Blog", vitesse : 1, couleur : 0x995471 } ); donnees.push ( { legende : "Liens", vitesse : 1, couleur : 0x332100 } ); donnees.push ( { legende : "Forum", vitesse : 1, couleur : 0x977821 } ); // nombre de rubriques var lng:int = donnees.length; var monBouton:Bouton; var legende:String; var couleur:Number; var vitesse:Number; for (var i:int = 0; i< lng; i++ ) { // récupération des infos legende = donnees[i].legende; couleur = donnees[i].couleur; vitesse = donnees[i].vitesse; // création des boutons monBouton = new Bouton( couleur, vitesse, legende ); // positionnement monBouton.y = 50 * i; // ajout au sein du conteneur conteneurMenu.addChild ( monBouton ); Chapitre 9 ? Etendre les classes natives ? version 0.1.2 72 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org } // affichage du menu addChild ( conteneurMenu ); En testant notre menu, nous obtenons le résultat illustré en figure 9- 20 : Figure 9-20. Boutons avec légendes. Le texte de légende n?est pas correctement formaté, pour y remédier nous utilisons la classe flash.text.TextFormat ainsi que la classe flash.text.Font. Nous reviendrons plus en profondeur sur le formatage du texte au cours du chapitre 16 intitulé Le texte. Afin d?intégrer une police au sein de la bibliothèque nous cliquons sur l?icône prévue illustrée en figure 9-21 : Figure 9-21. Options de la bibliothèque. Chapitre 9 ? Etendre les classes natives ? version 0.1.2 73 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org Le panneau d?options de bibliothèque s?ouvre, nous sélectionnons Nouvelle Police comme l?illustre la figure 9-22 : Figure 9-22. Insertion de police dans la bibliothèque. Une fois sélectionnée, le panneau Propriétés des symboles de police s?affiche, ce dernier permet de sélectionner la police qui sera embarquée dans l?animation. Nous sélectionnons pour notre exemple la police Trebuchet MS, puis nous donnons un nom de classe à la police, nous conservons Police 1. Figure 9-23. Propriétés des symboles de police. Chapitre 9 ? Etendre les classes natives ? version 0.1.2 74 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org Une fois validé, la police apparaît dans la bibliothèque, il ne nous reste plus qu?à l?utiliser au sein de nos champs texte contenus dans chaque bouton. Pour cela il faut lier cette police comme pour un symbole classique. En faisant un clic droit sur celle-ci, nous sélectionnons l?option Liaisons le panneau Propriétés de liaison s?ouvre comme l?illustre la figure 9-24 : Figure 9-24. Panneau propriétés de liaison. Nous choisissons MaPolice comme nom de classe, celle-ci va automatiquement être créée par Flash et héritera de la classe flash.text.Font. Nous définissons une nouvelle propriété formatage afin de stocker l?objet TextFormat : // formatage des légendes private var formatage:TextFormat; Nous importons la classe flash.text.TextFormat : import flash.text.TextFormat; Puis nous modifions le constructeur de la classe Bouton afin d?affecter le formatage et indiquer au champ texte d?utiliser la police embarquée : public function Bouton ( pCouleur:Number, pVitesse:Number, pLegende:String ) { // ajoute chaque instance au tableau Bouton.tableauBoutons.push ( this ); // création du fond du bouton fondBouton = new Shape(); // ajout à la liste d'affichage addChild ( fondBouton ); Chapitre 9 ? Etendre les classes natives ? version 0.1.2 75 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org // crée le champ texte legende = new TextField(); // redimensionnement automatique du champ texte legende.autoSize = TextFieldAutoSize.LEFT; // ajout à la liste d'affichage addChild ( legende ); // affecte la légende legende.text = pLegende; // active l'utilisation de police embarquée legende.embedFonts = true; // crée un objet de formatage formatage = new TextFormat(); // taille de la police formatage.size = 12; // instanciation de la police embarquée var police:MaPolice = new MaPolice(); // affectation de la police au formatage formatage.font = police.fontName; // affectation du formatage au champ texte legende.setTextFormat ( formatage ); // rend le champ texte non selectionnable legende.selectable = false; // stocke la couleur passée en paramètre couleur = pCouleur; // dessine le bouton fondBouton.graphics.beginFill ( couleur, 1 ); fondBouton.graphics.drawRect ( 0, 0, 40, 110 ); // activation du mode bouton buttonMode = true; // désactivation des objets enfants mouseChildren = false; // affectation de la vitesse contrôlée affecteVitesse ( pVitesse ); // création de l'objet Tween interpolation = new Tween ( fondBouton, "scaleX", Bounce.easeOut, 1, 1, vitesse, true ); // écoute de l'événement MouseEvent.CLICK addEventListener ( MouseEvent.ROLL_OVER, survolSouris ); } En testant le code précédent, chaque bouton possède désormais une légende intégrant les contours de police : Chapitre 9 ? Etendre les classes natives ? version 0.1.2 76 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 9-25. Champs texte formatés. Il ne nous reste plus qu?à intégrer une gestion de la largeur et hauteur de chaque bouton. Pour cela nous ajoutons au constructeur de la classe Bouton deux paramètres pLargeur et pHauteur : public function Bouton ( pLargeur:Number, pHauteur:Number, pCouleur:Number, pVitesse:Number, pLegende:String ) { // ajoute chaque instance au tableau Bouton.tableauBoutons.push ( this ); // création du fond du bouton fondBouton = new Shape(); // ajout à la liste d'affichage addChild ( fondBouton ); // crée le champ texte legende = new TextField(); // redimensionnement automatique du champ texte legende.autoSize = TextFieldAutoSize.LEFT; // ajout à la liste d'affichage addChild ( legende ); // affecte la légende legende.text = pLegende; // active l'utilisation de police embarquée legende.embedFonts = true; Chapitre 9 ? Etendre les classes natives ? version 0.1.2 77 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org // crée un objet de formatage formatage = new TextFormat(); // taille de la police formatage.size = 12; // instanciation de la police embarquée var police:MaPolice = new MaPolice(); // affectation de la police au formatage formatage.font = police.fontName; // affectation du formatage au champ texte legende.setTextFormat ( formatage ); // rend le champ texte non selectionnable legende.selectable = false; // stocke la couleur passée en paramètre couleur = pCouleur; // dessine le bouton fondBouton.graphics.beginFill ( couleur, 1 ); fondBouton.graphics.drawRect ( 0, 0, pLargeur, pHauteur ); // activation du mode bouton buttonMode = true; // désactivation des objets enfants mouseChildren = false; // affectation de la vitesse contrôlée affecteVitesse ( pVitesse ); // création de l'objet Tween interpolation = new Tween ( fondBouton, "scaleX", Bounce.easeOut, 1, 1, vitesse, true ); // écoute de l'événement MouseEvent.CLICK addEventListener ( MouseEvent.ROLL_OVER, survolSouris ); } Puis nous passons les valeurs voulues lors de la création de chaque bouton : // création des boutons monBouton = new Bouton( 60, 120, couleur, vitesse, legende ); La figure 9-26 illustre le résultat final : Chapitre 9 ? Etendre les classes natives ? version 0.1.2 78 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 9-26. Boutons aux dimensions dynamiques. Nous obtenons un menu simple à mettre en place et facilement modifiable. Nous pourrions rajouter d?autres fonctionnalités comme une adaptation automatique de la largeur du bouton par rapport à la légende. En réalité, nous pourrions ne jamais nous arrêter ! Dans beaucoup de projets, les boutons d?un menu sont généralement liés à des SWF. Lorsque le bouton est cliqué, le SWF correspondant est chargé, cela permet un meilleur découpage du site et un chargement à la demande. Nous allons modifier la classe Bouton afin de pouvoir stocker dans chaque bouton le nom du SWF correspondant. Nous ajoutons une propriété swf : // swf associé private var swf:String; Puis nous modifions le constructeur afin d?accueillir le nom du SWF associé : public function Bouton ( pLargeur:Number, pHauteur:Number, pSWF:String, pCouleur:Number, pVitesse:Number, pLegende:String ) { // ajoute chaque instance au tableau Bouton.tableauBoutons.push ( this ); // création du fond du bouton fondBouton = new Shape(); Chapitre 9 ? Etendre les classes natives ? version 0.1.2 79 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org // ajout à la liste d'affichage addChild ( fondBouton ); // crée le champ texte legende = new TextField(); // redimensionnement automatique du champ texte legende.autoSize = TextFieldAutoSize.LEFT; // ajout à la liste d'affichage addChild ( legende ); // affecte la légende legende.text = pLegende; // active l'utilisation de police embarquée legende.embedFonts = true; // crée un objet de formatage formatage = new TextFormat(); // taille de la police formatage.size = 12; // instanciation de la police embarquée var police:MaPolice = new MaPolice(); // affectation de la police au formatage formatage.font = police.fontName; // affectation du formatage au champ texte legende.setTextFormat ( formatage ); // rend le champ texte non selectionnable legende.selectable = false; // stocke la couleur passée en paramètre couleur = pCouleur; // stocke le nom du SWF associé swf = pSWF; // dessine le bouton fondBouton.graphics.beginFill ( couleur, 1 ); fondBouton.graphics.drawRect ( 0, 0, pLargeur, pHauteur ); // activation du mode bouton buttonMode = true; // désactivation des objets enfants mouseChildren = false; // affectation de la vitesse contrôlée affecteVitesse ( pVitesse ); // création de l'objet Tween interpolation = new Tween ( fondBouton, "scaleX", Bounce.easeOut, 1, 1, vitesse, true ); // écoute de l'événement MouseEvent.CLICK addEventListener ( MouseEvent.ROLL_OVER, survolSouris ); Chapitre 9 ? Etendre les classes natives ? version 0.1.2 80 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org } Puis nous instancions les boutons du menu : // import de la classe Bouton import org.bytearray.ui.Bouton; // création d'un conteneur pour le menu var conteneurMenu:Sprite = new Sprite(); // tableau associatif contenant les données var donnees:Array = new Array(); donnees.push ( { legende : "Accueil", vitesse : 1, swf : "accueil.swf", couleur : 0x999900 } ); donnees.push ( { legende : "Photos", vitesse : 1, swf : "photos.swf", couleur : 0x881122 } ); donnees.push ( { legende : "Blog", vitesse : 1, swf : "blog.swf", couleur : 0x995471 } ); donnees.push ( { legende : "Liens", vitesse : 1, swf : "liens.swf", couleur : 0xCC21FF } ); donnees.push ( { legende : "Forum", vitesse : 1, swf : "forum.swf", couleur : 0x977821 } ); // nombre de rubriques var lng:int = donnees.length; var monBouton:Bouton; var legende:String; var couleur:Number; var vitesse:Number; var swf:String; for (var i:int = 0; i< lng; i++ ) { // récupération des infos legende = donnees[i].legende; couleur = donnees[i].couleur; vitesse = donnees[i].vitesse; swf = donnees[i].swf; // création des boutons monBouton = new Bouton( 60, 120, swf, couleur, vitesse, legende ); // positionnement monBouton.y = 50 * i; // ajout au sein du conteneur conteneurMenu.addChild ( monBouton ); } // affichage du menu addChild ( conteneurMenu ); Chaque bouton est ainsi lié à un SWF. Lors du chapitre 7 nous avons créé un menu dynamique qui nous redirigeait à une adresse spécifique en ouvrant une nouvelle fenêtre navigateur. Pour cela nous stockions Chapitre 9 ? Etendre les classes natives ? version 0.1.2 81 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org le lien au sein d?une propriété du bouton, puis au moment du clic nous accédions à celle-ci. Voici le code final de la classe Bouton : package org.bytearray.ui { import flash.display.Shape; import flash.display.Sprite; import flash.text.Font; import flash.text.TextFormat; // import des classes liées Tween au mouvement import fl.transitions.Tween; import fl.transitions.easing.Bounce; // import de la classe MouseEvent import flash.events.MouseEvent; // import de la classe TextField et TextFieldAutoSize import flash.text.TextField; import flash.text.TextFieldAutoSize; public class Bouton extends Sprite { // stocke le fond du bouton private var fondBouton:Shape; // stocke l'objet Tween pour les différents états du bouton private var interpolation:Tween; // stocke les références aux boutons private static var tableauBoutons:Array = new Array(); // stocke la couleur en cours du bouton private var couleur:Number; // stocke la vitesse d'ouverture de chaque bouton private var vitesse:Number; // légende du bouton private var legende:TextField; // formatage des légendes private var formatage:TextFormat; // swf associé private var swf:String; public function Bouton ( pLargeur:Number, pHauteur:Number, pSWF:String, pCouleur:Number, pVitesse:Number, pLegende:String ) { // ajoute chaque instance au tableau Bouton.tableauBoutons.push ( this ); // création du fond du bouton fondBouton = new Shape(); // ajout à la liste d'affichage addChild ( fondBouton ); // crée le champ texte legende = new TextField(); // redimensionnement automatique du champ texte Chapitre 9 ? Etendre les classes natives ? version 0.1.2 82 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org legende.autoSize = TextFieldAutoSize.LEFT; // ajout à la liste d'affichage addChild ( legende ); // affecte la légende legende.text = pLegende; // active l'utilisation de police embarquée legende.embedFonts = true; // crée un objet de formatage formatage = new TextFormat(); // taille de la police formatage.size = 12; // instanciation de la police embarquée var police:MaPolice = new MaPolice(); // affectation de la police au formatage formatage.font = police.fontName; // affectation du formatage au champ texte legende.setTextFormat ( formatage ); // rend le champ texte non selectionnable legende.selectable = false; // stocke la couleur passée en paramètre couleur = pCouleur; // stocke le nom du SWF swf = pSWF; // dessine le bouton fondBouton.graphics.beginFill ( couleur, 1 ); fondBouton.graphics.drawRect ( 0, 0, pLargeur, pHauteur ); // activation du mode bouton buttonMode = true; // désactivation des objets enfants mouseChildren = false; // affectation de la vitesse contrôlée affecteVitesse ( pVitesse ); // création de l'objet Tween interpolation = new Tween ( fondBouton, "scaleX", Bounce.easeOut, 1, 1, vitesse, true ); // écoute de l'événement MouseEvent.CLICK addEventListener ( MouseEvent.ROLL_OVER, survolSouris ); } // déclenché lors du survol du bouton private function survolSouris ( pEvt:MouseEvent ):void { // stocke la longueur du tableau Chapitre 9 ? Etendre les classes natives ? version 0.1.2 83 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org var lng:int = Bouton.tableauBoutons.length; for (var i:int = 0; i<lng; i++ ) Bouton.tableauBoutons[i].fermer(); // démarrage de l'animation interpolation.continueTo ( 2, vitesse ); } // méthode permettant de refermer le bouton private function fermer ():void { // referme le bouton interpolation.continueTo ( 1, vitesse ); } // gère l'affectation de la vitesse public function affecteVitesse ( pVitesse:Number ):void { // affecte la vitesse if ( pVitesse >= 1 && pVitesse <= 10 ) vitesse = pVitesse; else { trace("Erreur : Vitesse non correcte, la valeur doit être comprise entre 1 et 10"); vitesse = 1; } } } } Cette approche fonctionne sans problème et convient dans beaucoup de situations, en revanche lors de l?utilisation de classes personnalisées comme dans cette application nous allons externaliser les informations nécessaires grâce au modèle événementiel. De cette manière les objets « parleront » entre eux de manière faiblement couplée. Nous allons épouser le même modèle que les objets natifs d?ActionScript 3, chaque bouton pourra diffuser un événement spécifique nous renseignant sur sa couleur, sa vitesse ainsi que le SWF correspondant. Certains d?entre vous ont peut être déjà deviné vers quelle nouvelle notion nous nous dirigeons, en route pour le chapitre suivant intitulé Diffusion d?événements personnalisés. Chapitre 10 ? Diffusion d?événements personnalisés ? version 0.1.1 1 / 29 Thibault Imbert pratiqueactionscript3.bytearray.org 10 Diffusion d?événements personnalisés L?HISTOIRE............................................................................................................ 1 LA CLASSE EVENTDISPATCHER..................................................................... 2 MISE EN APPLICATION............................................................................................. 4 CHOISIR UN NOM D?ÉVÉNEMENT............................................................................. 9 ETENDRE EVENTDISPATCHER...................................................................... 11 STOCKER EVENTDISPATCHER ..................................................................... 14 PASSER DES INFORMATIONS.................................................................................. 19 MENU ET ÉVÉNEMENT PERSONNALISÉ .................................................................. 23 L?histoire Dans une application ActionScript, la communication inter objets se réalise majoritairement par la diffusion d?événements natifs ou personnalisés. Mais qu?est ce qu?un événement personnalisé ? Il s?agit d?un événement qui n?existe pas au sein du lecteur Flash mais que nous ajoutons afin de gérer les différentes interactions entre nos objets. Depuis très longtemps ActionScript intègre différentes classes permettant de diffuser nos propres événements. La première fut AsBroadcaster intégrée depuis le lecteur Flash 5, son implémentation native l?avait rendu favorite des développeurs ActionScript. Sa remplaçante BroadcasterMX introduite plus tard par Flash MX et codée en ActionScript fut moins bien accueillie. Aujourd?hui ces classes n?existent plus en ActionScript 3 et sont remplacées par la classe native flash.events.EventDispatcher. Chapitre 10 ? Diffusion d?événements personnalisés ? version 0.1.1 2 / 29 Thibault Imbert pratiqueactionscript3.bytearray.org Au sein d?une interface graphique nous pourrions imaginer un bouton diffusant en événement indiquant son état actif ou inactif. Une classe générant des fichiers PDF pourrait diffuser des événements relatifs au processus de création du fichier. Ainsi, la diffusion d?événements personnalisés constitue la suite logique à la création d?objets personnalisés. La classe EventDispatcher Comme nous le voyons depuis le début de l?ouvrage, ActionScript 3 est un langage basé sur un modèle événementiel appelé Document Object Model. Celui-ci repose sur la classe EventDispatcher dont toutes les classes natives de l?API du lecteur Flash héritent. Dans le code suivant nous voyons la relation entre la classe EventDispatcher et deux classes graphiques : var monSprite:Sprite = new Sprite(); // affiche : true trace( monSprite is EventDispatcher ); var monClip:MovieClip = new MovieClip(); // affiche : true trace( monClip is EventDispatcher ); Rappelez-vous, toutes les classes issues du paquetage flash sont des sous-classes d?EventDispatcher et possèdent donc ce type commun. Nous créons un Sprite puis nous écoutons un événement personnalisé auprès de ce dernier : // création d'un Sprite var monSprite:Sprite = new Sprite(); // écoute de l'événement monEvent monSprite.addEventListener ( "monEvenement", ecouteur ); // fonction écouteur function ecouteur ( pEvt:Event ):void { trace( pEvt ); } La classe Sprite ne diffuse pas par défaut d?événement nommé monEvenement, mais nous pouvons le diffuser simplement grâce à la méthode dispatchEvent dont voici la signature : public function dispatchEvent(event:Event):Boolean Chapitre 10 ? Diffusion d?événements personnalisés ? version 0.1.1 3 / 29 Thibault Imbert pratiqueactionscript3.bytearray.org En ActionScript 2 la méthode dispatchEvent diffusait un objet événementiel non typé. Nous utilisions généralement un objet littéral auquel nous ajoutions différentes propriétés manuellement. En ActionScript 3, afin de diffuser un événement nous devons créer un objet événementiel, représenté par une instance de la classe flash.events.Event : // création de l'objet événementiel var objetEvenementiel:Event = new Event (type, bubbles, cancelable); Le constructeur de la classe Event accepte trois paramètres : ? type : le nom de l?événement à diffuser. ? bubbles : indique si l?événement participe à la phase de remontée. ? cancelable : indique si l?événement peut être annulé. Dans la plupart des situations nous n?utilisons que le premier paramètre type de la classe Event. Une fois l?objet événementiel créé, nous le passons à la méthode dispatchEvent : // création d'un sprite var monSprite:Sprite = new Sprite(); // écoute de l'événement monEvent monSprite.addEventListener ( "monEvenement", ecouteur ); // fonction écouteur function ecouteur (pEvt:Event):void { // affiche [Event type="monEvenement" bubbles=false cancelable=false eventPhase=2] trace( pEvt ); } // création de l'objet événementiel var objetEvenementiel:Event = new Event (?monEvenement?, bubbles, cancelable); // nous diffusons l'événement monEvenement monSprite.dispatchEvent (objetEvenementiel); Généralement, nous ne créons pas l?objet événementiel séparément, nous l?instancions directement en paramètre de la méthode dispatchEvent : // création d'un Sprite var monSprite:Sprite = new Sprite(); // écoute de l'événement monEvent monSprite.addEventListener ("monEvenement", ecouteur ); Chapitre 10 ? Diffusion d?événements personnalisés ? version 0.1.1 4 / 29 Thibault Imbert pratiqueactionscript3.bytearray.org // fonction écouteur function ecouteur ( pEvt:Event ):void { // affiche [Event type="monEvenement" bubbles=false cancelable=false eventPhase=2] trace( pEvt ); } // nous diffusons l'événement monEvenement monSprite.dispatchEvent ( new Event (?monEvenement?) ); Cet exemple nous montre la facilité avec laquelle nous pouvons diffuser un événement en ActionScript 3. A retenir ? Le modèle événementiel ActionScript 3 repose sur la classe EventDispatcher. ? Toutes les classes issues du paquetage flash peuvent diffuser des événements natifs ou personnalisés. ? Afin de diffuser un événement, nous utilisons la méthode dispatchEvent. ? La méthode dispatchEvent accepte comme paramètre une instance de la classe Event. Mise en application Nous allons développer une classe permettant à un symbole de se déplacer dans différentes directions. Lorsque celui-ci arrive à destination nous souhaitons diffuser un événement approprié. A côté d?un nouveau document Flash CS3, nous sauvons une classe nommée Balle.as contenant le code suivant : package { import flash.display.Sprite; // la classe Balle étend la classe Sprite public class Balle extends Sprite { public function Balle () { trace( this ); Chapitre 10 ? Diffusion d?événements personnalisés ? version 0.1.1 5 / 29 Thibault Imbert pratiqueactionscript3.bytearray.org } } } Nous lions notre symbole de forme circulaire à celle-ci par le panneau Propriétés de liaison. Puis nous instancions le symbole : // création du symbole // affiche : [object Balle] var maBalle:Balle = new Balle(); // ajout à la liste d'affichage addChild ( maBalle ); A chaque clic souris sur la scène, la balle doit se diriger vers le point cliqué. Nous devons donc écouter l?événement MouseEvent.CLICK de manière globale auprès de l?objet Stage. Nous avons vu au cours du précédent chapitre comment accéder de manière sécurisée à l?objet Stage. Nous intégrons le même mécanisme dans la classe Balle : package { import flash.display.Sprite; import flash.events.MouseEvent; import flash.events.Event; // la classe Balle étend la classe Sprite public class Balle extends Sprite { public function Balle () { // écoute de l'événement Event.ADDED_TO_STAGE addEventListener ( Event.ADDED_TO_STAGE, ajoutAffichage ); } private function ajoutAffichage( pEvt:Event ):void { // écoute de l'événement MouseEvent.CLICK stage.addEventListener ( MouseEvent.CLICK, clicSouris ); } private function clicSouris ( pEvt:MouseEvent ):void { Chapitre 10 ? Diffusion d?événements personnalisés ? version 0.1.1 6 / 29 Thibault Imbert pratiqueactionscript3.bytearray.org // affiche : [MouseEvent type="click" bubbles=true cancelable=false eventPhase=2 localX=81 localY=127 stageX=81 stageY=127 relatedObject=null ctrlKey=false altKey=false shiftKey=false delta=0] trace( pEvt ); } } } A chaque clic sur la scène, l?événement MouseEvent.CLICK est diffusé, la fonction écouteur clicSouris est alors déclenchée. Nous allons intégrer à présent la notion de mouvement. Nous définissons deux propriétés sourisX et sourisY au sein de la classe. Celles-ci vont nous permettre de stocker la position de la souris : // stocke les coordonnées de la souris private var sourisX:Number; private var sourisY:Number; Puis nous modifions la méthode clicSouris afin d?affecter ces propriétés : package { import flash.display.Sprite; import flash.events.MouseEvent; import flash.events.Event; // la classe Balle étend la classe Sprite public class Balle extends Sprite { // stocke les coordonnées de la souris private var sourisX:Number; private var sourisY:Number; public function Balle () { // écoute de l'événement Event.ADDED_TO_STAGE addEventListener ( Event.ADDED_TO_STAGE, ajoutAffichage ); } private function ajoutAffichage( pEvt:Event ):void { // écoute de l'événement MouseEvent.CLICK stage.addEventListener ( MouseEvent.CLICK, clicSouris ); } private function clicSouris ( pEvt:MouseEvent ):void Chapitre 10 ? Diffusion d?événements personnalisés ? version 0.1.1 7 / 29 Thibault Imbert pratiqueactionscript3.bytearray.org { // affecte les coordonnées aux propriétés sourisX = pEvt.stageX; sourisY = pEvt.stageY; } } } Enfin, nous déclenchons le mouvement en écoutant l?événement Event.ENTER_FRAME hérité de la classe Sprite : package { import flash.display.Sprite; import flash.events.MouseEvent; import flash.events.Event; // la classe Balle étend la classe Sprite public class Balle extends Sprite { // stocke les coordonnées de la souris private var sourisX:Number; private var sourisY:Number; public function Balle () { // écoute de l'événement Event.ADDED_TO_STAGE addEventListener ( Event.ADDED_TO_STAGE, ajoutAffichage ); } private function ajoutAffichage( pEvt:Event ):void { // écoute de l'événement MouseEvent.CLICK stage.addEventListener ( MouseEvent.CLICK, clicSouris ); } private function clicSouris ( pEvt:MouseEvent ):void { // affecte les coordonnées aux propriétés sourisX = pEvt.stageX; sourisY = pEvt.stageY; addEventListener ( Event.ENTER_FRAME, mouvement ); } Chapitre 10 ? Diffusion d?événements personnalisés ? version 0.1.1 8 / 29 Thibault Imbert pratiqueactionscript3.bytearray.org private function mouvement ( pEvt:Event ):void { // évalue la destination x et y var destinationX:Number = ( sourisX - width/2); var destinationY:Number = ( sourisY - height/2); // déplace la balle avec un effet de ralentissement (inertie) x -= (x - destinationX)*.1; y -= (y - destinationY)*.1; } } } Si nous testons notre animation, la balle se déplace à l?endroit cliqué avec un effet de ralenti. La figure 10.1 illustre le comportement. Figure 10.1 Déplacement position clic souris. Lorsqu?un mouvement entre en jeu, nous souhaitons généralement savoir quand est ce qu?il se termine. La classe Tween diffuse par défaut tous les événements nécessaires à la synchronisation d?une animation. Mais comment faire dans notre cas pour diffuser notre propre événement ? La classe Sprite hérite de la classe EventDispatcher et peut donc diffuser n?importe quel événement. Au sein de la méthode Chapitre 10 ? Diffusion d?événements personnalisés ? version 0.1.1 9 / 29 Thibault Imbert pratiqueactionscript3.bytearray.org mouvement nous testons si la différence entre la position en cours et la destination est inférieure à 1. Si c?est le cas, cela signifie que nous sommes arrivés à destination. private function mouvement ( pEvt:Event ):void { // évalue la destination x et y var destinationX:Number = ( sourisX - width/2); var destinationY:Number = ( sourisY - height/2); // déplace la balle avec un effet de ralentissement (inertie) x -= (x - destinationX)*.1; y -= (y - destinationY)*.1; if ( Math.abs ( x - destinationX ) < 1 && Math.abs ( y - destinationY ) < 1 ) { removeEventListener ( Event.ENTER_FRAME, mouvement ); trace ("arrivé à destination !"); } } La méthode Math.abs nous permet de rendre la distance absolue, car une distance entre deux points est toujours positive. Nous supprimons l?écoute de l?événement Event.ENTER_FRAME lorsque la balle arrive à destination afin d?optimiser les ressources, puis nous affichons un message indiquant que la balle est arrivée. En testant notre animation, nous remarquons que le message indiquant l?arrivée est bien déclenché, mais pour l?instant aucun événement n?est diffusé. Choisir un nom d?événement Lorsqu?un objet diffuse un événement nous devons nous assurer que son nom soit simple et intuitif pour les personnes utilisant la classe. Dans notre exemple nous allons diffuser un événement mouvementTermine. Nous modifions la méthode mouvement afin que celle-ci le diffuse : private function mouvement ( pEvt:Event ):void { // évalue la destination x et y var destinationX:Number = ( sourisX - width/2); var destinationY:Number = ( sourisY - height/2); Chapitre 10 ? Diffusion d?événements personnalisés ? version 0.1.1 10 / 29 Thibault Imbert pratiqueactionscript3.bytearray.org // déplace la balle avec un effet de ralentissement (inertie) x -= (x - destinationX)*.1; y -= (y - destinationY)*.1; if ( Math.abs ( x - destinationX ) < 1 && Math.abs ( y - destinationY ) < 1 ) { removeEventListener ( Event.ENTER_FRAME, mouvement ); // diffusion de l'événement motionComplete dispatchEvent ( new Event ("mouvementTermine") ); } } Notre symbole balle diffuse désormais un événement mouvementTermine. Il ne nous reste plus qu?à l?écouter : // création du symbole // affiche : [object Balle] var maBalle:Balle = new Balle(); // ajout à la liste d'affichage addChild ( maBalle ); // écoute de l'événement personnalisé motionComplete maBalle.addEventListener ( "mouvementTermine", arrivee ); // fonction écouteur function arrivee ( pEvt:Event ):void { trace("mouvement terminé !"); } La fonction écouteur arrivee est déclenchée lorsque la balle arrive à destination. En cas de réutilisation de la classe Balle nous savons que celle-ci diffuse l?événement mouvementTermine. Libre à nous de décider quoi faire lorsque l?événement est diffusé, la réutilisation de la classe Balle est donc facilitée. Notre code fonctionne très bien, mais il n?est pas totalement optimisé. Voyez-vous ce qui pose problème ? Souvenez-vous, lors du chapitre 3 intitulé Le modèle événementiel, nous avons vu que nous ne qualifions jamais le nom des événements directement. Cela rend notre code rigide et non standard. Nous préférons l?utilisation de constantes de classe. Nous définissons donc une propriété constante MOUVEMENT_TERMINE au sein de la classe Balle contenant le nom de l?événement diffusé : Chapitre 10 ? Diffusion d?événements personnalisés ? version 0.1.1 11 / 29 Thibault Imbert pratiqueactionscript3.bytearray.org // stocke le nom de l'événement diffusé public static const MOUVEMENT_TERMINE:String = "mouvementTermine"; Puis nous ciblons la propriété afin d?écouter l?événement : // écoute de l'événement personnalisé Balle.MOUVEMENT_TERMINE maBalle.addEventListener ( Balle.MOUVEMENT_TERMINE, arrivee ); Nous modifions la méthode mouvement afin de cibler la constante Balle.MOUVEMENT_TERMINE : private function mouvement ( pEvt:Event ):void { // évalue la destination x et y var destinationX:Number = ( sourisX - width/2); var destinationY:Number = ( sourisY - height/2); // déplace la balle avec un effet de ralentissement (inertie) x -= (x - destinationX)*.1; y -= (y - destinationY)*.1; if ( Math.abs ( x - destinationX ) < 1 && Math.abs ( y - destinationY ) < 1 ) { removeEventListener ( Event.ENTER_FRAME, mouvement ); // diffusion de l'événement motionComplete dispatchEvent ( new Event ( Balle.MOUVEMENT_TERMINE ) ); } } Nous venons de traiter à travers cet exemple le cas le plus courant lors de la diffusion d?événements personnalisés. Voyons maintenant d?autres cas. A retenir ? Un événement doit porter un nom simple et intuitif. ? Afin de stocker le nom d?un événement nous utilisons toujours une propriété constante de classe. Etendre EventDispatcher Rappelez-vous que toutes les classes résidant dans le paquetage flash héritent de la classe EventDispatcher. Dans vos développements, certaines classes n?hériteront pas de classes natives et n?auront pas la possibilité de diffuser des événements par défaut. Chapitre 10 ? Diffusion d?événements personnalisés ? version 0.1.1 12 / 29 Thibault Imbert pratiqueactionscript3.bytearray.org Prenons le cas d?une classe nommée XMLLoader devant charger des données XML. Nous souhaitons indiquer la fin du chargement des données en diffusant un événement approprié. Le code suivant illustre le contenu de la classe : package { import flash.events.Event; public class XMLLoader { public function XMLLoader () { // code non indiqué } public function charge ( pXML:String ):void { // code non indiqué } public function chargementTermine ( pEvt:Event ):void { dispatchEvent ( new Event ( XMLLoader.COMPLETE ) ); } } } Si nous tentons de compiler cette classe, un message d?erreur nous avertira qu?aucune méthode dispatchEvent n?existe au sein de la classe. Afin d?obtenir les capacités de diffusion la classe XMLLoader hérite de la classe EventDispatcher : package { import flash.events.Event; import flash.events.EventDispatcher; public class XMLLoader extends EventDispatcher { public static const COMPLETE:String = "complete"; Chapitre 10 ? Diffusion d?événements personnalisés ? version 0.1.1 13 / 29 Thibault Imbert pratiqueactionscript3.bytearray.org public function XMLLoader () { // code non indiqué } public function charge ( pXML:String ):void { // code non indiqué } public function chargementTermine ( pEvt:Event ):void { dispatchEvent ( new Event ( XMLLoader.COMPLETE ) ); } } } En sous classant EventDispatcher, la classe XMLLoader peut désormais diffuser des événements. Afin d?écouter l?événement XMLLoader.COMPLETE, nous écrivons le code suivant : // création d'une instance de XMLLoader var chargeurXML:XMLLoader = new XMLLoader(); // chargement des données chargeurXML.charge ("news.xml"); // écoute de l'événement XMLLoader.COMPLETE chargeurXML.addEventListener ( XMLLoader.COMPLETE, chargementTermine ); // fonction écouteur function chargementTermine ( pEvt:Event ):void { trace( "données chargées"); } Lorsque nous appelons la méthode charge, les données XML sont chargées. Une fois le chargement terminé nous diffusons l?événement XMLLoader.COMPLETE. Une partie du code de la classe XMLLoader n?est pas montré car nous n?avons pas encore traité la notion de chargement externe. Nous Chapitre 10 ? Diffusion d?événements personnalisés ? version 0.1.1 14 / 29 Thibault Imbert pratiqueactionscript3.bytearray.org reviendrons sur cette classe au cours du chapitre 14 intitulé Chargement et envoi de données afin de la compléter. Bien que cette technique soit efficace, elle ne révèle pas être la plus optimisée. Comme nous l?avons vu lors du chapitre 8 intitulé Programmation orientée objet, ActionScript n?intègre pas d?héritage multiple. Ainsi, en héritant de la classe EventDispatcher la classe XMLLoader ne peut hériter d?une autre classe. Nous cassons la chaîne d?héritage. Pire encore, si la classe XMLLoader devait étendre une autre classe, nous ne pourrions étendre en même temps EventDispatcher. Dans un scénario comme celui-ci nous allons utiliser une notion essentielle de la programmation orientée objet abordée au cours du chapitre 8. Peut être avez vous déjà une idée ? Stocker EventDispatcher Afin de bien comprendre cette nouvelle notion, nous allons nous attarder sur la classe Administrateur développée lors du chapitre 8. Voici le code de la classe Administrateur : package { // l'héritage est traduit par le mot clé extends public class Administrateur extends Joueur { public function Administrateur ( pPrenom:String, pNom:String, pAge:int, pVille:String ) { super ( pPrenom, pNom, pAge, pVille ); } // méthode permettant à l'administrateur de se présenter override public function sePresenter ( ):void { // déclenche la méthode surchargée super.sePresenter(); trace("Je suis modérateur"); } // méthode permettant de supprimer un joueur de la partie public function kickJoueur ( pJoueur:Joueur ):void Chapitre 10 ? Diffusion d?événements personnalisés ? version 0.1.1 15 / 29 Thibault Imbert pratiqueactionscript3.bytearray.org { trace ("Kick " + pJoueur ); } // méthode permettant de jouer un son public function jouerSon ( ):void { trace("Joue un son"); } } } En découvrant la notion d?événements personnalisés nous décidons de diffuser un événement lorsqu?un joueur est supprimé de la partie. Il faudrait donc que la méthode kickJoueur puisse appeler la méthode dispatchEvent, ce qui est impossible pour le moment. La classe Administrateur ne peut par défaut diffuser des événements, nous tentons donc d?étendre la classe EventDispatcher. Nous sommes alors confrontés à un problème, car la classe Administrateur étend déjà la classe Joueur. Comment allons nous faire, sommes nous réellement bloqués ? Souvenez vous, nous avons vu au cours du chapitre 8 une alternative à l?héritage. Cette technique décrivait une relation de type « possède un » au lieu d?une relation de type « est un ». En résumé, au lieu d?étendre une classe pour hériter de ses capacités nous allons créer une instance de celle-ci au sein de la classe et déléguer les fonctionnalités. Ainsi au lieu de sous classer EventDispatcher nous allons stocker une instance de la classe EventDispatcher et déléguer la gestion des événements à celle-ci : package { import flash.events.EventDispatcher; // l'héritage est traduit par le mot clé extends public class Administrateur extends Joueur { // stocke l'instance d'EventDispatcher private var diffuseur:EventDispatcher; Chapitre 10 ? Diffusion d?événements personnalisés ? version 0.1.1 16 / 29 Thibault Imbert pratiqueactionscript3.bytearray.org public function Administrateur ( pPrenom:String, pNom:String, pAge:int, pVille:String ) { super ( pPrenom, pNom, pAge, pVille ); // création d'une instance d'EventDispatcher diffuseur = new EventDispatcher(); } // méthode permettant à l'administrateur de se présenter override public function sePresenter ( ):void { // déclenche la méthode surchargée super.sePresenter(); trace("Je suis modérateur"); } // méthode permettant de supprimer un joueur de la partie public function kickJoueur ( pJoueur:Joueur ):void { // code gérant la déconnexion du joueur concerné trace ("Kick " + pJoueur ); } // méthode permettant de jouer un son public function jouerSon ( ):void { trace("Joue un son"); } } } Bien entendu, la classe Administrateur ne possède pas pour le moment les méthodes dispatchEvent, addEventListener, et removeEventListener. C?est à nous de les implémenter, pour cela nous implémentons l?interface flash.events.IEventDispatcher : package { import flash.events.EventDispatcher; import flash.events.IEventDispatcher; import flash.events.Event; Chapitre 10 ? Diffusion d?événements personnalisés ? version 0.1.1 17 / 29 Thibault Imbert pratiqueactionscript3.bytearray.org // l'héritage est traduit par le mot clé extends public class Administrateur extends Joueur implements IEventDispatcher { // stocke l'instance d'EventDispatcher private var diffuseur:EventDispatcher; public function Administrateur ( pPrenom:String, pNom:String, pAge:int, pVille:String ) { super ( pPrenom, pNom, pAge, pVille ); // création d'une instance d'EventDispatcher diffuseur = new EventDispatcher(); } // méthode permettant à l'administrateur de se présenter override public function sePresenter ( ):void { // déclenche la méthode surchargée super.sePresenter(); trace("Je suis modérateur"); } // méthode permettant de supprimer un joueur de la partie public function kickJoueur ( pJoueur:Joueur ):void { // code gérant la déconnexion du joueur concerné trace ("Kick " + pJoueur ); } // méthode permettant de jouer un son public function jouerSon ( ):void { trace("Joue un son"); } public function addEventListener( type:String, listener:Function, useCapture:Boolean=false, priority:int=0, useWeakReference:Boolean=false ):void { diffuseur.addEventListener( type, listener, useCapture, priority, useWeakReference ); Chapitre 10 ? Diffusion d?événements personnalisés ? version 0.1.1 18 / 29 Thibault Imbert pratiqueactionscript3.bytearray.org } public function dispatchEvent( event:Event ):Boolean { return diffuseur.dispatchEvent( event ); } public function hasEventListener( type:String ):Boolean { return diffuseur.hasEventListener( type ); } public function removeEventListener( type:String, listener:Function, useCapture:Boolean=false ):void { diffuseur.removeEventListener( type, listener, useCapture ); } public function willTrigger( type:String ):Boolean { return diffuseur.willTrigger( type ); } } } Afin de rendre notre classe Administrateur diffuseur d?événements nous implémentons l?interface IEventDispatcher. Chacune des méthodes définissant le type EventDispatcher doivent donc être définies au sein de la classe Administrateur. Nous définissons une constante de classe KICK_JOUEUR, stockant le nom de l?événement : public static const KICK_JOUEUR:String = "deconnecteJoueur"; Puis nous modifions la méthode kickJoueur afin de diffuser un événement Administrateur.KICK_JOUEUR : // méthode permettant de supprimer un joueur de la partie public function kickJoueur ( pJoueur:Joueur ):void { // code gérant la déconnexion du joueur concerné // diffuse l'événement Administrateur.KICK_JOUEUR Chapitre 10 ? Diffusion d?événements personnalisés ? version 0.1.1 19 / 29 Thibault Imbert pratiqueactionscript3.bytearray.org dispatchEvent ( new Event ( Administrateur.KICK_JOUEUR ) ); } Dans le code suivant, nous créons un modérateur et un joueur. Le joueur est supprimé de la partie, l?événement Administrateur.KICK_JOUEUR est bien diffusé : var monModo:Administrateur = new Administrateur("Michael", "Jackson", 48, "Los Angeles"); // écoute l'événement Administrateur.KICK_JOUEUR monModo.addEventListener (Administrateur.KICK_JOUEUR, deconnexionJoueur ); var premierJoueur:Joueur = new Joueur ("Bobby", "Womack", 66, "Detroit"); // un joueur est supprimé de la partie monModo.kickJoueur ( premierJoueur ); // fonction écouteur function deconnexionJoueur ( pEvt:Event ):void { trace( "Un joueur a quitté la partie !" ); } Grâce à la composition, nous avons pu rendre la classe Administrateur diffuseur d?événements. L?héritage n?est donc pas la seule alternative permettant de rendre une classe diffuseur d?événements. A retenir ? Lorsque nous ne pouvons pas étendre la classe EventDispatcher nous stockons une instance de celle-ci et déléguons les fonctionnalités. ? L?interface IeventDispatcher permet une implémentation obligatoire des différentes méthodes nécessaires à la diffusion d?événements. Passer des informations La plupart des événements diffusés contiennent différentes informations relatives à l?état de l?objet diffusant l?événement. Nous avons vu lors du chapitre 6 intitulé Intéractivité que les classes MouseEvent ou KeyboardEvent possédaient des propriétés renseignant sur l?état de l?objet diffusant l?événement. Dans l?exercice précédent, la classe Administrateur diffuse un événement Administrateur.KICK_JOUEUR mais celui-ci ne contient aucune information. Il serait intéressant que celui-ci nous renseigne sur le joueur supprimé. De la même manière la classe Chapitre 10 ? Diffusion d?événements personnalisés ? version 0.1.1 20 / 29 Thibault Imbert pratiqueactionscript3.bytearray.org XMLLoader pourrait diffuser un événement XMLoader.COMPLETE contenant le flux XML chargé afin qu?il soit facilement utilisable par les écouteurs. Afin de passer des informations lors de la diffusion d?un événement nous devons étendre la classe Event afin de créer un objet événementiel spécifique à l?événement diffusé. En réalité, nous nous plions au modèle définit par ActionScript 3. Lorsque nous écoutons un événement lié à la souris nous nous dirigeons instinctivement vers la classe flash.events.MouseEvent. De la même manière pour écouter des événements liés au clavier nous utilisons la classe flash.events.KeyboardEvent. De manière générale, il convient de créer une classe événementielle pour chaque classe devant diffuser des événements contenant des informations particulières. Nous allons donc étendre la classe Event et créer une classe nommée AdministrateurEvent : package { import flash.events.Event; public class AdministrateurEvent extends Event { public static const KICK_JOUEUR:String = "deconnecteJoueur"; public function AdministrateurEvent ( type:String, bubbles:Boolean=false, cancelable:Boolean=false ) { // initialisation du constructeur de la classe Event super( type, bubbles, cancelable ); } // la méthode clone doit être surchargée public override function clone ():Event { return new AdministrateurEvent ( type, bubbles, cancelable ) } // la méthode toString doit être surchargée public override function toString ():String { Chapitre 10 ? Diffusion d?événements personnalisés ? version 0.1.1 21 / 29 Thibault Imbert pratiqueactionscript3.bytearray.org return '[AdministrateurEvent type="'+ type +'" bubbles=' + bubbles + ' cancelable=' + cancelable + ']'; } } } Puis nous utilisons une instance de cette classe afin de diffuser l?événement : // méthode permettant de supprimer un joueur de la partie public function kickJoueur ( pJoueur:Joueur ):void { // code gérant la déconnexion du joueur concerné // diffuse l'évenement AdministrateurEvent.KICK_JOUEUR dispatchEvent ( new AdministrateurEvent ( AdministrateurEvent.KICK_JOUEUR ) ); } Il est important de noter que jusqu?à présent nous n?avions diffusé des événements qu?avec la classe Event. Lorsque nous définissons une classe événementielle spécifique nous stockons la constante de classe dans celle-ci. Ainsi au lieu de cibler le nom de l?événement sur la classe diffusant l?événement : // écoute l'événement Administrateur.KICK_JOUEUR monModo.addEventListener ( Administrateur.KICK_JOUEUR, joueurQuitte ); Nous préférerons stocker le nom de l?événement au sein d?une constante de la classe événementielle : // écoute l'événement AdministrateurEvent.KICK_JOUEUR monModo.addEventListener ( AdministrateurEvent.KICK_JOUEUR, joueurQuitte ); A ce stade, aucune information ne transite par l?objet événementiel AdministrateurEvent. Afin de stocker des données supplémentaires nous définissons les propriétés voulues au sein de la classe puis nous affectons leur valeur selon les paramètres passés durant l?instanciation de l?objet événementiel : package { import flash.events.Event; public class AdministrateurEvent extends Event { public static const KICK_JOUEUR:String = "deconnecteJoueur"; public var joueur:Joueur; Chapitre 10 ? Diffusion d?événements personnalisés ? version 0.1.1 22 / 29 Thibault Imbert pratiqueactionscript3.bytearray.org public function AdministrateurEvent ( type:String, bubbles:Boolean=false, cancelable:Boolean=false, pJoueur:Joueur=null ) { // initialisation du constructeur de la classe Event super( type, bubbles, cancelable ); // stocke le joueur supprimé joueur = pJoueur; } // la méthode clone doit être surchargée public override function clone ():Event { return new AdministrateurEvent ( type, bubbles, cancelable ) } // la méthode toString doit être surchargée public override function toString ():String { return "[AdministrateurEvent type : " + type +", bubbles : " + bubbles + ", cancelable : " + cancelable + "]"; } } } Puis nous modifions la méthode kickJoueur de la classe Administrateur afin de passer en paramètre le joueur supprimé : // méthode permettant de supprimer un joueur de la partie public function kickJoueur ( pJoueur:Joueur ):void { // code gérant la déconnexion du joueur concerné // diffuse l'événement Administrateur.KICK_JOUEUR dispatchEvent ( new AdministrateurEvent ( AdministrateurEvent. KICK_JOUEUR, false, false, pJoueur ) ); } Lorsque l?événement est diffusé, la fonction écouteur accède à la propriété joueur afin de savoir quel joueur a quitté la partie : var monModo:Administrateur = new Administrateur("Michael", "Jackson", 48, "Los Angeles"); // écoute l'événement Administrateur.KICK_JOUEUR monModo.addEventListener ( AdministrateurEvent.KICK_JOUEUR, joueurQuitte ); var premierJoueur:Joueur = new Joueur ("Bobby", "Womack", 66, "Detroit"); Chapitre 10 ? Diffusion d?événements personnalisés ? version 0.1.1 23 / 29 Thibault Imbert pratiqueactionscript3.bytearray.org // un joueur est supprimé de la partie monModo.kickJoueur ( premierJoueur ); // fonction écouteur function joueurQuitte ( pEvt:AdministrateurEvent ):void { // affiche : [AdministrateurEvent type="deconnecteJoueur" bubbles=false cancelable=false] trace( pEvt ); // affiche Bobby a quitté la partie ! trace( pEvt.joueur.prenom + " a quitté la partie !"); } En testant le code précédent, fonction écouteur joueurQuitte est notifié de l?événement AdministrateurEvent.KICK_JOUEUR et reçoit en paramètre un objet événementiel de type AdministrateurEvent. La propriété joueur retourne le joueur supprimé de la partie, nous n?avons plus qu?à accéder aux propriétés voulues. A retenir ? Afin de passer des paramètres lors de la diffusion d?un événement, nous devons obligatoirement étendre la classe Event. ? Les classes et sous-classes d?Event représentent les objets événementiels diffusés. Menu et événement personnalisé En fin de chapitre précédent nous avons développé un menu entièrement dynamique. Nous ne l?avions pas totalement finalisé car il nous manquait la notion d?événements personnalisés. Souvenez-vous, nous avions besoin de diffuser un événement qui contiendrait le nom du SWF lié au bouton cliqué. Au sein d?un répertoire evenements lui-même placé au sein du répertoire org nous créons une classe ButtonEvent : package org.bytearray.evenements { import flash.events.Event; public class ButtonEvent extends Event { Chapitre 10 ? Diffusion d?événements personnalisés ? version 0.1.1 24 / 29 Thibault Imbert pratiqueactionscript3.bytearray.org public static const CLICK:String = "buttonClick"; public var lien:String; public function ButtonEvent ( type:String, bubbles:Boolean=false, cancelable:Boolean=false, pLien:String=null ) { // initialisation du constructeur de la classe Event super( type, bubbles, cancelable ); // stocke le lien lié au bouton cliqué lien = pLien; } // la méthode clone doit être surchargée public override function clone ():Event { return new ButtonEvent ( type, bubbles, cancelable, lien ) } // la méthode toString doit être surchargée public override function toString ():String { return '[ButtonEvent type="'+ type +'" bubbles=' + bubbles + ' eventPhase='+ eventPhase + ' cancelable=' + cancelable + ']'; } } } Dans le constructeur nous ajoutons la ligne suivante afin d?écouter l?événement MouseEvent.CLICK : // écoute de l'événement MouseEvent.CLICK addEventListener ( MouseEvent.CLICK, clicSouris ); Nous définissons la méthode écouteur clicSouris qui diffuse l?événement ButtonEvent.CLICK en passant le SWF correspondant : private function clicSouris ( pEvt:MouseEvent ):void { // diffusion de l'événement ButtonEvent.CLICK dispatchEvent ( new ButtonEvent ( ButtonEvent.CLICK, true, false, swf ) ); } Chapitre 10 ? Diffusion d?événements personnalisés ? version 0.1.1 25 / 29 Thibault Imbert pratiqueactionscript3.bytearray.org Puis nous écoutons l?événement ButtonEvent.CLICK auprès de chaque bouton, nous utilisons la phase de capture afin d?optimiser le code : // import de la classe Bouton import org.bytearray.ui.Button; import org.bytearray.evenements.ButtonEvent; // tableau associatif contenant les données var donnees:Array = new Array(); donnees.push ( { legende : "Accueil", vitesse : 1, swf : "accueil.swf", couleur : 0x999900 } ); donnees.push ( { legende : "Photos", vitesse : 1, swf : "photos.swf", couleur : 0x881122 } ); donnees.push ( { legende : "Blog", vitesse : 1, swf : "blog.swf", couleur : 0x995471 } ); donnees.push ( { legende : "Liens", vitesse : 1, swf : "liens.swf", couleur : 0xCC21FF } ); donnees.push ( { legende : "Forum", vitesse : 1, swf : "forum.swf", couleur : 0x977821 } ); // nombre de rubriques var lng:int = donnees.length; // conteneur du menu var conteneurMenu:Sprite = new Sprite(); addChild ( conteneurMenu ); for (var i:int = 0; i< lng; i++ ) { // récupération des infos var legende:String = donnees[i].legende; var couleur:Number = donnees[i].couleur; var vitesse:Number = donnees[i].vitesse; var swf:String = donnees[i].swf; // création des boutons var monBouton:Button = new Button( 60, 120, swf, couleur, vitesse, legende ); // positionnement monBouton.y = 50 * i; // ajout à la liste d'affichage conteneurMenu.addChild ( monBouton ); } // écoute de l'événement ButtonEvent.CLICK pour la phase de capture conteneurMenu.addEventListener ( ButtonEvent.CLICK, clicBouton, true ); function clicBouton ( pEvt:ButtonEvent ):void { // affiche : /*accueil.swf photos.swf Chapitre 10 ? Diffusion d?événements personnalisés ? version 0.1.1 26 / 29 Thibault Imbert pratiqueactionscript3.bytearray.org blog.swf liens.swf */ trace( pEvt.lien ); } Lorsque l?événement ButtonEvent.CLICK est diffusé, la fonction clicBouton est déclenchée. La propriété lien de l?objet événementiel de type ButtonEvent permet de charger le SWF correspondant. Celle-ci pourrait contenir dans une autre application une URL à atteindre lorsque l?utilisateur clique sur un bouton. Il serait tout à fait envisageable de définir de nouvelles propriétés au sein de la classe ButtonEvent telles legende, couleur ou vitesse ou autres afin de passer les caractéristiques de chaque bouton. Voici le code complet de la classe Button : package org.bytearray.ui { import flash.display.Shape; import flash.display.Sprite; import flash.text.Font; import flash.text.TextFormat; import org.events.ButtonEvent; // import des classes liées Tween au mouvement import fl.transitions.Tween; import fl.transitions.easing.Bounce; // import de la classe MouseEvent import flash.events.MouseEvent; // import de la classe TextField et TextFieldAutoSize import flash.text.TextField; import flash.text.TextFieldAutoSize; public class Button extends Sprite { // stocke le fond du bouton private var fondBouton:Shape; // stocke l'objet Tween pour les différents état du bouton private var etatTween:Tween; // stocke les références aux boutons private static var tableauBoutons:Array = new Array(); // stocke la couleur en cours du bouton private var couleur:Number; // stocke la vitesse d'ouverture de chaque bouton private var vitesse:Number; // légende du bouton private var legende:TextField; // formatage des légendes private var formatage:TextFormat; // swf associé private var swf:String; Chapitre 10 ? Diffusion d?événements personnalisés ? version 0.1.1 27 / 29 Thibault Imbert pratiqueactionscript3.bytearray.org public function Button ( pWidth:Number, pHeight:Number, pSWF:String, pCouleur:Number, pVitesse:Number, pLegende:String ) { // ajoute chaque instance au tableau Button.tableauBoutons.push ( this ); // création du fond du bouton fondBouton = new Shape(); // ajout à la liste d'affichage addChild ( fondBouton ); // crée le champ texte legende = new TextField(); // redimensionnement automatique du champ texte legende.autoSize = TextFieldAutoSize.LEFT; // ajout à la liste d'affichage addChild ( legende ); // affecte la légende legende.text = pLegende; // active l'utilisation de police embarquée legende.embedFonts = true; // crée un objet de formatage formatage = new TextFormat(); // taille de la police formatage.size = 12; // instanciation de la police embarquée var police:MaPolice = new MaPolice(); // affectation de la police au formatage formatage.font = police.fontName; // affectation du formatage au champ texte legende.setTextFormat ( formatage ); // rend le champ texte non selectionnable legende.selectable = false; // stocke la couleur passée en paramètre couleur = pCouleur; // stocke le nom du SWF swf = pSWF; // dessine le bouton fondBouton.graphics.beginFill ( couleur, 1 ); fondBouton.graphics.drawRect ( 0, 0, pWidth, pHeight ); // activation du mode bouton buttonMode = true; // désactivation des objets enfants mouseChildren = false; Chapitre 10 ? Diffusion d?événements personnalisés ? version 0.1.1 28 / 29 Thibault Imbert pratiqueactionscript3.bytearray.org // affecte la vitesse passée en paramêtre setVitesse ( pVitesse ); // création de l'objet Tween etatTween = new Tween ( fondBouton, "scaleX", Bounce.easeOut, 1, 1, vitesse, true ); // écoute de l'événement MouseEvent.ROLL_OVER addEventListener ( MouseEvent.ROLL_OVER, survolSouris ); // écoute de l'événement MouseEvent.CLICK addEventListener ( MouseEvent.CLICK, clicSouris ); } private function clicSouris ( pEvt:MouseEvent ):void { // diffusion de l'événement ButtonEvent.CLICK dispatchEvent ( new ButtonEvent ( ButtonEvent.CLICK, true, false, swf ) ); } // déclenché lors du clic sur le bouton private function survolSouris ( pEvt:MouseEvent ):void { // stocke la longueur du tableau var lng:int = Button.tableauBoutons.length; for (var i:int = 0; i<lng; i++ ) Button.tableauBoutons[i].fermer(); // démarrage de l'animation etatTween.continueTo ( 2, vitesse ); } // méthode permettant de refermer le bouton private function fermer ():void { // referme le bouton etatTween.continueTo ( 1, vitesse ); } // gère l'affectation de la vitesse public function setVitesse ( pVitesse:Number ):void { // affecte la vitesse if ( pVitesse >= 1 && pVitesse <= 10 ) vitesse = pVitesse; else { Chapitre 10 ? Diffusion d?événements personnalisés ? version 0.1.1 29 / 29 Thibault Imbert pratiqueactionscript3.bytearray.org trace("Erreur : Vitesse non correcte, la valeur doit être comprise entre 1 et 10"); vitesse = 1; } } } } La diffusion d?événements personnalisés est un point essentiel dans tout développement orienté objet ActionScript. En diffusant nos propres événements nous rendons nos objets compatibles avec le modèle événementiel ActionScript 3 et facilement réutilisables. En livrant une classe à un développeur tiers, celui-ci regardera en premier lieu les capacités offertes par celle-ci puis s?attardera sur les différents événements diffusés pour voir comment dialoguer facilement avec celle-ci. A retenir ? Nous pouvons définir autant de propriétés que nous le souhaitons au sein de l?objet événementiel diffusé. Nous allons nous intéresser maintenant à la notion de classe du document. Cette nouveauté apportée par Flash CS3 va nous permettre de rendre nos développements ActionScript 3 plus aboutis. En avant pour le chapitre 11 intitulé Classe du document. Chapitre 11 ? Classe du document ? version 0.1.2 1 / 27 Thibault Imbert pratiqueactionscript3.bytearray.org 11 Classe du document INTERETS ............................................................................................................... 1 LA CLASSE MAINTIMELINE ............................................................................. 2 CLASSE DU DOCUMENT..................................................................................... 3 LIMITATIONS DE LA CLASSE SPRITE ........................................................................ 6 ACTIONS D?IMAGES................................................................................................. 8 DECLARATION AUTOMATIQUE DES OCCURRENCES.............................. 9 DÉCLARATION MANUELLE DES OCCURRENCES ..................................................... 11 AJOUTER DES FONCTIONNALITES.............................................................. 12 INITIALISATION DE L?APPLICATION.......................................................... 15 ACCÈS GLOBAL À L?OBJET STAGE ......................................................................... 18 AUTOMATISER L?ACCÈS GLOBAL À L?OBJET STAGE .............................................. 21 Intérêts Depuis l?introduction d?ActionScript 2, les développeurs avaient pour habitude de créer une classe servant de point d?entrée à leur application. Celle-ci instanciait d?autres objets ayant généralement besoin d?accéder au scénario principal. Il fallait donc passer une référence au scénario principal. Il n?était donc pas rare de trouver sur une image du scénario de l?application le code suivant : var monApplication:Application = new Application ( this ); L?application pouvait aussi être initialisée à l?aide d?une simple méthode statique : Application.initialise ( this ); Chapitre 11 ? Classe du document ? version 0.1.2 2 / 27 Thibault Imbert pratiqueactionscript3.bytearray.org D?autres développeurs utilisaient une autre technique, consistant à changer le contexte d?exécution de la classe principale avec le code suivant : this.__proto__= Application.prototype; Application['apply']( this , null ); En utilisant le mot-clé this au sein de l?instance de la classe Application nous faisions alors référence au scénario principal. ActionScript 3 intègre une nouvelle fonctionnalité appelée Classe du document permettant d?éviter d?avoir recours à ces différentes astuces. La classe MainTimeline Comme nous l?avons vu lors du chapitre 4 intitulé La liste d?affichage le lecteur Flash est constitué d?un objet Stage situé au sommet de la liste d?affichage. Lorsqu?un SWF est lu dans le lecteur, celui-ci ajoute le scénario principal du SWF chargé en tant qu?enfant de l?objet Stage : Figure 11-1. Classe du document MainTimeline. Par défaut le scénario d?un SWF est représenté par la classe MainTimeline. Nous pouvons nous en rendre compte très facilement. Dans un nouveau document Flash CS3, testez le code suivant sur le scénario principal : // affiche : [object MainTimeline] trace( this ); Nous allons voir que nous ne sommes pas limités à cette classe. Chapitre 11 ? Classe du document ? version 0.1.2 3 / 27 Thibault Imbert pratiqueactionscript3.bytearray.org A retenir ? Par défaut le scénario principal est représenté par la classe MainTimeline. Classe du document Flash CS3 intègre une nouvelle fonctionnalité permettant d?utiliser une instance de sous classe graphique comme scénario principal. Attention, celle-ci doit hériter obligatoirement d?une classe graphique telle flash.display.Sprite ou flash.display.MovieClip. Il est techniquement possible d?utiliser une sous classe de flash.display.Shape mais il serait impossible d?ajouter un objet graphique sur le scénario principal, la classe Shape n?étant pas une sous classe de flash.display.DisplayObjectContainer. Si nous utilisons comme classe du document une sous-classe graphique nommée Application, Flash ajoute aussitôt une instance de celle-ci comme scénario principal. La figure 11-2 illustre l?idée : Figure 11-2. Classe du document Application. Si nous ne spécifions aucune classe, le lecteur crée une instance de MainTimeline. Chapitre 11 ? Classe du document ? version 0.1.2 4 / 27 Thibault Imbert pratiqueactionscript3.bytearray.org Nous allons créer un nouveau document Flash CS3 puis créer a coté un répertoire org dans lequel nous créons un répertoire document. Au sein du répertoire document nous définissons une classe Application dont voici le code : package org.bytearray.document { import flash.display.Sprite; public class Application extends Sprite { public function Application () { // affiche : [object Application] trace( this ); } } } Grâce à la notion de classe du document, le mot-clé this fait ici référence à l?instance de la classe Application, donc au scénario principal. Afin de lier cette classe du document à notre document Flash nous utilisons le champ Classe du document du panneau Inspecteur de propriétés illustré en figure 11-3 : Figure 11-3. Champ Classe du document. Nous spécifions le paquetage complet de la classe Application. Il est intéressant de noter que l?instanciation de la classe Application est transparente. Lorsque l?animation est chargée, le lecteur Flash crée automatiquement une instance de la classe Application et l?ajoute en tant qu?enfant de l?objet Stage : package org.bytearray.document { Chapitre 11 ? Classe du document ? version 0.1.2 5 / 27 Thibault Imbert pratiqueactionscript3.bytearray.org import flash.display.Sprite; public class Application extends Sprite { public function Application () { // affiche : [object Stage] trace( parent ); // affiche : [object Stage] trace( stage ); // affiche : [object Application] trace( this ); } } } Si nous écoutons l?événement Event.ADDED_TO_STAGE nous remarquons que celui-ci est bien diffusé : package org.bytearray.document { import flash.display.Sprite; import flash.events.Event; public class Application extends Sprite { public function Application () { // écoute de l'événement Event.ADDED_TO_STAGE // est diffusé automatiquement car le lecteur // ajoute à la liste d'affichage une instance // de la classe Application addEventListener ( Event.ADDED_TO_STAGE, ajoutAffichage ); } private function ajoutAffichage ( pEvt:Event ):void { // affiche : [Event type="addedToStage" bubbles=true cancelable=false eventPhase=2] trace( pEvt ); } Chapitre 11 ? Classe du document ? version 0.1.2 6 / 27 Thibault Imbert pratiqueactionscript3.bytearray.org } } Il faut donc considérer la classe du document comme le point d?entrée de notre projet. Nous avons dans notre exemple utilisé une sous-classe de Sprite comme classe du document. Nous allons voir dans quelle mesure son utilisation est limitée dans ce contexte. A retenir ? Pour affecter une classe du document, nous utilisons le champ Classe du document de l?inspecteur de propriétés. ? L?instanciation de classe du document est transparente, le lecteur s?en charge automatiquement. ? L?instance de la classe du document devient le scénario principal. Limitations de la classe Sprite Rappelez-vous, lors du chapitre 4 intitulé La liste d?affichage nous avons découvert que la classe Sprite ne disposait pas de scénario. Ainsi, lorsque la classe du document est une sous-classe de Sprite il est impossible de faire appel aux différentes méthodes de manipulation du scénario telles gotoAndPlay, gotoAndStop ou autres. Le code suivant ne peut être compilé : package org.bytearray.document { import flash.display.Sprite; public class Application extends Sprite { public function Application () { gotoAndStop ( "intro" ); } } } L?erreur suivante est générée à la compilation : 1180: Appel à une méthode qui ne semble pas définie, gotoAndStop. Chapitre 11 ? Classe du document ? version 0.1.2 7 / 27 Thibault Imbert pratiqueactionscript3.bytearray.org Nous utiliserons donc une sous-classe de Sprite comme classe du document lorsque nous n?aurons pas besoin de scénario. Nous pourrions penser qu?il s?agit de la seule limitation de la classe Sprite, mais il est aussi impossible d?ajouter du code sur les images du scénario. Les lignes suivantes sont posées sur l?image 1 de notre animation : var monClip:MovieClip = new MovieClip(); L?erreur suivante est générée : 1180: Appel à une méthode qui ne semble pas définie, addFrameScript. Une simple ligne de code commentée empêche la compilation : //var monClip:MovieClip = new MovieClip(); La puissance de la classe du document réside dans l?interaction entre le code des images et les fonctionnalités apportées par la classe du document. L?utilisation d?une sous-classe de Sprite comme classe du document est donc généralement déconseillée. En étendant la classe MovieClip, nous pouvons ajouter des actions d?images et manipuler le scénario. Nous modifions la classe Application afin d?obtenir le code suivant : package org.bytearray.document { import flash.display.MovieClip; public class Application extends MovieClip { public function Application () { } } } Dans le cas d?une application nécessitant un minimum d?animations et de code sur les images nous préférerons étendre la classe MovieClip. Chapitre 11 ? Classe du document ? version 0.1.2 8 / 27 Thibault Imbert pratiqueactionscript3.bytearray.org Actions d?images Toute action d?image s?exécute dans le contexte de l?instance de la classe du document. Prenons un exemple simple, nous ajoutons une méthode afficheMenu au sein de la classe Application : package org.bytearray.document { import flash.display.MovieClip; public class Application extends MovieClip { public function Application () { } // méthode de création du menu public function afficheMenu ( ):void { trace("création du menu"); } } } La méthode afficheMenu devient donc une méthode du scénario principal, sur n?importe quelle image nous pouvons l?exécuter en plaçant le code suivant : // stop le scénario principal stop(); // appelle la méthode afficheMenu de la classe du document // affiche : création du menu afficheMenu(); Nous stoppons le scénario principal puis appelons la méthode afficheMenu définie au sein de la classe du document. Cette capacité à pouvoir déclencher différentes fonctionnalités de la classe du document depuis les images rend le développement plus souple. Lorsqu?un simple effet ou un ensemble de mécanismes complexes doivent être déclenchés nous l?appelons directement depuis le scénario. A retenir Chapitre 11 ? Classe du document ? version 0.1.2 9 / 27 Thibault Imbert pratiqueactionscript3.bytearray.org ? L?utilisation d?une sous classe de Sprite comme classe du document est généralement déconseillée. ? Nous préférons généralement utiliser une sous-classe de MovieClip. ? La puissance réside dans l?interaction entre les actions d?images et les fonctionnalités de la classe du document. Déclaration automatique des occurrences Afin d?accéder aux objets graphiques depuis la classe du document nous utilisons deux techniques. Comme nous l?avons vu au cours du chapitre 9 intitulé Etendre les classes natives, l?option Déclarer automatiquement les occurrences de scène est activée par défaut dans Flash CS3. Dans l?exemple suivant nous créons un symbole clip auquel nous associons une classe Personnage, puis nous posons une occurrence sur la scène, nommée monPersonnage : Figure 11-4. Occurrence monPersonnage. A la compilation, une propriété monPersonnage est automatiquement ajoutée et référence l?occurrence : package org.bytearray.document { import flash.display.MovieClip; public class Application extends MovieClip Chapitre 11 ? Classe du document ? version 0.1.2 10 / 27 Thibault Imbert pratiqueactionscript3.bytearray.org { public function Application () { // affiche : [object Personnage] trace( monPersonnage ); } } } On pourrait également recourir à la méthode getChildByName : package org.bytearray.document { import flash.display.MovieClip; public class Application extends MovieClip { public function Application () { // suppression du symbole monPersonnage removeChild ( getChildByName ( "monPersonnage" ) ); } } } Le code suivant supprime l?occurrence monPersonnage posée sur le scénario principal : package org.bytearray.document { import flash.display.MovieClip; public class Application extends MovieClip { public function Application () { // suppression du symbole monPersonnage removeChild ( monPersonnage ); } Chapitre 11 ? Classe du document ? version 0.1.2 11 / 27 Thibault Imbert pratiqueactionscript3.bytearray.org } } Lorsque l?option Déclarer automatiquement les occurrences de scène est activée, le compilateur ajoute automatiquement au sein de la classe conteneur des propriétés pointant vers les occurrences créées depuis l?environnement auteur. Cette déclaration automatique des occurrences empêche le développeur de voir quels sont les objets utilisés au sein de la classe et ne facilite pas le déboguage de l?application. C?est pour cette raison que nous préférons généralement désactiver la déclaration automatique dans un projet géré par des développeurs seulement. A l?inverse, dans un contexte d?interactions développeurs-designers le graphiste peut être amené à poser un nouvel objet sur la scène et lui donner un nom d?occurrence afin d?enrichir graphiquement l?application. Si l?option Déclarer automatiquement les occurrences de scène est désactivée, le graphiste, ne pourra plus compiler le projet sans que le développeur n?ajoute une propriété au sein de la classe du document correspondant à l?occurrence ajoutée. Cela risque donc de gêner le flux de travail au sein d?une équipe. Déclaration manuelle des occurrences En désactivant la déclaration automatique des occurrences, nous devons définir manuellement au sein de la classe des propriétés du même nom que les occurrences : package org.bytearray.document { import flash.display.MovieClip; public class Application extends MovieClip { // propriété liée à l'occurrence public var monPersonnage:MovieClip public function Application () { // suppression du symbole monPersonnage removeChild ( monPersonnage ); } Chapitre 11 ? Classe du document ? version 0.1.2 12 / 27 Thibault Imbert pratiqueactionscript3.bytearray.org } } La propriété définie manuellement doit obligatoirement être publique. Si nous la rendons privée à l?aide de l?attribut private le compilateur génère une erreur : ReferenceError: Error #1056: Impossible de créer la propriété monPersonnage sur org.bytearray.document.Application. Lorsque le compilateur tente d?affecter la propriété monPersonnage afin de la faire pointer vers l?occurrence, celle-ci est privée et n?est donc pas accessible depuis l?extérieur de la classe. Ainsi, les propriétés liées aux occurrences doivent toujours être publique. Si nous définissons la propriété et que l?option Déclarer les occurrences de scène est activée, une erreur à la compilation s?affiche : 1151: Conflit dans la définition monPersonnage dans l'espace de nom internal. Le compilateur tente de définir une propriété pointant vers l?occurrence posée sur la scène, mais celui ci rencontre une propriété du même nom déjà définie. Un conflit de propriétés est généré. A retenir ? De manière générale, il est préférable de désactiver la déclaration automatique des occurrences de scène. ? Dans un contexte d?interactions designers-développeurs, il est préférable d?activer la déclaration automatique des occurrences. ? Les propriétés liées aux occurrences doivent toujours être publique. Ajouter des fonctionnalités Il faut bien comprendre que lorsqu?une classe du document est définie, son instance représente le scénario principal. Nous avons découvert lors du chapitre 4 intitulé La liste d?affichage différents comportements liés à l?accès aux objets graphiques. Souvenez vous en ActionScript 2 nous pouvions écrire le code suivant : gotoAndStop (20); monPersonnage._alpha = 100; Même si l?occurrence monPersonnage n?était présente qu?à l?image 20, nous pouvions depuis n?importe quelle image y accéder. Cela n?est plus vrai en ActionScript 3. Chapitre 11 ? Classe du document ? version 0.1.2 13 / 27 Thibault Imbert pratiqueactionscript3.bytearray.org Afin d?accéder correctement à l?objet monPersonnage nous pouvons utiliser l?événement Event.RENDER. Cet événement est déclenché lorsque le lecteur a fini de rendre les données vectorielles. Afin qu?il soit diffusé nous devons appeler la méthode invalidate de la classe Stage. Ainsi, nous pourrions accéder à l?objet monPersonnage avec le code suivant : // écoute de l'événement Event.RENDER addEventListener ( Event.RENDER, miseAJour ); function miseAJour ( pEvt:Event ):void { monPersonnage.alpha = 1; // supprime l'écoute removeEventListener( Event.RENDER, miseAJour ); } // déplace le tête de lecture gotoAndStop (20); // force le rafraîchissement stage.invalidate(); La fonction miseAJour est déclenchée lorsque le lecteur a terminé d?afficher les objets graphiques présents à l?image 20. Nous pouvons donc accéder sans problème à l?occurrence monPersonnage. Grâce à la classe du document, nous allons mettre en place une fonctionnalité rendant tout ce traitement transparent. Au sein de la classe Application nous définissons une méthode myGotoAndStop : package org.bytearray.document { import flash.display.MovieClip; import flash.events.Event; public class Application extends MovieClip { // propriétés liées à l'occurrence public var monPersonnage:MovieClip // propriété permettant l?exécution de la fonction de rappel private var rappel:Function; public function Application () Chapitre 11 ? Classe du document ? version 0.1.2 14 / 27 Thibault Imbert pratiqueactionscript3.bytearray.org { } // méthode de déplacement de la tête de lecture personnalisé public function myGotoAndStop ( pImage:int, pFonction:Function ):void { // écoute de l'événement Event.RENDER addEventListener ( Event.RENDER, miseAJour ); // déplacement de la tête de lecture gotoAndStop ( pImage ); // retourne un objet permettant rappel = pFonction; // force la diffusion de l'événement Event.RENDER stage.invalidate(); } private function miseAJour ( pEvt:Event ):void { // nous tentons d'appeler la fonction de rappel try { rappel(); // si cela échoue, nous affichons un message d'erreur } catch ( pErreur:Error ) { trace("Erreur : La méthode de rappel n'a pas été définie"); // dans tout les cas, nous supprimons l'écoute de l'événement Event.RENDER } finally { removeEventListener ( Event.RENDER, miseAJour ); } } } } Ainsi lorsque nous souhaitons retrouver le comportement de la méthode gotoAndStop des anciennes versions d?ActionScript nous écrivons le code suivant : // déplace la tête de lecture en image 10 // lorsque tout les objets sont disponibles // la fonction actifAccessible est déclenchée myGotoAndStop ( 10, actifAccessible ); Chapitre 11 ? Classe du document ? version 0.1.2 15 / 27 Thibault Imbert pratiqueactionscript3.bytearray.org function actifAccessible ():void { // affiche : [object Personnage] trace(monPersonnage); // affecte la rotation de l'occurrence monPersonnage.rotation = 90; } stop(); La fonction actifAccessible est déclenchée automatiquement lorsque les occurrences de l?image 20 sont disponibles. Grâce au bloc try, catch, finally nous gérons les erreurs à l?exécution. Au cas où l?utilisateur oublierait de définir la fonction associée : // déplace la tête de lecture en image 10 // lorsque tout les objets sont disponibles // la fonction actifAccessible est déclenchée // affiche : Erreur : La méthode de rappel n'a pas été définie myGotoAndStop ( 10, actifAccessible ); // fonction non définie var actifAccessible:Function; stop(); L?erreur à l?exécution est gérée et nous affichons le message indiquant l?oublie de définition. Grâce à la classe du document nous pouvons ajouter des fonctionnalités au scénario principal ou modifier certains comportements. Nous étendons les capacités du scénario en définissant de nouvelles méthodes au sein de la classe du document. A retenir ? Il est préférable de désactiver la déclaration automatique des occurrences. ? Les propriétés liées aux occurrences doivent toujours être publique. Initialisation de l?application Nous allons supprimer l?occurrence nommée monPersonnage sur la scène, puis initialiser l?application en instanciant par programmation l?instance de Personnage. Chapitre 11 ? Classe du document ? version 0.1.2 16 / 27 Thibault Imbert pratiqueactionscript3.bytearray.org Afin d?initialiser notre application, nous utilisons simplement le constructeur de la classe du document. Dans le code suivant nous instancions puis affichons le symbole Personnage : package org.bytearray.document { import flash.display.MovieClip; import flash.events.Event; public class Application extends MovieClip { // propriété permettant l?exécution de la fonction de rappel private var rappel:Function; public function Application () { // création du symbole Personnage var autrePersonnage:Personnage = new Personnage(); // ajout à la liste d'affichage addChild ( autrePersonnage ); // l'occurrence est centrée autrePersonnage.x = (stage.stageWidth - autrePersonnage.width)/2; autrePersonnage.y = (stage.stageHeight - autrePersonnage.height)/2; } // méthode de déplacement de la tête de lecture personnalisé public function myGotoAndStop ( pImage:int, pFonction:Function ):void { // écoute de l'événement Event.RENDER addEventListener ( Event.RENDER, miseAJour ); // déplacement de la tête de lecture gotoAndStop ( pImage ); // retourne un objet permettant rappel = pFonction; // force la diffusion de l'événement Event.RENDER stage.invalidate(); } private function miseAJour ( pEvt:Event ):void { // nous tentons d'appeler la fonction de rappel try { Chapitre 11 ? Classe du document ? version 0.1.2 17 / 27 Thibault Imbert pratiqueactionscript3.bytearray.org rappel(); // si cela échoue, nous affichons un message d'erreur } catch ( pErreur:Error ) { trace("Erreur : La méthode de rappel n'a pas été définie"); // dans tout les cas, nous supprimons // l'écoute de l'événement Event.RENDER } finally { removeEventListener ( Event.RENDER, miseAJour ); } } } } La figure 11-6 illustre le résultat : Figure 11-6. Symbole Personnage affiché. Ne remarquez vous pas quelque chose de particulier ? Nous devrions normalement écouter l?événement Event.ADDED_TO_STAGE pour pouvoir accéder à l?objet Stage de manière sécurisée. Dans le cas de la classe du document, le code est Chapitre 11 ? Classe du document ? version 0.1.2 18 / 27 Thibault Imbert pratiqueactionscript3.bytearray.org exécuté une fois l?instance de la classe du document ajoutée à la liste d?affichage, ce qui est automatique et assuré par le lecteur. L?objet Stage est donc directement accessible par la propriété stage depuis le constructeur. Accès global à l?objet Stage Il serait intéressant d?utiliser la classe du document comme point d?accès global à l?objet Stage. Pour cela, nous définissons une propriété statique GLOBAL_STAGE au sein de la classe Application : // point d'accès à l'objet Stage public static var GLOBAL_STAGE:Stage; Puis nous modifions le constructeur : public function Application () { // affecte une référence à l'objet Stage Application.GLOBAL_STAGE = stage; } En ciblant la propriété Application.GLOBAL_STAGE, n?importe quel objet obtiendra une référence à l?objet Stage. Afin d?illustrer cette fonctionnalité, nous allons définir au sein d?un répertoire ui une classe Fenetre : package org.bytearray.ui { import flash.display.Shape; public class Fenetre extends Shape { public function Fenetre ( pLargeur:Number, pHauteur:Number, pRayon:Number, pCouleur:Number ) { graphics.beginFill ( pCouleur ); graphics.drawRoundRect ( 0, 0, pLargeur, pHauteur, pRayon ); } } } Cette classe crée une forme rectangulaire illustrant une simple fenêtre. Chapitre 11 ? Classe du document ? version 0.1.2 19 / 27 Thibault Imbert pratiqueactionscript3.bytearray.org En pointant vers la propriété Application.GLOBAL_STAGE nous obtenons un point d?accès global à l?objet Stage depuis n?importe quelle instance de la classe Fenetre : package org.bytearray.ui { import flash.display.Shape; import org.bytearray.document.Application; public class Fenetre extends Shape { public function Fenetre ( pLargeur:Number, pHauteur:Number, pRayon:Number, pCouleur:Number ) { graphics.beginFill ( pCouleur ); graphics.drawRoundRect ( 0, 0, pLargeur, pHauteur, pRayon ); // nous centrons la fenêtre // en utilisant la propriété statique Application.GLOBAL_STAGE nous bénéficions d'un point d'accès global à l'objet Stage x = (Application.GLOBAL_STAGE.stageWidth - width)/2; y = (Application.GLOBAL_STAGE.stageHeight - height)/2; } } } Puis nous affichons la fenêtre : package org.bytearray.document { import flash.display.MovieClip; import flash.events.Event; import org.bytearray.ui.Fenetre; public class Application extends MovieClip { // point d'accès à l'objet Stage public static var GLOBAL_STAGE:Stage; // propriété permettant l?exécution de la fonction de rappel private var rappel:Function; public function Application () { addEventListener ( Event.ADDED_TO_STAGE, activation ); } Chapitre 11 ? Classe du document ? version 0.1.2 20 / 27 Thibault Imbert pratiqueactionscript3.bytearray.org private function activation ( pEvt:Event ):void { // affecte une référence à l'objet Stage Application.GLOBAL_STAGE = stage; // création de la fenêtre var maFenetre:Fenetre = new Fenetre( 250, 250, 8, 0x88CCAA ); // ajout à la liste d'affichage addChild ( maFenetre ); } // méthode de déplacement de la tête de lecture personnalisé public function myGotoAndStop ( pImage:int, pFonction:Function ):void { // écoute de l'événement Event.RENDER addEventListener ( Event.RENDER, miseAJour ); // déplacement de la tête de lecture gotoAndStop ( pImage ); // retourne un objet permettant rappel = pFonction; // force la diffusion de l'événement Event.RENDER stage.invalidate(); } private function miseAJour ( pEvt:Event ):void { // nous tentons d'appeler la fonction de rappel try { rappel(); // si cela échoue, nous affichons un message d'erreur } catch ( pErreur:Error ) { trace("Erreur : La méthode de rappel n'a pas été définie"); // dans tout les cas, nous supprimons l'écoute de l'événement Event.RENDER } finally { removeEventListener ( Event.RENDER, miseAJour ); } } } } Chapitre 11 ? Classe du document ? version 0.1.2 21 / 27 Thibault Imbert pratiqueactionscript3.bytearray.org La figure 11-7 illustre le résultat : Figure 11-7. Instance de la classe Fenetre affichée. Grâce à cette technique nous n?avons pas besoin d?écouter l?événement Event.ADDED_TO_STAGE afin de pouvoir cibler l?objet Stage. Mais comme nous l?avons vu précédemment, il est toujours recommandé d?écouter en interne l?événement Event.ADDED_TO_STAGE afin d?accéder à l?objet Stage. Cette technique permet donc aux objets non graphiques d?accéder à l?objet Stage de manière simplifiée. En revanche, si nous devons réutiliser la classe Fenetre dans un autre projet, nous serons obligé de définir une classe du document nommée Application ainsi qu?une propriété statique GLOBAL_STAGE référençant l?objet Stage. Afin d?automatiser ce processus, nous allons utiliser l?héritage. Automatiser l?accès global à l?objet Stage Pour pouvoir bénéficier d?un accès global à l?objet Stage automatiquement dans tous nos projets, nous avons plusieurs possibilités. La première, comme nous venons de voir à l?instant consiste à définir dans la classe du document de chaque projet, une propriété statique Chapitre 11 ? Classe du document ? version 0.1.2 22 / 27 Thibault Imbert pratiqueactionscript3.bytearray.org accessible de n?importe classe. Permettant ainsi un accès simplifié à l?objet Stage. Il serait dommage de devoir recopier ce code dans classe du document, nous préférerons donc utiliser la solution suivante. Nous savons que la classe Application contient des fonctionnalités pouvant être nécessaires à chaque projet : ? Un accès global à l?objet Stage ? Une méthode myGotoAndStop Afin d?hériter de ces fonctionnalités, nous allons utiliser pour chaque projet une classe du document héritant de la classe Application. Il n?est pas nécessaire de dupliquer celle-ci dans le répertoire de classes de chaque projet nous allons l?isoler en la plaçant dans un répertoire spécifique global à tous nos projets. A la racine de notre disque dur nous créons un répertoire nommé classes_as3. Puis nous créons répertoire org contenant un répertoire abstrait. Au sein de ce dernier nous stockons la classe ApplicationDefaut dont voici le code complet final : package org.bytearray.abstrait { import flash.display.MovieClip; import flash.events.Event; import flash.display.Stage; public class ApplicationDefaut extends MovieClip { // point d'accès à l'objet Stage public static var GLOBAL_STAGE:Stage; // propriété permettant l?exécution de la fonction de rappel private var rappel:Function; public function ApplicationDefaut () { // affecte une référence à l'objet Stage ApplicationDefaut.GLOBAL_STAGE = stage; } // méthode de déplacement de la tête de lecture personnalisé public function myGotoAndStop ( pImage:int, pFonction:Function ):void { // écoute de l'événement Event.RENDER Chapitre 11 ? Classe du document ? version 0.1.2 23 / 27 Thibault Imbert pratiqueactionscript3.bytearray.org addEventListener ( Event.RENDER, miseAJour ); // déplacement de la tête de lecture gotoAndStop ( pImage ); // retourne un objet permettant rappel = pFonction; // force la diffusion de l'événement Event.RENDER stage.invalidate(); } private function miseAJour ( pEvt:Event ):void { // nous tentons d'appeler la fonction de rappel try { rappel(); // si cela échoue, nous affichons un message d'erreur } catch ( pErreur:Error ) { trace("Erreur : La méthode de rappel n'a pas été définie"); // dans tout les cas, nous supprimons l'écoute de l'événement Event.RENDER } finally { removeEventListener ( Event.RENDER, miseAJour ); } } } } Afin de spécifier un chemin d?accès de classes global à tous les projets au sein de Flash CS3, nous cliquons sur Modifier puis Préférences puis dans la liste nous sélectionnons ActionScript. Figure 11-8. Paramètres d?ActionScript 3. Une fois cliqué sur le bouton Paramètres d?ActionScript 3 le panneau indiquant le chemin d?accès aux classes s?affiche. Comme l?illustre la figure 11-9. Chapitre 11 ? Classe du document ? version 0.1.2 24 / 27 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 11-9. Chemin d?accès aux classes. En cliquant sur l?icône + nous ajoutons un champ afin de spécifier le chemin d?accès au répertoire contenant les classes globales. Dans notre exemple nous ajoutons une entrée contenant le chemin C:\classes_as3. Deux autres lignes sont déjà présentes, la première indique que le compilateur doit regarder à coté du document en cours. Puis la deuxième indique le chemin d?accès aux classes natives de Flash. En cliquant sur OK, les classes stockées au sein du répertoire classes_as3 seront disponibles depuis n?importe quel projet. Nous allons modifier notre application en affectant une classe du document héritant de la classe ApplicationDefaut. Au sein du répertoire document nous créons une classe Document : package org.bytearray.document { import org.bytearray.abstrait.ApplicationDefaut; public class Document extends ApplicationDefaut { public function Document () { trace( this ); } } Chapitre 11 ? Classe du document ? version 0.1.2 25 / 27 Thibault Imbert pratiqueactionscript3.bytearray.org } Puis nous l?affectons comme classe du document en cours, comme l?indique la figure 11-10 : Figure 11-10. Affectation de la classe Document. A la compilation [object Document] s?affiche dans le panneau Sortie. La classe ApplicationDefaut devient ainsi une classe ne devant être qu?héritée. Une sorte de classe abstraite, même si comme nous l?avons vu lors du chapitre 8 intitulé Programmation orientée objet, ActionScript 3 ne permet pas la définition de vraie classe abstraite. Tous les objets présents ou non au sein de la liste d?affichage devant faire référence à l?objet Stage devront cibler la propriété ApplicationDefaut.GLOBAL_STAGE. Nous modifions donc la classe Fenetre : package org.bytearray.ui { import flash.display.Shape; import org.bytearray.abstrait.ApplicationDefaut; public class Fenetre extends Shape { public function Fenetre ( pLargeur:Number, pHauteur:Number, pRayon:Number, pCouleur:Number ) { graphics.beginFill ( pCouleur ); graphics.drawRoundRect ( 0, 0, pLargeur, pHauteur, pRayon ); // nous centrons la fenêtre // en utilisant la propriété statique Application.GLOBAL_STAGE // nous bénéficions d'un point d'accès global à l'objet Stage x = (ApplicationDefaut.GLOBAL_STAGE.stageWidth - width)/2; y = (ApplicationDefaut.GLOBAL_STAGE.stageHeight - height)/2; } } } Chapitre 11 ? Classe du document ? version 0.1.2 26 / 27 Thibault Imbert pratiqueactionscript3.bytearray.org Puis nous modifions la classe Document afin d?instancier la classe Fenetre : package org.bytearray.document { import flash.events.Event; import org.bytearray.abstrait.ApplicationDefaut; import org.bytearray.ui.Fenetre; public class Document extends ApplicationDefaut { public function Document () { // création de la fenêtre var maFenetre:Fenetre = new Fenetre( 250, 250, 8, 0x88CCAA ); // ajout à la liste d'affichage addChild ( maFenetre ); } } } La figure 11-11 illustre le résultat : Chapitre 11 ? Classe du document ? version 0.1.2 27 / 27 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 11-11. Instance de la classe Fenetre. Souvenez-vous, la sous-classe n?hérite pas des propriétés statiques de la classe parente. Ainsi, la classe Document n?hérite pas de la propriété GLOBAL_STAGE de la classe ApplicationDefaut. A retenir ? La fenêtre Paramètres d?ActionScript 3 permet de définir un chemin d?accès global aux classes. Nous allons nous intéresser au cours du prochain chapitre à la gestion des bitmap par programmation en ActionScript 3. Les classes flash.display.Bitmap et flash.display.BitmapData n?auront plus de secrets pour vous ! Chapitre 12 ? Programmation Bitmap ? version 0.1.2 1 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org 12 Programmation Bitmap BITMAP ET VECTORIELS .................................................................................. 2 COULEURS ............................................................................................................. 3 MANIPULER LES COULEURS .................................................................................... 5 LA CLASSE BITMAPDATA ................................................................................. 7 CODAGE DE COULEURS..................................................................................... 8 GERER LES RESSOURCES AVEC LE PROFILER ......................................... 9 LA CLASSE BITMAP........................................................................................... 12 RÉUTILISER LES DONNÉES BITMAPS ...................................................................... 15 LIBERER LES RESSOURCES............................................................................ 18 CALCULER LE POIDS D?UNE IMAGE EN MEMOIRE................................................... 26 LIMITATIONS MÉMOIRE......................................................................................... 27 IMAGES EN BIBLIOTHEQUE........................................................................... 28 PEINDRE DES PIXELS........................................................................................ 30 LIRE DES PIXELS.................................................................................................... 34 ACCROCHAGE AUX PIXELS.................................................................................... 37 LE LISSAGE.......................................................................................................... 38 MISE EN CACHE DES BITMAP A L?EXECUTION ....................................... 39 EFFETS PERVERS ................................................................................................... 43 FILTRER UN ÉLÉMENT VECTORIEL............................................................ 46 FILTRER UNE IMAGE BITMAP................................................................................. 59 ANIMER UN FILTRE................................................................................................ 61 RENDU BITMAP D?OBJET VECTORIELS ..................................................... 63 OPTIMISER LES PERFORMANCES................................................................ 78 Chapitre 12 ? Programmation Bitmap ? version 0.1.2 2 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org Bitmap et vectoriels Avant d?entamer la découverte des fonctionnalités offertes par le lecteur Flash en matière de programmation bitmap, il convient de s?attarder sur le concept d?image bitmap et vectorielle. Une image bitmap peut être considérée comme une grille constituée de pixels de couleur spécifique. En zoomant sur une image bitmap nous pouvons apercevoir chaque pixel la constituant. Figure 12-1. Image bitmap agrandie. A l?inverse, une image vectorielle n?est pas composée de pixels, mais de tracés issus de coordonnées mathématiques. Le lecteur Flash les interprète et dessine la forme correspondante. La figure 12-2 illustre un exemple de tracé vectoriel : Chapitre 12 ? Programmation Bitmap ? version 0.1.2 3 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 12-2. Image vectorielle agrandie. En cas d?agrandissement le tracé est recalculé, empêchant toute pixellisation de l?image quelque soit la résolution ou dimension. En matière de performances, l?affichage vectoriel requiert peu de mémoire mais peut nécessiter en cas de tracés complexes une forte sollicitation du processeur. Couleurs L?espace de couleur utilisé dans Flash est l?ARVB, chaque couleur repose sur quatre composantes : ? L?alpha ? Le rouge ? Le vert ? Le bleu Les trois composantes de couleurs donnent une combinaison de 16777215 couleurs possibles. L?espace colorimétrique RVB s?approche en réalité du nombre de couleurs maximum que l??il de l?homme peut distinguer, ainsi le terme de couleurs vraies est couramment utilisé. Chapitre 12 ? Programmation Bitmap ? version 0.1.2 4 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org Chaque composante est codée sur 8 bits soit 1 octet et varie de 0 à 255. En binaire, une couleur ARVB peut être représentée de la manière suivante : Alpha | Rouge | Vert | Bleu 11111111 | 11111111 | 00000000 | 00000000 Bien entendu, pour des questions de pratique nous travaillons généralement avec une base 16, plus couramment appelée représentation hexadécimale : Alpha | Rouge | Vert | Bleu FF | FF | 00 | 00 Nous ajoutons le préfixe 0x devant une valeur hexadécimale afin de préciser au lecteur Flash qu?il s?agit d?une couleur encodée en base 16 : // stocke une couleur hexadécimale var couleur:Number = 0xFFFF0000; // affiche : 4294901760 trace( couleur ); Pour générer une couleur aléatoire, nous pouvons évaluer un nombre aléatoire compris entre 0 et la couleur la plus haute, soit 0xFFFFFF : // génère une couleur aléatoire var couleurAleatoire:Number = Math.floor ( Math.random()*0xFFFFFF ); // affiche : 9019179 trace( couleurAleatoire ); Lorsque nous affichons la couleur évaluée, celle-ci est rendue par défaut sous une forme décimale. Si nous souhaitons obtenir une autre représentation de la couleur nous pouvons utiliser la méthode toString de la classe Object. Celle-ci permet de convertir un nombre dans une base spécifique : // génère une couleur aléatoire var couleurAleatoire:Number = Math.floor ( Math.random()*0xFFFFFF ); // affiche la couleur au format héxadécimal (base 16) // affiche : d419f6 trace( couleurAleatoire.toString(16) ); // affiche la couleur au format octal (base 8) // affiche : 52267144 trace( couleurAleatoire.toString(8) ); // affiche la couleur au format binaire (base 2) // affiche : 101010010110111001100100 trace( couleurAleatoire.toString(2) ); Chapitre 12 ? Programmation Bitmap ? version 0.1.2 5 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org Une couleur RVB est représentée par une valeur hexadécimale à six chiffres. Deux chiffres étant nécessaires pour chaque composante : var rouge:Number = 0xFF0000; var vert:Number = 0x00FF00; var bleu:Number = 0x0000FF; Pour représenter une couleur ARVB, nous ajoutons le composant alpha en début de couleur : var rougeTransparent:Number = 0x00FF0000; var rougeSemiTransparent:Number = 0x88FF0000; var rougeOpaque:Number = 0xFFFF0000; Afin de faciliter la compréhension des couleurs dans Flash, nous allons nous attarder à présent sur leur manipulation. Manipuler les couleurs La manipulation de couleurs est facilitée grâce aux opérateurs de manipulation binaire. Au cours de ce chapitre, nous allons travailler avec les différentes composantes de couleurs. Nous allons créer une classe BitmapOutils globale à tous nos projets, contenant différentes méthodes de manipulation. Rappelez-vous, au cours du chapitre 11 intitulé Classe du document, nous avions créé un répertoire global de classes nommé classes_as3. Au sein du répertoire org du répertoire nous créons un répertoire nommé outils. Puis nous définissons une classe BitmapOutils contenant une première méthode hexArgb : package org.bytearray.outils { import flash.display.BitmapData; public class BitmapOutils { public static function hexArgb ( pCouleur:Number ):Object { var composants:Object = new Object(); // extraction de chaque composante composants.alpha = (pCouleur >>> 24) & 0xFF; composants.rouge = (pCouleur >>> 16) & 0xFF; composants.vert = (pCouleur >>> 8) & 0xFF; composants.bleu = pCouleur & 0xFF; return composants; Chapitre 12 ? Programmation Bitmap ? version 0.1.2 6 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org } } } Grâce à la méthode hexArgb, l?extraction des composantes est simplifiée : import org.bytearray.outils.BitmapOutils; // génère une couleur aléatoire var couleurAleatoire:Number = Math.floor ( Math.random()*0xFFFFFFFF ); // affiche : 767D62D5 trace( couleurAleatoire.toString(16).toUpperCase() ); // extraction des composants var composants:Object = BitmapOutils.hexArgb ( couleurAleatoire ); var transparence:Number = composants.alpha; var rouge:Number = composants.rouge; var vert:Number = composants.vert; var bleu:Number = composants.bleu; // affiche : 76 trace( transparence.toString(16).toUpperCase() ); // affiche : 7D trace( rouge.toString(16).toUpperCase() ); // affiche : 62 trace( vert.toString(16).toUpperCase() ); // affiche : D5 trace( bleu.toString(16).toUpperCase() ); Nous pouvons aussi ajouter une méthode argbHex permettant d?assembler une couleur hexadécimale à partir de quatre composantes : package org.bytearray.outils { import flash.display.BitmapData; public class BitmapOutils { public static function hexArgb ( pCouleur:Number ):Object { var composants:Object = new Object(); composants.alpha = (pCouleur >>> 24) & 0xFF; composants.rouge = (pCouleur >>> 16) & 0xFF; composants.vert = (pCouleur >>> 8) & 0xFF; composants.bleu = pCouleur & 0xFF; Chapitre 12 ? Programmation Bitmap ? version 0.1.2 7 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org return composants; } public static function argbHex ( pAlpha:int, pRouge:int, pVert:int, pBleu:int ):uint { return pAlpha << 24 | pRouge << 16 | pVert << 8 | pBleu; } } } Une fois la méthode argbHex définie, nous pouvons générer une couleur aléatoire à partir de quatre composantes : import org.bytearray.outils.BitmapOutils; var transparence:Number = Math.floor ( Math.random()*256 ); var rouge:Number = Math.floor ( Math.random()*256 ); var vert:Number = Math.floor ( Math.random()*256 ); var bleu:Number = Math.floor ( Math.random()*256 ); // assemble la couleur var couleur:Number = BitmapOutils.argbHex ( transparence, rouge, vert, bleu ); // affiche : 3F31D4B2 trace( couleur.toString(16).toUpperCase() ); Notre classe BitmapOutils sera très vite enrichie, nous y ajouterons bientôt de nouvelles fonctionnalités. Libre à vous d?ajouter par la suite différentes méthodes facilitant la manipulation des couleurs. A retenir ? Une couleur est constituée de 4 composants codés sur 8 bits, soit un octet. ? Chaque composant varie entre 0 et 255. ? Pour changer la base d?un nombre, nous utilisons la méthode toString de la classe Object. ? La manipulation des couleurs est facilitée grâce aux opérateurs de manipulation binaire. La classe BitmapData La classe BitmapData fut introduite avec le lecteur Flash 8 et permet la création d?images bitmaps par programmation. En ActionScript 3, son utilisation est étendue car toutes les données bitmaps sont représentées par la classe flash.display.BitmapData. Chapitre 12 ? Programmation Bitmap ? version 0.1.2 8 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org L?utilisation d?images bitmaps par programmation permet de travailler sur les pixels et de créer toutes sortes d?effets complexes qui ne peuvent être réalisés à l?aide de vecteurs. Afin de créer dynamiquement une image bitmap, nous devons tout d?abord créer les pixels la composant. Pour cela nous utilisons la classe flash.display.BitmapData dont voici la signature du constructeur : public fonction BitmapData(width:int, height:int, transparent:Boolean = true, fillColor:uint = 0xFFFFFFFF) Celui-ci accepte quatre paramètres : ? width : largueur de l?image. ? height : hauteur de l?image. ? transparent : un booléen indiquant la transparence de l?image. Transparent par défaut ? fillColor : la couleur du bitmap en 32 ou 24 bits. Couleur blanche par défaut. Pour créer une image transparente de 1000 * 1000 pixels de couleur beige nous écrivons le code suivant : // création d'une image de 1000 * 1000 pixels, transparente de couleur beige var monImage:BitmapData = new BitmapData (1000, 1000, true, 0x00F0D062); Aussitôt l?image créée, les données bitmaps sont stockées en mémoire. Codage de couleurs Lorsqu?une image est créée, le lecteur Flash stocke la couleur de chaque pixel en mémoire. Chacun d?entre eux est codé sur 32 bits, 4 octets sont donc nécessaires à la description d?un pixel. Afin d?illustrer ce comportement, nous créons une première image bitmap semi transparente de 1000 * 1000 pixels : // création d'une image transparente var monImage:BitmapData = new BitmapData ( 1000, 1000, true, 0xAAF0D062 ); // récupère la couleur d'un pixel var couleurPixel:Number = monImage.getPixel32 ( 0, 0 ); // extraction du canal alpha var transparence:Number = BitmapOutils.hexArgb ( couleurPixel ).alpha; // affiche : 170 trace( transparence ); En isolant le canal alpha, nous voyons que son intensité vaut 170. Dans un souci d?optimisation nous pourrions décider de créer une image non transparente, pensant que celle-ci serait codée sur 24 bits : Chapitre 12 ? Programmation Bitmap ? version 0.1.2 9 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org // création d'une image non transparente var monImage:BitmapData = new BitmapData ( 1000, 1000, false, 0xF0D062 ); // récupère la couleur d'un pixel var couleurPixel:Number = monImage.getPixel32 ( 0, 0 ); // extraction du canal alpha var transparence:Number = BitmapOutils.hexArgb ( couleurPixel ).alpha; // affiche : 255 trace( transparence ); Dans le cas d?une image non transparente, l?intensité du canal alpha est automatiquement définie à 255. Si nous tentons de passer une couleur 32 bits, les 8 premiers bits sont ignorés : // création d'une image non transparente var monImage:BitmapData = new BitmapData ( 1000, 1000, false, 0xBBF0D062 ); // récupère la couleur d'un pixel var couleurPixel:Number = monImage.getPixel32 ( 0, 0 ); // récupère le canal alpha var transparence:Number = BitmapOutils.hexArgb ( couleurPixel ).alpha; // affiche : 255 trace( transparence ); Au sein du lecteur Flash, quelque soit la transparence de l?image, les couleurs sont toujours codées sur 32 bits. La création d?une image opaque n?entraîne donc aucune optimisation mémoire mais facilite en revanche l?affichage. A retenir ? Pour créer une image par programmation nous utilisons la classe BitmapData. ? Chaque couleur de pixel composant une instance de BitmapData est codée sur 32 bits. ? 1 pixel pèse 4 octets en mémoire. ? La création d?image opaque n?optimise pas la mémoire mais facilite le rendu de l?image. Gérer les ressources avec le profiler Afin d?optimiser un projet ActionScript 3, il est impératif de maîtriser la gestion des ressources. L?utilisation de la classe BitmapData nécessite une attention toute particulière quant à l?occupation mémoire engendrée par son utilisation. Pour tester le comportement du lecteur Flash nous utiliserons le profiler de Flex Builder 3, qui est aujourd?hui l?outil le plus adapté en matière de gestion et optimisation des ressources. Chapitre 12 ? Programmation Bitmap ? version 0.1.2 10 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org Le profiler de Flex Builder 3 est un module permettant de connaître en temps réel l?occupation mémoire de chaque objet ainsi que l?occupation mémoire totale d?une application ActionScript 3. Il n?existe malheureusement pas d?outil similaire dans Flash CS3. Lorsque vous souhaitez tester les ressources d?un projet ActionScript 3 développé avec Flash CS3, vous avez la possibilité de charger celle- ci au sein d?une application Flex, afin de bénéficier du profiler. Nous allons analyser la mémoire utilisée par le lecteur Flash en créant une première image bitmap transparente : // création d?une image transparente var monImage:BitmapData = new BitmapData ( 1000, 1000, true, 0x00F0D062 ); La figure 12-4 illustre la fenêtre Utilisation Mémoire du profiler : Figure 12-4. Création de données bitmaps transparente. Nous voyons la courbe augmenter sensiblement lors de la création de l?instance de BitmapData. Celle-ci occupe environ 3906 Ko en mémoire vive. Afin d?analyser ce résultat, faisons un tour d?horizon des différentes légendes du panneau Utilisation mémoire : ? Peak Memory (Seuil maximum atteint) : plus haute occupation mémoire atteinte depuis le lancement de l?animation. ? Current Memory (Mémoire actuelle) : occupation mémoire courante. ? Time (Temps) : nombre de secondes écoulées depuis le lancement de l?animation. Le profiler nous indique que l?image créée, occupe en mémoire environ 3,9 Mo. Nous pouvons facilement vérifier cette valeur avec le calcul suivant : Chapitre 12 ? Programmation Bitmap ? version 0.1.2 11 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org 1000 * 1000 = 1000000 pixels Chaque pixel est codé sur 32 bits (4 octets) : 1000000 pixels * 4 octets = 4000000 octets Afin d?obtenir l?équivalent en Ko, nous divisons par 1024 : 4000000 / 1024 = 3906,25 Ko Nous retrouvons le poids indiqué par le profiler. Nous verrons très bientôt comment faciliter ce calcul au sein d?une application ActionScript. Comme nous l?avons vu précédemment, le lecteur Flash code, quelque soit la transparence de l?image, les couleurs sur 32 bits. Si nous créons une image non transparente, l?occupation mémoire reste la même : // création d'une image non transparente var monImage:BitmapData = new BitmapData ( 1000, 1000, false, 0xF0D062 ); La figure 12-4 illustre le résultat : Figure 12-4. Création de données bitmaps non transparentes. Le profiler est un outil essentiel au déboguage d?applications ActionScript 3. Certains outils tiers existent mais n?offrent pas une telle granularité dans les informations apportées. A retenir Chapitre 12 ? Programmation Bitmap ? version 0.1.2 12 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org ? Le Profiler est un outil intégré à Flex Builder 3 facilitant la gestion des ressources d?un projet ActionScript 3. ? Il n?existe pas d?outil intégré équivalent dans Flash CS3. La classe Bitmap Comme son nom l?indique, la classe BitmapData représente les données bitmaps mais celle-ci ne peut être affichée directement. Afin de rendre une image nous devons associer l?instance de BitmapData à la classe flash.display.Bitmap. Il est important de considérer la classe Bitmap comme simple conteneur, celle-ci sert à présenter les données bitmaps. Dans les précédentes versions d?ActionScript la classe MovieClip était utilisée pour afficher l?image : // création d'un clip conteneur var monClipConteneur:MovieClip = this.createEmptyMovieClip ("conteneur", 0); // création des données bitmaps var donneesBitmap:BitmapData = new BitmapData (300, 300, false, 0xFF00FF); // affichage de l'image monClipConteneur.attachBitmap (donneesBitmap, 0); En ActionScript 3, nous utilisons la classe Bitmap dont voici le constructeur : Bitmap(bitmapData:BitmapData = null, pixelSnapping:String = "auto", smoothing:Boolean = false) Celui-ci accepte trois paramètres : ? bitmapData : les données bitmaps à afficher. ? pixelSnapping : accrochage aux pixels. ? Smoothing : un booléen indiquant si l?image doit être lissée ou non. Nous passons en premier paramètre l?instance de BitmapData à afficher, les deux autres paramètres seront traités plus loin : // création d'une image de 250 * 250 pixels, non transparente de couleur beige var monImage:BitmapData = new BitmapData (250, 250, false, 0xF0D062); // création d'un conteneur pour l'image bitmap var monConteneurImage:Bitmap = new Bitmap ( monImage ); // ajout du conteneur addChild ( monConteneurImage ); En testant le code précédent, nous obtenons le résultat illustré en figure 12-5 : Chapitre 12 ? Programmation Bitmap ? version 0.1.2 13 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 12-5. Affichage d?une instance de BitmapData. Si nous souhaitons modifier la présentation des données bitmaps, nous utilisons les différentes propriétés de la classe Bitmap. A l?inverse, si nous devons travailler sur les pixels composant l?image, nous utiliserons les méthodes de la classe BitmapData. De par l?héritage, toutes les propriétés de la classe DisplayObject sont donc disponibles sur la classe Bitmap : // création d'une image de 250 * 250 pixels, non transparente de couleur beige var monImage:BitmapData = new BitmapData (250, 250, false, 0xF0D062); // création d'un conteneur pour l'image bitmap var monConteneurImage:Bitmap = new Bitmap ( monImage ); // ajout du conteneur addChild ( monConteneurImage ); // positionnement et redimensionnement monConteneurImage.x = 270; monConteneurImage.y = 120; monConteneurImage.scaleX = .5; monConteneurImage.scaleY = .5; monConteneurImage.rotation = 45; Le code précédent déplace l?image, réduit l?image et lui fait subir une rotation de 45 degrés. Le résultat est illustré en figure 12-6 : Chapitre 12 ? Programmation Bitmap ? version 0.1.2 14 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 12-6. Déplacement et rotation d?une image bitmap. Il était impossible d?accéder aux données bitmaps associées à un MovieClip dans les précédentes versions d?ActionScript. En ActionScript 3, nous utilisons la propriété bitmapData de la classe Bitmap : // création d'une image de 250 * 250 pixels, non transparente de couleur beige var monImage:BitmapData = new BitmapData (250, 250, false, 0xF0D062); // création d'un conteneur pour l'image bitmap var monConteneurImage:Bitmap = new Bitmap ( monImage ); // ajout du conteneur addChild ( monConteneurImage ); // positionnement et redimensionnement monConteneurImage.x = 270; monConteneurImage.y = 120; monConteneurImage.scaleX = .5; monConteneurImage.scaleY = .5; monConteneurImage.rotation = 45; var donneesBitmap:BitmapData = monConteneurImage.bitmapData; // affiche : 250 trace(donneesBitmap.width ); // affiche : 250 trace(donneesBitmap.height ); Nous remarquons que les dimensions de l?instance de BitmapData ne sont pas altérées par le redimensionnement de l?objet Bitmap. Chapitre 12 ? Programmation Bitmap ? version 0.1.2 15 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org Souvenez-vous, la classe Bitmap présente simplement les données bitmaps définies par la classe BitmapData. Réutiliser les données bitmaps Dans certaines applications, les données bitmaps peuvent être réutilisées lorsque les pixels sont identiques mais présentés différemment. Imaginons que nous souhaitons construire un damier comme celui illustré en figure 12-7 : Figure 12-7. Damier constitué d?instances de BitmapData. Nous pourrions être tentés d?écrire le code suivant : for ( var i:int = 0; i< 300; i++ ) { // création d'un carré de 20 * 20 pixels, non transparent de couleur beige var monImage:BitmapData = new BitmapData (20, 20, false, 0xF0D062); // création d'un conteneur pour l'image bitmap var monConteneurImage:Bitmap = new Bitmap ( monImage ); // ajout du conteneur à la liste d?affichage addChild ( monConteneurImage ); // positionnement des conteneurs d?images monConteneurImage.x = ( monConteneurImage.width + 8 ) * Math.round (i % 20); Chapitre 12 ? Programmation Bitmap ? version 0.1.2 16 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org monConteneurImage.y = ( monConteneurImage.height + 8 ) * Math.floor (i / 20); } Pour chaque itération nous créons une instance de BitmapData, puis un objet Bitmap afin d?afficher chaque image. Si nous regardons l?occupation mémoire. Lorsque le damier est créé, l?occupation mémoire est de 571 Ko. Figure 12-8. Occupation mémoire sans optimisation du damier. Le code précédent n?est pas optimisé car nous créons à chaque itération une instance de BitmapData de 20 pixels pesant 1,56 Ko. En instanciant 300 fois ces données bitmaps, nous obtenons un poids total cumulé pour les images d?environ 470 Ko. Souvenez-vous, la classe Bitmap permet d?afficher des données bitmaps, il est donc tout à fait possible d?afficher plusieurs images à partir d?une même instance de BitmapData. Nous pourrions obtenir le même damier en divisant le poids de presque 6 fois : // création d?une seule instance de BitmapData en dehors de la boucle var monImage:BitmapData = new BitmapData (20, 20, false, 0xF0D062); for ( var i:int = 0; i< 300; i++ ) { // création d'un conteneur pour les données bitmaps var monConteneurImage:Bitmap = new Bitmap ( monImage ); // ajout du conteneur addChild ( monConteneurImage ); // positionnement des conteneurs de bitmap Chapitre 12 ? Programmation Bitmap ? version 0.1.2 17 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org monConteneurImage.x = ( monConteneurImage.width + 8 ) * Math.round (i % 20); monConteneurImage.y = ( monConteneurImage.height + 8 ) * Math.floor (i / 20); } Avec le code précédent l?occupation mémoire a chuté, nous passons à environ 88 Ko d?occupation mémoire. Figure 12-9. Occupation mémoire avec optimisation du damier. Il est donc fortement recommandé de réutiliser les données bitmaps lorsque nous souhaitons afficher plusieurs fois les mêmes données bitmaps, même sous une forme différente. Nous pourrions modifier la présentation des données bitmaps grâce aux différentes propriétés de la classe Bitmap. Dans le code suivant nous modifier la taille et la rotation de chaque élément du damier : // création d?une seule instance de BitmapData en dehors de la boucle var monImage:BitmapData = new BitmapData (20, 20, false, 0xF0D062); for ( var i:int = 0; i< 300; i++ ) { // création d'un conteneur pour les données bitmaps var monConteneurImage:Bitmap = new Bitmap ( monImage ); // ajout du conteneur addChild ( monConteneurImage ); // positionnement des conteneurs de bitmap monConteneurImage.x = ( monConteneurImage.width + 8 ) * Math.round (i % 20); monConteneurImage.y = ( monConteneurImage.height + 8 ) * Math.floor (i / 20); // taille, rotation et transparence aléatoires Chapitre 12 ? Programmation Bitmap ? version 0.1.2 18 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org monConteneurImage.scaleX = monConteneurImage.scaleY = Math.random(); monConteneurImage.rotation = Math.floor ( Math.random()*360 ); monConteneurImage.alpha = Math.random(); } La figure 12-10 illustre le résultat : Figure 12-10. Damier constitué d?une seule instance de BitmapData. Ce décor est constitué des mêmes données bitmaps, mais présentées sous différentes formes. A retenir ? La classe Bitmap permet de présenter les données bitmaps définies par la classe BitmapData. ? Il est important de réutiliser les données bitmaps lorsque cela est possible. Libérer les ressources Comme nous l?avons vu lors du chapitre 2 intitulé Langage et API du lecteur Flash lorsqu?un objet n?est pas référencé, celui-ci est aussitôt considéré comme éligible à la suppression par le ramasse-miettes. Chapitre 12 ? Programmation Bitmap ? version 0.1.2 19 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org Dans le code suivant nous créons une instance de BitmapData à partir de 5 secondes, aucune référence n?est conservée : // création d'un minuteur var minuteur:Timer = new Timer ( 5000, 0 ); // écoute de l'événement TimerEvent.TIMER minuteur.addEventListener( TimerEvent.TIMER, creation ); // démarrage du minuteur minuteur.start(); function creation ( pEvt:TimerEvent ):void { // création d'une image non transparente de 350 * 350 pixels var monBitmap:BitmapData = new BitmapData ( 350, 350, false, 0x990000 ); } Une fois la fonction creation exécutée, la variable locale monBitmap expire, les données bitmaps sont aussitôt supprimées de la mémoire. En testant le code précédent nous ne remarquons aucune augmentation de l?occupation mémoire : Figure 12-11. Occupation mémoire de l?application. Si nous ajoutons à la liste d?affichage chaque instance de BitmapData créée, les données bitmaps sont alors référencées et ne sont plus éligibles à la suppression : // création d'un minuteur var minuteur:Timer = new Timer ( 5000, 0 ); // écoute de l'événement TimerEvent.TIMER minuteur.addEventListener( TimerEvent.TIMER, creation ); // démarrage du minuteur minuteur.start(); Chapitre 12 ? Programmation Bitmap ? version 0.1.2 20 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org function creation ( pEvt:TimerEvent ):void { // création d'une image non transparente de 350 * 350 pixels var monBitmap:BitmapData = new BitmapData ( 350, 350, false, 0x990000 ); // création de l?enveloppe Bitmap var monConteneurBitmap:Bitmap = new Bitmap ( monBitmap ); // ajout à la liste d?affichage addChild ( monConteneurBitmap ); } La figure 12-12 montre l?augmentation de l?occupation mémoire : Figure 12-12. Augmentation de l?occupation mémoire de l?application. Toutes les 5 secondes, une image bitmap est créée puis ajoutée à la liste d?affichage. A partir d?une minute et dix secondes nous obtenons une occupation mémoire d?environ 6,7 Mo. Chaque instance nécessitant environ 478,5 Ko. Lorsque nous n?avons plus besoin d?une image, il est fortement recommandé de la désactiver afin de libérer la mémoire. Pour cela, nous disposons de plusieurs solutions. La première, consiste à désactiver l?image et libérer la mémoire en utilisant la méthode dispose de la classe BitmapData. Dans le code suivant, un minuteur crée des instances de BitmapData toutes les 5 secondes puis les ajoutent à la liste d?affichage. A partir de 30 secondes nous stoppons la création des images et appelons la méthode dispose sur chaque instance de BitmapData : // création d'un minuteur var minuteur:Timer = new Timer ( 5000, 0 ); Chapitre 12 ? Programmation Bitmap ? version 0.1.2 21 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org // écoute de l'événement TimerEvent.TIMER minuteur.addEventListener( TimerEvent.TIMER, creation ); // démarrage du minuteur minuteur.start(); function creation ( pEvt:TimerEvent ):void { // création d'une image non transparente de 350 * 350 pixels var monBitmap:BitmapData = new BitmapData ( 350, 350, false, 0x990000 ); // création de l?enveloppe Bitmap var monConteneurBitmap:Bitmap = new Bitmap ( monBitmap ); // ajout à la liste d?affichage addChild ( monConteneurBitmap ); } var minuteurNettoyage:Timer = new Timer ( 30000, 1 ); minuteurNettoyage.addEventListener( TimerEvent.TIMER, nettoyage ); minuteurNettoyage.start(); // désactivation des données bitmaps à partir de 30 secondes function nettoyage ( pEvt:TimerEvent ):void { minuteur.stop(); var lng:int = numChildren; var monImage:Bitmap; while ( lng-- ) { monImage = Bitmap ( getChildAt ( lng ) ); // désactive les données bitmaps monImage.bitmapData.dispose(); } } En analysant les informations fournies par le profiler, nous voyons que la mémoire n?est pas libérée : Chapitre 12 ? Programmation Bitmap ? version 0.1.2 22 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 12-13. Occupation mémoire de l?application. Il s?agit en réalité d?un bogue du profiler, qui n?affiche pas correctement la libération de la mémoire lors de l?utilisation de la méthode dispose. A l?aide d?autres outils, nous remarquons que la mémoire est libérée immédiatement. Attention, bien que l?appel à la méthode dispose supprime visuellement les images et libère la mémoire celles-ci sont toujours présentes au sein de la liste d?affichage et donc référencées. Afin de totalement désactiver une instance de BitmapData il faut veiller à supprimer l?instance de la liste d?affichage, puis appeler la méthode dispose. Nous modifions le code précédent en supprimant chaque instance de l?affichage puis en appelant la méthode dispose : // création d'un minuteur var minuteur:Timer = new Timer ( 5000, 0 ); // écoute de l'événement TimerEvent.TIMER minuteur.addEventListener( TimerEvent.TIMER, execution ); // démarrage du minuteur minuteur.start(); // création et ajout à l 'affichage d'une instance de BitmapData // toutes les 5 secondes function execution ( pEvt:TimerEvent ):void { // création d'une image non transparente de 350 * 350 pixels var monBitmap:BitmapData = new BitmapData ( 350, 350, false, 0x990000 ); // création de l?enveloppe Bitmap var monConteneurBitmap:Bitmap = new Bitmap ( monBitmap ); // ajout à la liste d?affichage addChild ( monConteneurBitmap ); Chapitre 12 ? Programmation Bitmap ? version 0.1.2 23 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org } var minuteurNettoyage:Timer = new Timer ( 30000, 1 ); minuteurNettoyage.addEventListener( TimerEvent.TIMER, nettoyage ); minuteurNettoyage.start(); // libération des ressources au bout de 30 secondes function nettoyage ( pEvt:TimerEvent ):void { minuteur.stop(); var lng:int = numChildren; var monImage:Bitmap; while ( lng-- ) { monImage = Bitmap ( removeChildAt ( lng ) ); // désactive les données bitmaps monImage.bitmapData.dispose(); } } L?approche suivante s?appuie sur le ramasse-miettes en supprimant les références pointant vers les instances de BitmapData. Cette technique a pour inconvénient de ne pas supprimer immédiatement les données bitmaps en mémoire. Elles le seront uniquement si le ramasse-miettes procède à un nettoyage. Souvenez-vous que celui-ci peut ne jamais intervenir. Dans certaines applications, les données bitmaps peuvent être utilisées sans être affichées. Dans le cas d?une application d?encodage et de compression d?images, un tableau de références est généralement créé afin d?accéder rapidement à chaque instance : // création d'un minuteur var minuteur:Timer = new Timer ( 5000, 5 ); // écoute de l'événement TimerEvent.TIMER minuteur.addEventListener( TimerEvent.TIMER, execution ); // démarrage du minuteur minuteur.start(); // conteneur de références var tableauImages:Array = new Array(); // création d'une instance de BitmapData // toutes les 5 secondes Chapitre 12 ? Programmation Bitmap ? version 0.1.2 24 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org function execution ( pEvt:TimerEvent ):void { // création d'une image non transparente de 350 * 350 pixels var monBitmap:BitmapData = new BitmapData ( 350, 350, false, Math.random()*0xFFFFFF ); // création de l?enveloppe Bitmap var monConteneurBitmap:Bitmap = new Bitmap ( monBitmap ); // stockage des références tableauImages.push ( monConteneurBitmap ); } var minuteurNettoyage:Timer = new Timer ( 30000, 1 ); minuteurNettoyage.addEventListener( TimerEvent.TIMER, nettoyage ); minuteurNettoyage.start(); // libération des ressources au bout de 30 secondes function nettoyage ( pEvt:TimerEvent ):void { minuteur.stop(); var lng:int = tableauImages.length; while ( lng-- ) { // supprime chaque référence tableauImages [ lng ] = null; } } Les seules références aux instances de BitmapData ne résident pas au sein de la liste d?affichage mais au sein du tableau tableauImages. Pour libérer les ressources, nous passons chaque référence à null. Lorsque le ramasse-miettes intervient, les ressources sont libérées : Chapitre 12 ? Programmation Bitmap ? version 0.1.2 25 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 12-14. Chute de l?occupation mémoire de l?application. En utilisant cette technique il n?est pas nécessaire d?appeler la méthode dispose. Afin d?optimiser nos tests nous pouvons grâce au profiler déclencher le ramasse-miettes et voir si les ressources sont bien libérées lors de son passage. Il n?est pas possible officiellement de déclencher le ramasse-miettes par programmation. Une fois une image désactivée, celle-ci ne peut plus être utilisée. Dans le code suivant, nous tentons de cloner une image bitmap désactivée : // création d'une image de 250 * 250 pixels // non transparente de couleur beige var monImage:BitmapData = new BitmapData (250, 250, false, 0xF0D062); // désactivation des données bitmaps monImage.dispose(); // tentative de clonage des données bitmaps // affiche : ArgumentError: Error #2015: BitmapData non valide. monImage.clone(); L?appel de la méthode clone lève une erreur de type ArgumentError. Une fois désactivée, il est impossible de réactiver l?image. A retenir Chapitre 12 ? Programmation Bitmap ? version 0.1.2 26 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org ? La méthode dispose permet de désactiver une image et de libérer instantanément la mémoire utilisée. Cette technique est recommandée. ? En supprimant simplement les références pointant vers l?image nous ne sommes pas garantis que la mémoire soit libérée. Nous sommes tributaires du ramasse-miettes. Calculer le poids d?une image en mémoire Afin de faciliter la manipulation de données bitmaps par programmation nous allons ajouter une méthode nommée poids au sein de la classe BitmapOutils : package org.bytearray.outils { import flash.display.BitmapData; public class BitmapOutils { public static function hexArgb ( pCouleur:Number ):Object { var composants:Object = new Object(); composants.alpha = (pCouleur >>> 24) & 0xFF; composants.rouge = (pCouleur >>> 16) & 0xFF; composants.vert = (pCouleur >>> 8) & 0xFF; composants.bleu = pCouleur & 0xFF; return composants; } public static function argbHex ( pAlpha:int, pRouge:int, pVert:int, pBleu:int ):uint { return ( pAlpha << 24 | pRouge << 16 | pVert << 8 | pBleu ); } public static function poids ( pBitmapData:BitmapData ):Number { return (pBitmapData.width * pBitmapData.height) * 4; } } } Chapitre 12 ? Programmation Bitmap ? version 0.1.2 27 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org La méthode poids nous renvoie le poids de l?image en mémoire en octets : import org.bytearray.outils.BitmapOutils; // création d'une instance de BitmapData var monBitmap:BitmapData = new BitmapData ( 1000, 1000, false, Math.random()*0xFFFFFF ); // calcul du poids en mémoire var poids:Number = BitmapOutils.poids ( monBitmap ) / 1024; // affiche : 3906.25 trace( poids ); La classe BitmapOutils pourra ainsi être réutilisée à tout moment dans différents projets. Limitations mémoire Pour des raisons de performances, la taille maximale d?une instance de BitmapData créée par programmation est limitée à 2880 * 2880 pixels. Une image d?une telle dimension nécessite près de 32 Mo de mémoire vive, ce qui représente une occupation mémoire non négligeable : import org.bytearray.outils.BitmapOutils; // création d'une image de 2880 * 2880 pixels // non transparente de couleur beige var monImage:BitmapData = new BitmapData (2880, 2880, false, 0xF0D062); var poidsImage:Number = BitmapOutils.poids ( monImage ); // affiche : 32400 trace( poidsImage / 1024 ); Si nous tentons tout de même de créer une image d?une taille supérieure, le lecteur Flash lève une erreur à l?exécution : // lève l'erreur à l'exécution suivante : // Error #2015: BitmapData non valide. var monImage:BitmapData = new BitmapData (3000, 3000, false, 0xF0D062); Dans le cas d?une application de dessin, nous pourrions indiquer à l?utilisateur que l?image crée est trop grande en gérant l?exception : try { var monImage:BitmapData = new BitmapData (3000, 3000, false, 0xF0D062); } catch ( pError:Error ) { // affiche : ArgumentError: Error #2015: BitmapData non valide. trace( pError ); Chapitre 12 ? Programmation Bitmap ? version 0.1.2 28 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org trace("Image trop grande !"); } Dans le cas d?une application nécessitant des images de dimensions supérieures, plusieurs instances de BitmapData peuvent être utilisées afin de contourner cette limitation. A retenir ? La taille maximale d?une instance de BitmapData créée par programmation est de 2880*2880 pixels. Images en bibliothèque Lorsqu?une image est présente au sein de la bibliothèque. Celle-ci est assimilée à une instance de BitmapData. Dans un nouveau document Flash CS3, nous importons une image en bibliothèque, puis nous définissons une classe associée nommée Logo. Nous instancions l?image et l?affichons : // instanciation des données bitmaps var monLogo:Logo = new Logo(0,0); // creation d?une envelope Bitmap var monConteneur:Bitmap = new Bitmap ( monLogo ); // ajout à l'affichage addChild ( monConteneur ); // positionnement de l'image monConteneur.x = 100; monConteneur.y = 100; La figure 12-15 montre le résultat : Chapitre 12 ? Programmation Bitmap ? version 0.1.2 29 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 12-15. Affichage d?une image de bibliothèque. De manière générale, il n?est pas forçément nécessaire d?utiliser le type spécifique pour stocker l?instance de BitmapData. Nous pouvons aussi stocker l?instance de Logo au sein d?une variable de type BitmapData : // instanciation des données bitmaps var monLogo:BitmapData = new Logo(0,0); Si l?image en bibliothèque est un PNG transparent, l?instance de BitmapData créée est transparente : // instanciation du logo var monLogo:BitmapData = new Logo(0,0); // affiche : true trace( monLogo.transparent ); Nous avons jusqu?à présent créé des images bitmaps de couleur unies, nous allons nous attarder maintenant à la modification des données bitmaps, en travaillant sur les pixels. Il est important de noter qu?une image en bibliothèque ne possède pas de limitations de taille, contrairement aux instances de BitmapData créées par programmation. Chapitre 12 ? Programmation Bitmap ? version 0.1.2 30 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org Peindre des pixels Trois méthodes sont disponibles pour peindre les pixels d?une image, voici le détail de chacune d?entre elles : ? BitmapData.setPixel : peint un pixel au format RVB. ? BitmapData.setPixel32 : peint un pixel au format ARVB. ? BitmapData.setPixels : peint des pixels d?après un tableau d?octets source définie par la classe flash.utils.ByteArray. La méthode setPixel permet de colorer un pixel à une position donnée : public function setPixel(x:int, y:int, color:uint):void Les paramètres x et y définissent la position du pixel à peindre. La couleur doit être spécifiée au format RVB. Dans le code suivant, nous nous colorons aléatoirement au sein d?une boucle certains pixels de l?image : var monImage:BitmapData = new BitmapData ( 200, 200, false, 0xFFFFFF ); var conteneurImage:Bitmap = new Bitmap ( monImage ); addChild ( conteneurImage ); for ( var i:int = 0; i<20000; i++ ) { // positions aléatoires // arrondi automatique, dû au type int var positionX:int = Math.random()*251; var positionY:int = Math.random()*251; // peint un pixel monImage.setPixel ( positionX, positionY, 0x990000 ); } Le résultat est illustré en figure 12-16 : Chapitre 12 ? Programmation Bitmap ? version 0.1.2 31 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 12-16. Dessin par setPixel. Si nous passons une couleur au format ARVB, le canal alpha est ignoré : var monImage:BitmapData = new BitmapData ( 200, 200, false, 0xFFFFFF ); var conteneurImage:Bitmap = new Bitmap ( monImage ) addChild ( conteneurImage ); for ( var i:int = 0; i<20000; i++ ) { // positions aléatoires // arrondi automatique, dû au type int var positionX:int = Math.random()*251; var positionY:int = Math.random()*251; // peint un pixel, le canal alpha est ignoré monImage.setPixel ( positionX, positionY, 0x33990000 ); } En réalité lorsque nous appelons la méthode setPixel nous travaillons avec une couleur codée sur 24 bits, le canal alpha étant automatiquement défini. Afin de pouvoir peindre des pixels en précisant la transparence, nous devons utiliser la méthode setPixel32 dont voici la signature : Chapitre 12 ? Programmation Bitmap ? version 0.1.2 32 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org public function setPixel32(x:int, y:int, color:uint):void Dans l?exemple suivant nos modifions la transparence de l?image en utilisant une couleur semi opaque : var monImage:BitmapData = new BitmapData ( 200, 200, true, 0xFFFFFFFF ); var conteneurImage:Bitmap = new Bitmap ( monImage ) addChild ( conteneurImage ); for ( var i:int = 0; i<20000; i++ ) { // positions aléatoires // arrondi automatique, dû au type int var positionX:int = Math.random()*251; var positionY:int = Math.random()*251; // peint les pixels avec un transparence de 40% monImage.setPixel32 ( positionX, positionY, 0x66990000 ); } La figure 12-17 illustre le résultat : Figure 12-17. Dessin par setPixel32. Contrairement aux méthodes setPixel et setPixel32 permettant de peindre un seul pixel par appel, la méthode setPixels permet de peindre une zone de pixels définie par une instance de la classe flash.geom.Rectangle. Chapitre 12 ? Programmation Bitmap ? version 0.1.2 33 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org Voici la signature de la méthode setPixels : public function setPixels(rect:Rectangle, inputByteArray:ByteArray):void Le premier paramètre accueille une instance de la classe Rectangle définissant la zone à peindre, puis en deuxième paramètre un tableau d?octets contenant la couleur de chaque pixel. Nous reviendrons sur la manipulation de données binaire au cours du chapitre 19 intitulé ByteArray. Dans le code suivant, nous créons une image bitmap non transparente : // création d'une image bitmap non transparente var monImage:BitmapData = new BitmapData ( 200, 200, false, 0x99AAAA ); var conteneurImage:Bitmap = new Bitmap ( monImage ) addChild ( conteneurImage ); Puis un tableau binaire contenant la couleur de chaque pixel : // tableau de pixels var pixels:ByteArray = new ByteArray(); for ( var i:int = 0; i< 50; i++ ) { for ( var j:int = 0; j< 50; j++ ) { // store la couleur de chaque pixel 32bits pixels.writeUnsignedInt(0x990000); } } Nous réinitialisons l?index de lecture du flux : pixels.position = 0; Puis, les pixels sont peints au sein de l?image bitmap : monImage.setPixels( new Rectangle (0, 0, 50, 50), pixels ); La figure 12-18 illustre le résultat : Chapitre 12 ? Programmation Bitmap ? version 0.1.2 34 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 12-18. Dessin d?une zone par setPixels. Bien que cette méthode puisse paraître pratique, elle ne s?avère pas être la plus efficace. Nous sommes obligés de remplir un tableau d?octets contenant la couleur de chaque pixel puis d?appeler la méthode setPixels. Dans la plupart des cas nous préférerons utiliser la méthode setPixel qui s?avère plus rapide. A retenir ? La méthode setPixel permet de peindre un pixel au format RVB. ? La méthode setPixel32 permet de peindre un pixel au format ARVB. ? La méthode setPixels permet de peindre un ensemble de pixels, à partir d?un tableau d?octets source. Lire des pixels Pour accéder à la couleur de chaque pixel nous disposons de trois autres méthodes dont voici le détail : ? getPixel : renvoie la couleur du pixel au format RVB selon un point spécifique. Chapitre 12 ? Programmation Bitmap ? version 0.1.2 35 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org ? getPixel32 : renvoie la couleur du pixel au format ARVB selon un point spécifique. ? getPixels : renvoie un tableau d?octets contenant les couleurs de pixels défini selon une zone rectangulaire (flash.geom.Rectangle) Afin de lire les pixels nous pouvons utiliser les méthodes correspondantes getPixel et getPixel32. La méthode getPixel renvoie la couleur du pixel au format RVB et possède la signature suivante : public function getPixel(x:int, y:int):uint Nous allons créer une image de couleur verte et récupérer la couleur d?un pixel : var monImage:BitmapData = new BitmapData ( 200, 200, false, 0xFFFF00 ); var conteneurImage:Bitmap = new Bitmap ( monImage ); addChild ( conteneurImage ); var couleurPixel:Number = monImage.getPixel( 0, 0 ); // affiche : FFFF00 trace( couleurPixel.toString(16).toUpperCase() ); Si nous tentons de récupérer la couleur d?un pixel composant une image transparente, le canal alpha est ignoré : var monImage:BitmapData = new BitmapData ( 200, 200, true, 0x99FF0088 ); var conteneurImage:Bitmap = new Bitmap ( monImage ); addChild ( conteneurImage ); var couleurPixel:Number = monImage.getPixel ( 0, 0 ); // affiche : FF0088 // le canal alpha est ignoré trace( couleurPixel.toString(16).toUpperCase() ); La méthode getPixel32 permet, de récupérer la couleur d?un pixel au format ARVB : var monImage:BitmapData = new BitmapData ( 200, 200, true, 0x99FF0088 ); var conteneurImage:Bitmap = new Bitmap ( monImage ); addChild ( conteneurImage ); var couleurPixel:Number = monImage.getPixel32 ( 0, 0 ); // affiche : 99FF0088 trace( couleurPixel.toString(16).toUpperCase() ); Chapitre 12 ? Programmation Bitmap ? version 0.1.2 36 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org Alors que les méthodes getPixel et getPixel32 existent depuis le lecteur Flash 8, la méthode getPixels a été introduite par ActionScript 3. Celle-ci à l?avantage de renvoyer un tableau binaire contenant les pixels de la zone spécifiée. En ActionScript 1 et 2, nous étions obligés de parcourir manuellement l?image bitmap afin d?obtenir l?ensemble des pixels. Dans le code suivant nous examinons une image bitmap et stockons chaque pixel dans un tableau : // instanciation du logo var monLogo:Logo = new Logo(0,0); // affichage var monConteneur:Bitmap = new Bitmap ( monLogo ); // ajout à l'affichage addChild ( monConteneur ); // positionnement de l'image monConteneur.x = 100; monConteneur.y = 100; // récupération largeur et hauteur var largeur:Number = monConteneur.width; var hauteur:Number = monConteneur.height; // tableau contenant les pixels var tableauPixels:Array = new Array(); for ( var i:int = 0; i< largeur; i++ ) { for ( var j:int = 0; j< hauteur; j++ ) { // récupère la couleur de chaque pixel var couleur:Number = monLogo.getPixel ( i, j ); // stocke chaque couleur au sein d'un tableau tableauPixels.push ( couleur ); } } // affiche : 17424 trace( tableauPixels.length ); En affichant la longueur du tableau, nous voyons que 17424 pixels sont stockés au sein du tableau. Cette opération fonctionnait sans problème sur des images bitmapss de petite taille. Pour des images de dimensions élevées, l?utilisation de boucles imbriquées n?était plus possible. Chapitre 12 ? Programmation Bitmap ? version 0.1.2 37 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org En ActionScript 3, nous utilisons la méthode getPixels : // instanciation du logo var monLogo:Logo = new Logo(0,0); // affiche : 132, 132 (132*132 = 17424) trace( monLogo.width, monLogo.height ); var tableauPixels:ByteArray = monLogo.getPixels ( monLogo.rect ); // affiche : 69696 trace( tableauPixels.length ); Celle-ci retourne un tableau d?octets contenant les pixels de la zone spécifiée. Au sein d?un tableau d?octets, un pixel occupe 4 index. Si nous divisons 69696 par 4 nous obtenons 17424 pixels. L?utilisation de la méthode getPixels s?avère beaucoup plus rapide que la méthode getPixel, car la totalité des pixels d?une image peut être retournée instantanément. A retenir ? La méthode getPixel retourne la couleur d?un pixel au format RVB. ? La méthode getPixel32 retourne la couleur d?un pixel au format ARVB. ? La méthode getPixels retourne un tableau d?octets contenant un ensemble de pixels. Accrochage aux pixels Lorsqu?une image bitmap est affichée nous pouvons garantir l?accrochage aux pixels grâce au paramètre pixelSnapping. Trois constantes sont utilisées afin de déterminer l?accrochage d?une image : ? PixelSnapping.ALWAYS : l?image est toujours accrochée au pixel le plus proche. ? PixelSnapping.AUTO : l?image bitmap est accrochée au pixel le plus proche si elle est dessinée sans rotation ni inclinaison et que son facteur de redimensionnement est compris entre 99,9 % et 100,1 %. ? PixelSnapping.NEVER : l?accrochage aux pixels est désactivé. Par défaut la valeur du paramètre est à auto : Bitmap(bitmapData:BitmapData = null, pixelSnapping:String = "auto", smoothing:Boolean = false) En cas de rotation ou transformation de l?image affichée, celle-ci pourrait voir ses coordonnées glisser sur des coordonnées flottantes, Chapitre 12 ? Programmation Bitmap ? version 0.1.2 38 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org donnant un aspect flouté aux contours de l?image. L?accrochage au pixels garantie un rendu net de l?image affichée. Le lissage Grâce au lissage, la classe Bitmap permet d?améliorer le rendu d?une image lorsque celle-ci est redimensionnée. Nous avons la possibilité d?activer le lissage grâce au dernier paramètre du constructeur de la classe Bitmap : // instanciation du logo var monLogo:Logo = new Logo(0,0); // affichage de l'image en accrochant toujours les pixels et en désactivant le lissage var monConteneur:Bitmap = new Bitmap ( monLogo, PixelSnapping.ALWAYS, false ); // ajout à l'affichage addChild ( monConteneur ); // positionnement de l'image monConteneur.x = 100; monConteneur.y = 100; // redimensionnement monConteneur.scaleX = monConteneur.scaleY = 1.2; // affichage de l'image en accrochant toujours les pixels et en activant le lissage var monConteneurBis:Bitmap = new Bitmap ( monLogo, PixelSnapping.ALWAYS, true ); // ajout à l'affichage addChild ( monConteneurBis ); // positionnement de l'image monConteneurBis.x = 300; monConteneurBis.y = 100; // redimensionnement monConteneurBis.scaleX = monConteneurBis.scaleY = 1.2; La figure 12-19 illustre la différence entre une image non lissée et lissée : Chapitre 12 ? Programmation Bitmap ? version 0.1.2 39 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 12-19. Images bitmaps non lissée et lissée. En adoucissant l?interpolation des pixels, l?image conserve un rendu lissé lorsque celle-ci doit être redimensionnée. Mise en cache des bitmap à l?exécution La mise en cache des bitmap à l?exécution est une fonctionnalité accessible par programmation ou depuis l?environnement auteur, permettant d?accélérer grandement la vitesse de rendu d?un élément vectoriel. Celle-ci est exclusivement réservée aux objets de type DisplayObject. Pour comprendre cette fonctionnalité, nous allons nous attarder quelques instants sur le système de rendu du lecteur Flash. Le terme de mise en cache illustre le mécanisme interne du lecteur visant à créer temporairement en mémoire une version bitmap de l?objet vectoriel. En activant la mise en cache des bitmap sur un DisplayObject de 300 * 300 pixels, une image bitmap 32 bits de même taille est créée en mémoire puis affichée en remplacement de l?objet vectoriel. Cette technique permet au lecteur d?afficher simplement l?image stockée en mémoire et de ne plus rendre les données vectorielles, entraînant ainsi une augmentation significative de la vitesse d?affichage. Pour mettre en cache un objet graphique, il suffit d?activer la propriété cacheAsBitmap de la classe DisplayObject : DisplayObject.cacheAsBitmap = true; Pour désactiver la mise en cache, nous passons la valeur booléenne false à la propriété cacheAsBitmap : DisplayObject.cacheAsBitmap = false; Lorsque la mise en cache est désactivée, le bitmap associé est automatiquement supprimé de la mémoire. Afin de mettre en avant l?intérêt de la mise en cache des bitmap à l?exécution nous allons mettre cette fonctionnalité en pratique. Dans un nouveau document Flash CS3 nous créons un nouveau symbole clip représentant une pomme. La figure 12-20 illustre le symbole utilisé : Chapitre 12 ? Programmation Bitmap ? version 0.1.2 40 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 12-20. Symbole Pomme. Grâce au panneau Liaison nous associons une classe nommée Pomme que nous définissons a côté du document Flash en cours. Celle-ci contient le code suivant : package { import flash.display.MovieClip; import flash.events.Event; public class Pomme extends MovieClip { private var destinationX:Number; private var destinationY:Number; public function Pomme () { addEventListener ( Event.ADDED_TO_STAGE, ajoutAffichage ); addEventListener ( Event.REMOVED_FROM_STAGE, supprimeAffichage ); } public function ajoutAffichage ( pEvt:Event ):void { init(); Chapitre 12 ? Programmation Bitmap ? version 0.1.2 41 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org addEventListener ( Event.ENTER_FRAME, mouvement ); } private function supprimeAffichage ( ):void { removeEventListener ( Event.ENTER_FRAME, mouvement ); } private function init ( ):void { destinationX = Math.random()*(stage.stageWidth-width); destinationY = Math.random()*(stage.stageHeight-height); } private function mouvement ( pEvt:Event ):void { x -= ( x - destinationX ) *.5; y -= ( y - destinationY ) *.5; if ( Math.abs ( x - destinationX ) < 1 && Math.abs ( y - destinationY ) < 1 ) init(); } } } Puis nous affichons différentes instances du symbole Pomme : var conteneur:Sprite = new Sprite(); addChild ( conteneur ); for ( var i:int = 0; i< 100; i++ ) { var maPomme:Pomme = new Pomme(); conteneur.addChild ( maPomme ); } Chaque instance se déplace aléatoirement sur la scène comme l?illustre la figure 12-21 : Chapitre 12 ? Programmation Bitmap ? version 0.1.2 42 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 12-21. Déplacement des pommes. Nous remarquons que l?animation n?est pas très fluide. Nous allons optimiser le rendu en activant la mise en cache des bitmaps à l?exécution : var conteneur:Sprite = new Sprite(); addChild ( conteneur ); for ( var i:int = 0; i< 100; i++ ) { var maPomme:Pomme = new Pomme(); conteneur.addChild ( maPomme ); } stage.addEventListener ( MouseEvent.CLICK, miseEnCache ); function miseEnCache ( pEvt:MouseEvent ):void { var lng:int = conteneur.numChildren; for ( var i:int = 0; i< lng; i++) { var pomme:Pomme = Pomme ( conteneur.getChildAt ( i ) ); pomme.cacheAsBitmap = ! Boolean ( pomme.cacheAsBitmap ); Chapitre 12 ? Programmation Bitmap ? version 0.1.2 43 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org } } Lorsque nous cliquons sur la scène, nous activons ou désactivons la mise en cache des bitmap sur chaque pomme, le rendu est grandement accéléré. L?utilisation de cette fonctionnalité entraîne les mêmes considérations que la création d?images bitmaps avec la classe BitmapData. Chaque pomme mesure 47,5 * 43 pixels, cela correspond pour chaque pomme à une image bitmap de 7,97 Ko en mémoire. Nous affichons dans le code précédent 100 pommes, l?activation de la mise en cache des bitmap consomme donc pour cette animation 797 Ko en mémoire vive. Ainsi, il convient de veiller aux dimensions de l?objet mis en cache. Un élément vectoriel de plus de 2880 pixels ne peut être mis en cache car la création d?une telle image bitmap en mémoire est impossible pour les raisons évoquées en début de chapitre. Cette fonctionnalité de mise en cache des bitmap peut paraître comme la situation à un grand nombre de problèmes liés aux performances, mais il faut prendre en considération certains effets pervers souvent méconnus pouvant inverser la donne. A retenir ? Afin d?activer la mise en cache des bitmap à l?exécution, nous passons la valeur true à la propriété cacheAsBitmap. ? Afin de désactiver la mise en cache des bitmap à l?exécution, nous passons la valeur false à la propriété cacheAsBitmap. ? L?intérêt de la mise en cache des bitmap, consiste à afficher une représentation bitmap de l?objet vectoriel. ? La mise en cache des bitmap augmente sensiblement la vitesse de rendu mais requiert plus de mémoire. ? Lorsque la mise en cache des bitmaps est désactivée, l?image bitmap associée est supprimée de la mémoire. Effets pervers La mise en cache des bitmap à l?exécution est une fonctionnalité qui doit être utilisée avec réflexion. Si celle-ci n?est pas maitrisée, nous obtenons l?inverse du résultat escompté. Lorsqu?un DisplayObject subit une transformation autre qu?une simple translation en x et y, la mise en cache des bitmap est à éviter. Chaque étirement, rotation, changement d?opacité, ou déplacement de Chapitre 12 ? Programmation Bitmap ? version 0.1.2 44 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org la tête de lecture nécessite une mise à jour de l?image bitmap créée en mémoire. Afin de mettre en évidence cet effet pervers, nous ajoutons une image clé à l?image 10 du symbole Pomme comme l?illustre la figure 12-22 : Figure 12-22. Agrandissement du symbole Pomme. Sur cette image clé, nous agrandissons la taille de la pomme. Si nous testons à nouveau notre animation et activons la mise en cache des bitmap. Nous remarquons qu?à chaque passage de la tête de lecture sur l?image 10, le lecteur détecte un changement de taille et met à jour l?image bitmap en cache. Ce processus ralentit grandement l?affichage et annule l?intérêt de la fonctionnalité. De la même manière, si nous procédons simplement à une rotation de chaque instance, le lecteur met à jour le bitmap pour chaque nouvelle image. Nous modifions la méthode mouvement au sein de la classe Pomme : private function mouvement ( pEvt:Event ):void { rotation += 5; x -= ( x - destinationX ) *.5; y -= ( y - destinationY ) *.5; if ( Math.abs ( x - destinationX ) < 1 && Math.abs ( y - destinationY ) < 1 ) init(); } Chapitre 12 ? Programmation Bitmap ? version 0.1.2 45 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org Pour chaque nouvelle image parcourue, le lecteur Flash met à jour le bitmap associé. Il convient donc d?utiliser la mise en cache des bitmap uniquement lorsque l?objet subit une translation en x et y. Nous allons nous intéresser maintenant à un autre effet pervers. La figure 12-23 illustre un symbole clip contenant plusieurs instances du symbole Pomme : Figure 12-23. Clip contenant différentes instances du symbole Pomme. Afin de gagner du temps, nous pourrions être tentés d?activer la mise en cache sur le clip conteneur. Cela entraîne la création d?une image bitmap transparente en mémoire de la taille du conteneur comme l?illustre la figure 12-24 : Chapitre 12 ? Programmation Bitmap ? version 0.1.2 46 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 12-24. Dimensions du clip conteneur. La mise en cache du clip conteneur crée un bitmap de 229,95 Ko en mémoire. Toute la surface transparente est stockée en mémoire inutilement. Il serait plus judicieux d?activer la mise en cache sur chaque instance du symbole Pomme, ce qui nécessiterait 55,79 Ko en mémoire. Il est aussi possible d?activer la mise en cache des bitmap à l?exécution au sein de l?environnement auteur en sélectionnant l?objet graphique à mettre en cache puis en cochant la case correspondante au sein de l?inspecteur de propriétés : Figure 12-25. Mise en cache des bitmaps à l?exécution depuis l?environnement auteur. Il convient d?utiliser cette fonctionnalité avec attention, de plus l?utilisation de filtres est directement liée à la mise en cache des bitmaps à l?exécution. Nous allons nous y intéresser à présent afin de mieux comprendre le fonctionnement des filtres. A retenir ? La mise en cache des bitmaps à l?exécution doit être activée uniquement sur des objets subissant une translation en x et y. ? Le lecteur met à jour le bitmap associé pour tout autre modification. ? Si cette fonctionnalité n?est pas maîtrisée, nous obtenons un ralentissement de la vitesse de rendu et une forte occupation mémoire. Filtrer un élément vectoriel Les filtres sont intimement liés à la notion de bitmap. Lorsque nous utilisons un filtre sur un objet vectoriel, le lecteur Flash crée en mémoire deux images bitmaps afin de produire le résultat filtré. La figure 12-26 illustre le mécanisme interne : Chapitre 12 ? Programmation Bitmap ? version 0.1.2 47 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 12-26. Mécanisme de création de filtres. Le premier bitmap est utilisé pour représenter l?objet non filtré, en réalité le lecteur utilise la mise en cache des bitmaps à l?exécution afin de générer un premier bitmap sur lequel travailler pour appliquer le filtre. Le deuxième bitmap sert à accueillir le rendu filtré. L?utilisation de filtres requiert donc deux fois plus de mémoire que la mise en cache des bitmap à l?exécution et entraîne les mêmes précautions d?utilisation. Afin d?affecter un filtre à un DisplayObject, nous affectons un tableau de filtres à la propriété filters. L?utilisation d?un tableau permet de cumuler plusieurs filtres appliqués à un objet graphique et de modifier la superposition de chaque filtre. Dans un nouveau document Flash CS3, nous posons une instance du symbole Pomme sur la scène et lui donnons comme nom d?occurrence pomme : Chapitre 12 ? Programmation Bitmap ? version 0.1.2 48 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 12-27. Occurrence du symbole Pomme. Puis nous ajoutons dynamiquement un des filtres situés dans le paquetage flash.filters. Voici en détail les différents filtres disponibles : ? flash.filters.BevelFilter : applique un effet de biseau. ? flash.filters.GradientBevelFilter : applique un effet de biseau dégradé. ? flash.filters.BlurFilter : applique un effet de flou. ? flash.filters.GlowFilter : applique un effet de rayonnement. ? flash.filters.GradientGlowFilter : applique un effet de rayonnement dégradé. ? flash.filters.ColorMatrixFilter : applique une transformation de couleurs à chaque pixel. ? flash.filters.ConvolutionFilter : applique un filtre de convolution de matrice. ? flash.filters.DisplacementMapFiler : applique un effet de déplacement sur chaque pixel. ? flash.filters.DropShadowFilter : applique un effet d?ombre portée. Nous allons utiliser la classe BlurFilter pour affecter un filtre de flou, dont voici le constructeur : public fonction BlurFilter(blurX:Number = 4.0, blurY:Number = 4.0, quality:int = 1) Les deux premiers paramètres concernent la dilatation des pixels pour chaque axe. Le dernier paramètre permet de spécifier la qualité du Chapitre 12 ? Programmation Bitmap ? version 0.1.2 49 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org résultat final, il s?agit en réalité du nombre de passage du filtre. Une valeur comprise entre 1 et 3 est généralement utilisée. Quelque soit la qualité du filtre ou dilatation des pixels, le poids des images bitmaps créées en mémoire reste le même. A l?inverse, la vitesse d?affichage est fortement liée à la dilatation des pixels ainsi que la qualité. Pour des raisons de performances il est fortement recommandé de toujours utiliser des multiples de 2 pour les quantités de flous et de ne jamais dépasser une qualité de 15. Dans le code suivant nous ajoutons un filtre de flou : // création du filtre de flou var filtreFlou:BlurFilter = new BlurFilter (10, 10, 1); // tableau de filtres var filtresEnCours:Array = new Array(); // ajout du filtre de flou filtresEnCours.push ( filtreFlou ); // affectation du filtre pomme.filters = filtresEnCours; La figure 12-28 illustre le résultat : Figure 12-28. Filtre de flou. Chapitre 12 ? Programmation Bitmap ? version 0.1.2 50 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org Trois qualités de filtres sont disponibles et accessible depuis des constantes de la classe BitmapFilterQuality : ? BitmapFilterQuality.LOW : qualité inférieure. ? BitmapFilterQuality.MEDIUM : qualité moyenne. ? BitmapFilterQuality.HIGH : qualité supérieure, s?approche du flou gaussien. Il est donc possible de spécifier manuellement la qualité du filtre, mais pour des raisons de portabilité nous préférons toujours utiliser des constantes de classe : // création du filtre de flou var filtreFlou:BlurFilter = new BlurFilter ( 10, 10, BitmapFilterQuality.HIGH ); // tableau de filtres var filtresEnCours:Array = new Array(); // ajout du filtre de flou filtresEnCours.push ( filtreFlou ); // affectation du filtre pomme.filters = filtresEnCours; Une fois le filtre appliqué nous remarquons que la mise en cache des bitmaps est activée automatiquement : // création du filtre de flou var filtreFlou:BlurFilter = new BlurFilter ( 10, 10, BitmapFilterQuality.HIGH ); // tableau de filtres var filtresEnCours:Array = new Array(); // ajout du filtre de flou filtresEnCours.push ( filtreFlou ); // affectation du filtre pomme.filters = filtresEnCours; // affiche : true trace( pomme.cacheAsBitmap ); L?instance du symbole Pomme mesure 122 * 110 pixels. Dans notre exemple, le filtre de flou pèse en mémoire 104,84 Ko. Afin de faciliter le calcul du poids d?un objet en mémoire contenant différents filtres nous pouvons ajouter au sein de notre classe BitmapOutils une nouvelle méthode appelée poidsFiltres : package org.outils { import flash.display.BitmapData; import flash.display.DisplayObject; Chapitre 12 ? Programmation Bitmap ? version 0.1.2 51 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org public class BitmapOutils { public static function hexArgb ( pCouleur:Number ):Object { var composants:Object = new Object(); composants.alpha = (pCouleur >>> 24) & 0xFF; composants.rouge = (pCouleur >>> 16) & 0xFF; composants.vert = (pCouleur >>> 8) & 0xFF; composants.bleu = pCouleur & 0xFF; return composants; } public static function argbHex ( pAlpha:int, pRouge:int, pVert:int, pBleu:int ):uint { return ( pAlpha << 24 | pRouge << 16 | pVert << 8 | pBleu ); } public static function poids ( pBitmapData:BitmapData ):Number { return (pBitmapData.width * pBitmapData.height) * 4; } public static function poidsFiltres ( pDisplayObject:DisplayObject ):Number { return (((pDisplayObject.width * pDisplayObject.height) * 4) * pDisplayObject.filters.length) * 2; } } } Cette méthode nous permet de connaître le poids total d?un objet filtré en mémoire : import org.outils.BitmapOutils; // création du filtre de flou var filtreFlou:BlurFilter = new BlurFilter ( 10, 10, BitmapFilterQuality.HIGH ); // tableau de filtres var filtresEnCours:Array = new Array(); // ajout du filtre de flou filtresEnCours.push ( filtreFlou ); Chapitre 12 ? Programmation Bitmap ? version 0.1.2 52 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org // affectation du filtre pomme.filters = filtresEnCours; // affiche : 112.08052734375 trace( BitmapOutils.poidsFiltres ( pomme ) / 1024 ); L?instance du symbole Pomme pèse en mémoire environ 112 Ko. Si nous souhaitons ajouter de nouveaux filtres il n?est pas possible d?ajouter directement un filtre au tableau filters : // création du filtre de flou var filtreFlou:BlurFilter = new BlurFilter ( 10, 10, BitmapFilterQuality.HIGH ); // tableau de filtres var filtresEnCours:Array = new Array(); // ajout du filtre de flou filtresEnCours.push ( filtreFlou ); // affectation du filtre pomme.filters = filtresEnCours; // création d'une ombre portée var ombrePortee:DropShadowFilter = new DropShadowFilter (); // ajout du filtre d'ombre portée filtresEnCours.push ( ombrePortee ); Nous devons obligatoirement affecter à nouveau à la propriété filters un tableau contenant la totalité des filtres : // création du filtre de flou var filtreFlou:BlurFilter = new BlurFilter ( 10, 10, BitmapFilterQuality.HIGH ); // tableau de filtres var filtresEnCours:Array = new Array(); // ajout du filtre de flou filtresEnCours.push ( filtreFlou ); // affectation du filtre pomme.filters = filtresEnCours; // création d'une ombre portée var ombrePortee:DropShadowFilter = new DropShadowFilter (); // ajout du filtre d'ombre portée filtresEnCours.push ( ombrePortee ); // affectation des filtres pomme.filters = filtresEnCours; Le code précédent génère le résultat suivant : Chapitre 12 ? Programmation Bitmap ? version 0.1.2 53 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 12-29. Filtre de flou et ombre portée. Si nous évaluons à nouveau le poids de l?instance du symbole Pomme en mémoire, nous remarquons que son poids en mémoire a doublé et nécessite désormais 224 Ko en mémoire : import org.outils.BitmapOutils; // création du filtre de flou var filtreFlou:BlurFilter = new BlurFilter ( 10, 10, BitmapFilterQuality.HIGH ); // tableau de filtres var filtresEnCours:Array = new Array(); // ajout du filtre de flou filtresEnCours.push ( filtreFlou ); // affectation du filtre pomme.filters = filtresEnCours; // création d'une ombre portée var ombrePortee:DropShadowFilter = new DropShadowFilter (); // ajout du filtre d'ombre portée filtresEnCours.push ( ombrePortee ); // affectation des filtres pomme.filters = filtresEnCours; // affiche : 224.1610546875 trace( BitmapOutils.poidsFiltres ( pomme ) / 1024 ); Chapitre 12 ? Programmation Bitmap ? version 0.1.2 54 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org Il n?existe aucune méthode native permettant d?inverser la position des filtres au sein du tableau filters. Si nous souhaitons supprimer un filtre, nous devons travailler avec les méthodes de classe Array puis affecter à nouveau le tableau de filtres modifié. Dans le code suivant nous supprimons le filtre de flou : import org.outils.BitmapOutils; // création du filtre de flou var filtreFlou:BlurFilter = new BlurFilter ( 10, 10, BitmapFilterQuality.HIGH ); // tableau de filtres var filtresEnCours:Array = new Array(); // ajout du filtre de flou filtresEnCours.push ( filtreFlou ); // affectation du filtre pomme.filters = filtresEnCours; // création d'une ombre portée var ombrePortee:DropShadowFilter = new DropShadowFilter (); // ajout du filtre d'ombre portée filtresEnCours.push ( ombrePortee ); // suppression du filtre de flou filtresEnCours.splice ( 0, 1 ); // affectation des filtres pomme.filters = filtresEnCours; // affiche : 112.08052734375 trace( BitmapOutils.poidsFiltres ( pomme ) / 1024 ); La figure 12-30 illustre le résultat : Chapitre 12 ? Programmation Bitmap ? version 0.1.2 55 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 12-30. Ombre portée. A l?aide de la méthode splice nous avons supprimé le filtre de flou positionné à l?index 0. Dans certaines situations, si nous ne connaissons pas la position d?un filtre au sein du tableau interne, nous pouvons utiliser la méthode indexOf de la classe Array : // création du filtre de flou var filtreFlou:BlurFilter = new BlurFilter ( 10, 10, BitmapFilterQuality.HIGH ); // tableau de filtres var filtresEnCours:Array = new Array(); // ajout du filtre de flou filtresEnCours.push ( filtreFlou ); // affectation du filtre pomme.filters = filtresEnCours; // création d'une ombre portée var ombrePortee:DropShadowFilter = new DropShadowFilter (); // ajout du filtre d'ombre portée filtresEnCours.push ( ombrePortee ); // récupère la position du filtre var position:int = filtresEnCours.indexOf( filtreFlou ); // si le filtre est trouvé if ( position != -1 ) { Chapitre 12 ? Programmation Bitmap ? version 0.1.2 56 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org // le filtre est supprimé filtresEnCours.splice ( position, 1 ); } else trace ( "Filtre non présent !" ); // affectation des filtres pomme.filters = filtresEnCours; Grâce à la méthode indexOf, nous n?avons pas besoin de savoir la position du filtre dans le tableau interne. L?index retourné nous permet de supprimer le filtre correspondant. Il n?existe pas de méthode dispose pour libérer la mémoire utilisée par un filtre. Si nous souhaitons libérer les ressources nous devons supprimer les références pointant vers le filtre. Dans le code suivant, nous rendons le filtre de flou éligible à la suppression par le ramasse- miettes : // création du filtre de flou var filtreFlou:BlurFilter = new BlurFilter ( 10, 10, BitmapFilterQuality.HIGH ); // tableau de filtres var filtresEnCours:Array = new Array(); // ajout du filtre de flou filtresEnCours.push ( filtreFlou ); // affectation du filtre pomme.filters = filtresEnCours; // création d'une ombre portée var ombrePortee:DropShadowFilter = new DropShadowFilter (); // ajout du filtre d'ombre portée filtresEnCours.push ( ombrePortee ); // récupère la position du filtre var position:int = filtresEnCours.indexOf( filtreFlou ); // si le filtre est trouvé if ( position != -1 ) { // le filtre est supprimé filtresEnCours.splice ( position, 1 ); // puis désactivé filtreFlou = null; } else trace ( "Filtre non présent !" ); // affectation des filtres pomme.filters = filtresEnCours; Afin de supprimer la totalité des filtres affectés à un DisplayObject, nous affectons à la propriété filters un tableau vide : // création du filtre de flou Chapitre 12 ? Programmation Bitmap ? version 0.1.2 57 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org var filtreFlou:BlurFilter = new BlurFilter ( 10, 10, BitmapFilterQuality.HIGH ); // tableau de filtres var filtresEnCours:Array = new Array(); // ajout du filtre de flou filtresEnCours.push ( filtreFlou ); // affectation du filtre pomme.filters = filtresEnCours; // création d'une ombre portée var ombrePortee:DropShadowFilter = new DropShadowFilter (); // ajout du filtre d'ombre portée filtresEnCours.push ( ombrePortee ); // affectation des filtres pomme.filters = filtresEnCours; // écrase le tableau de filtres existants filtresEnCours = new Array(); // désactivation des filtres filtreFlou = null; ombrePortee = null; // mise à jour de l'affichage pomme.filters = filtresEnCours; La figure 12-31 illustre le résultat final : Figure 12-31. Suppression des filtres. Chapitre 12 ? Programmation Bitmap ? version 0.1.2 58 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org Une fois que les filtres ne sont plus liés au DisplayObject, la mise en cache des bitmaps à l?exécution est désactivée automatiquement : // création du filtre de flou var filtreFlou:BlurFilter = new BlurFilter ( 10, 10, BitmapFilterQuality.HIGH ); // tableau de filtres var filtresEnCours:Array = new Array(); // ajout du filtre de flou filtresEnCours.push ( filtreFlou ); // affectation du filtre pomme.filters = filtresEnCours; // création d'une ombre portée var ombrePortee:DropShadowFilter = new DropShadowFilter (); // ajout du filtre d'ombre portée filtresEnCours.push ( ombrePortee ); // affectation des filtres pomme.filters = filtresEnCours; // écrase le tableau de filtres existants filtresEnCours = new Array(); // désactivation des filtres filtreFlou = null; ombrePortee = null; // mise à jour de l'affichage pomme.filters = filtresEnCours; // affiche : false trace( pomme.cacheAsBitmap ); Attention, la désactivation des filtres en mémoire par suppression des références repose sur l?intervention du ramasse-miettes. A retenir Chapitre 12 ? Programmation Bitmap ? version 0.1.2 59 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org ? L?utilisation de filtres sur un DisplayObject active automatiquement la mise en cache des bitmaps à l?exécution. ? En plus du premier bitmap créé lors de la mise en cache des bitmaps à l?exécution, un deuxième est créé afin de produire le rendu filtré. ? Les mêmes précautions d?utilisation liées à l?activation de la mise en cache des bitmaps doivent être appliquées lors de l?utilisation de filtres appliqués à des éléments vectoriels. ? La désactivation des filtres s?appuie sur l?intervention du ramasse- miettes. Filtrer une image bitmap Il est aussi possible d?appliquer différents filtres à une image bitmap. Pour cela nous utilisons la méthode applyFilter de la classe BitmapData dont voici la signature : public function applyFilter(sourceBitmapData:BitmapData, sourceRect:Rectangle, destPoint:Point, filter:BitmapFilter):void Celle ci accepte quatre paramètres : ? sourceBitmapData : les données bitmaps à filtrer. ? sourceRect : la zone sur laquelle le filtre est appliqué. ? destPoint : point de départ utilisé par le rectangle. ? Filter : le filtre à appliquer. Dans le code suivant, nous instancions une image provenant de la bibliothèque. Un filtre de flou est partiellement appliqué : // instanciation du logo var donneesBitmap:Logo = new Logo ( 0, 0 ); var monImage:Bitmap = new Bitmap ( donneesBitmap ); monImage.x = (stage.stageWidth - monImage.width) / 2; monImage.y = (stage.stageHeight - monImage.height) / 2 addChild ( monImage ); // création du filtre de flou var filtreFlou:BlurFilter = new BlurFilter ( 10, 10, BitmapFilterQuality.HIGH ); // définition d'une surface var zone:Rectangle = new Rectangle ( 0, 0, 80, 60 ); // affectation du filtre de flou sur une surface limitée donneesBitmap.applyFilter( donneesBitmap, zone, new Point(0,0), filtreFlou ); La figure 12-32 illustre le résultat : Chapitre 12 ? Programmation Bitmap ? version 0.1.2 60 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 12-32. Filtre partiel. Afin d?affecter le filtre sur la surface totale de l?image, nous passons l?objet Rectangle accessible par la propriété rect de la classe BitmapData : // instanciation du logo var donneesBitmap:Logo = new Logo ( 0, 0 ); var monImage:Bitmap = new Bitmap ( donneesBitmap ); monImage.x = (stage.stageWidth - monImage.width) / 2; monImage.y = (stage.stageHeight - monImage.height) / 2 addChild ( monImage ); // création du filtre de flou var filtreFlou:BlurFilter = new BlurFilter ( 10, 10, BitmapFilterQuality.HIGH ); // définition d'une surface var zone:Rectangle = donneesBitmap.rect; // affectation du filtre de flou sur une surface limitée donneesBitmap.applyFilter( donneesBitmap, zone, new Point(0,0), filtreFlou ); Ainsi, le filtre est appliqué sur la totalité de l?image : Chapitre 12 ? Programmation Bitmap ? version 0.1.2 61 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org La figure 12-33. Image entièrement filtrée. Lorsqu?un filtre est appliqué à une image bitmap à l?aide de la méthode applyFilter, le lecteur ne crée aucun bitmap supplémentaire en mémoire afin de produire le rendu filtré. Les pixels de l?image bitmap sont directement travaillés sur l?instance de BitmapData. Lorsqu?un filtre est associé à un DisplayObject, il est possible de faire marche arrière et de supprimer le filtre. A l?inverse, lorsqu?un filtre est appliqué à une image bitmap, les pixels sont définitivement modifiés. Il est impossible de faire marche arrière. A retenir ? L?utilisation de filtres sur une instance de BitmapData ne crée aucune image bitmap supplémentaire en mémoire. Animer un filtre Afin de donner du mouvement à un filtre nous devons modifier les valeurs correspondantes puis appliquer constamment le filtre afin de mettre à jour l?affichage. Dans le cas de filtres appliqués à un DisplayObject nous pouvons écrire le code suivant : stage.addEventListener ( Event.ENTER_FRAME, animFiltre ); var tableauFiltres:Array = new Array(); Chapitre 12 ? Programmation Bitmap ? version 0.1.2 62 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org var filtreFlou:BlurFilter = new BlurFilter ( 10, 10, BitmapFilterQuality.HIGH ); tableauFiltres.push ( filtreFlou ); var i:Number = 0; function animFiltre ( pEvt:Event ):void { // valeur oscillant entre 1 et 21 var oscillation:Number = Math.floor ( Math.sin ( i += .2 ) * 10 + 11 ); filtreFlou.blurX = filtreFlou.blurY = oscillation pomme.filters = tableauFiltres; } Le code précédent fait osciller la quantité de flou entre 1 et 21 donnant un effet d?ondulation. Pour reproduire le même effet sur une instance de BitmapData, nous utilisons la méthode applyFilter : var donneesBitmap:Logo = new Logo ( 0, 0 ); var copieDonneesBitmap:BitmapData = donneesBitmap.clone(); var monImage:Bitmap = new Bitmap ( donneesBitmap ); monImage.x = (stage.stageWidth - monImage.width) / 2; monImage.y = (stage.stageHeight - monImage.height) / 2 addChild ( monImage ); stage.addEventListener ( Event.ENTER_FRAME, animFiltre ); var tableauFiltres:Array = new Array(); var filtreFlou:BlurFilter = new BlurFilter ( 10, 10, BitmapFilterQuality.HIGH ); tableauFiltres.push ( filtreFlou ); var i:Number = 0; function animFiltre ( pEvt:Event ):void { // valeur oscillant entre 1 et 20 var oscillation:Number = Math.floor ( Math.sin ( i += .2 ) * 10 + 11 ); filtreFlou.blurX = filtreFlou.blurY = oscillation; donneesBitmap.copyPixels ( copieDonneesBitmap, copieDonneesBitmap.rect, new Point ( 0, 0 ) ); donneesBitmap.applyFilter ( donneesBitmap, donneesBitmap.rect, new Point ( 0, 0 ), filtreFlou ); } Chapitre 12 ? Programmation Bitmap ? version 0.1.2 63 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org Très souvent, les filtres sont utilisés sur des DisplayObject déjà animés. Dans le cas d?une agence Web, le graphiste anime généralement des objets et affecte différents filtres. L?animation du filtre est alors gérée automatiquement par le lecteur Flash. Pour chaque étape de l?animation, le lecteur Flash met à jour les deux bitmaps en mémoire afin de produire le rendu filtré. Ce processus s?avère complexe et peut ralentir grandement la vitesse d?affichage. Il est donc conseillé de ne pas utiliser un trop grand nombre de filtres sous peine de voir les performances de l?animation chuter. Rendu bitmap d?objet vectoriels La méthode draw de la classe BitmapData s?avère être une méthode très intéressante. Celle-ci permet de rendre sous forme bitmap n?importe quel DisplayObject. Cette fonctionnalité ouvre un grand nombre de possibilités que nous explorerons au cours des prochains chapitres. Voici la signature de la méthode draw : public function draw(source:IBitmapDrawable, matrix:Matrix = null, colorTransform:ColorTransform = null, blendMode:String = null, clipRect:Rectangle = null, smoothing:Boolean = false):void Chacun des paramètres permet de travailler sur l?image bitmap : ? source : objet source à dessiner. ? matrix : matrice de transformation ? colorTransform : objet de transformation de couleur permettant de teinter l?image bitmap générée. ? blendMode : mode de fondu à appliquer à l?image bitmap générée. ? clipRect : surface définissant la zone à dessiner. ? smoothing : booléen définissant le lissage de l?image bitmap générée. Dans un nouveau document Flash CS3, nous plaçons une instance du symbole Pomme sur la scène et lui donnons comme nom d?occurrence pomme. L?instance a été agrandie d?environ 300% : Chapitre 12 ? Programmation Bitmap ? version 0.1.2 64 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org La figure 12-34. Instance du symbole Pomme. Nous allons rendre sous forme bitmap une instance du symbole Pomme posée sur la scène. Pour cela nous créons une image bitmap afin d?accueillir les pixels dessinés : // création des données bitmaps aux dimensions de l'image source var donneesBitmap:BitmapData = new BitmapData ( pomme.width, pomme.height, false, 0xCCCCCC ); var monImage:Bitmap = new Bitmap ( donneesBitmap ); // positionnement de la copie bitmap monImage.x += 310; monImage.y += pomme.y addChild ( monImage ); Nous récupérons les dimensions de l?objet à rendre sous forme bitmap afin de créer un bitmap de destination aux dimensions identiques. Souvenez-vous que la création de bitmap entraîne une forte occupation mémoire, chaque pixel doit être optimisé. La figure 12-35 illustre le résultat : Chapitre 12 ? Programmation Bitmap ? version 0.1.2 65 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org La figure 12-35. Image bitmap. Puis nous passons à la méthode draw l?instance du symbole à rendre sous forme bitmap : // création des données bitmaps aux dimensions de l'image source var donneesBitmap:BitmapData = new BitmapData ( pomme.width, pomme.height, false, 0xCCCCCC ); var monImage:Bitmap = new Bitmap ( donneesBitmap ); // positionnement de la copie bitmap monImage.x += 310; monImage.y += pomme.y addChild ( monImage ); // rend sous forme bitmap les données vectorielles donneesBitmap.draw ( pomme ); Le résultat suivant est généré : Chapitre 12 ? Programmation Bitmap ? version 0.1.2 66 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org La figure 12-36. Rendu bitmap. Nous remarquons que le rendu bitmap ne possède pas les mêmes dimensions que l?instance. La méthode draw dessine par défaut l?objet selon son état en bibliothèque. De la même manière si nous faisons subir une rotation ou un étirement à l?instance du symbole, le rendu bitmap ne reflète pas ces modifications : Chapitre 12 ? Programmation Bitmap ? version 0.1.2 67 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org La figure 12-37. Rendu sans transformations. Si nous souhaitons prendre en considération les transformations actuelles de l?objet à dessiner, nous devons utiliser une matrice de transformation. Dans notre exemple, nous allons passer la matrice de transformation de l?instance. Celle-ci est accessible depuis la propriété matrix de la classe Transform, elle-même accessible depuis la propriété transform de tout DisplayObject : // création des données bitmaps aux dimensions de l'image source var donneesBitmap:BitmapData = new BitmapData ( pomme.width, pomme.height, false, 0xCCCCCC ); var monImage:Bitmap = new Bitmap ( donneesBitmap ); // positionnement de la copie bitmap monImage.x += 310; monImage.y += pomme.y addChild ( monImage ); // transformations actuelles de la pomme var transformationPomme:Matrix = pomme.transform.matrix; // rend sous forme bitmap les données vectorielles en conservant les transformations donneesBitmap.draw ( pomme, transformationPomme ); Ainsi, le rendu bitmap prend en considération les transformations de l?instance. La figure 12-37 illustre le résultat : Chapitre 12 ? Programmation Bitmap ? version 0.1.2 68 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org La figure 12-37. Rendu avec transformations. Les pixels ont donc été dessinés avec un décalage correspondant à la position de l?instance par rapport à la scène. Dans notre cas, nous souhaitons conserver les mêmes dimensions. Afin de décaler les pixels du bitmap, nous utilisons les propriétés tx et ty de la classe Matrix. Celles-ci nous permettent de modifier la translation des pixels dans l?image bitmap générée : // création des données bitmaps aux dimensions de l'image source var donneesBitmap:BitmapData = new BitmapData ( pomme.width, pomme.height, false, 0xCCCCCC ); var monImage:Bitmap = new Bitmap ( donneesBitmap ); // positionnement de la copie bitmap monImage.x += 310; monImage.y += pomme.y addChild ( monImage ); // transformations actuelles de la pomme var transformationPomme:Matrix = pomme.transform.matrix; // repositionne les pixels transformationPomme.tx = 0; transformationPomme.ty = 0; // rend sous forme bitmap les données vectorielles en conservant les transformations donneesBitmap.draw ( pomme, transformationPomme ); Chapitre 12 ? Programmation Bitmap ? version 0.1.2 69 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org La figure 12-38 illustre le résultat : La figure 12-38. Translation. Nous pouvons voir que l?image bitmap est tronquée sur le coté gauche et sur le coté supérieur de l?image. Cela vient du fait que les vecteurs composant le symbole Pomme passent en dessous de 0 pour l?axe x et y. Attention, lorsque la méthode draw est utilisée, le point d?enregistrement de l?objet rasterisé devient le point haut gauche du BitmapData généré. Afin d?éviter cette coupure nous pouvons déplacer simplement les pixels en procèdent à une simple translation de quelques pixels : // décalage en pixels var decalage:int = 5; // création des données bitmaps aux dimensions de l'image source var donneesBitmap:BitmapData = new BitmapData ( pomme.width+decalage, pomme.height+decalage, false, 0xCCCCCC ); var monImage:Bitmap = new Bitmap ( donneesBitmap ); // positionnement de la copie bitmap monImage.x += 310; monImage.y += pomme.y - decalage; addChild ( monImage ); Chapitre 12 ? Programmation Bitmap ? version 0.1.2 70 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org // transformations actuelles de la pomme var transformationPomme:Matrix = pomme.transform.matrix; // repositionne les pixels transformationPomme.tx = decalage; transformationPomme.ty = decalage; // rend sous forme bitmap les donneés vectorielles en conservant les transformations donneesBitmap.draw ( pomme, transformationPomme ); Puis nous adaptons la taille du bitmap de destination et supprimons la couleur de fond : // décalage en pixels var decalage:int = 5; // création des données bitmaps aux dimensions de l'image source var donneesBitmap:BitmapData = new BitmapData ( pomme.width+decalage, pomme.height+decalage ); var monImage:Bitmap = new Bitmap ( donneesBitmap ); // positionnement de la copie bitmap monImage.x += 310; monImage.y += pomme.y - decalage; addChild ( monImage ); // transformations actuelles de la pomme var transformationPomme:Matrix = pomme.transform.matrix; // repositionne les pixels transformationPomme.tx = decalage; transformationPomme.ty = decalage; // rend sous forme bitmap les donneés vectorielles en conservant les transformations donneesBitmap.draw ( pomme, transformationPomme ); La figure 12-39 illustre le résultat : Chapitre 12 ? Programmation Bitmap ? version 0.1.2 71 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org La figure 12-39. Rendu bitmap de l?instance. Comme vous pouvez l?imaginer, l?évaluation manuelle d?une valeur de translation ne s?avère pas la solution la plus souple. Dans la pratique, il serait idéal de pouvoir déterminer dynamiquement la translation nécessaire à l?image bitmap sans risquer que celle-ci soit coupée lors de la capture. Afin d?évaluer les débordements de l?objet vectoriel, nous pouvons utiliser la méthode getBounds de la classe DisplayObject. Dans le code suivant nous déterminons le débordement du symbole Pomme à l?aide de la méthode getBounds : var debordement:Rectangle = pomme.getBounds ( pomme ); // affiche : (x=-1, y=-0.5, w=48.75, h=44.5) trace( debordement ); La méthode getBounds renvoie un objet Rectangle dont les propriétés x et y nous renvoient les débordements pour les axes respectifs. Grâce à celles-ci nous pouvons établir une translation dynamique, plus souple qu?un décalage manuel : // création des données bitmaps aux dimensions de l'image source var donneesBitmap:BitmapData = new BitmapData ( pomme.width, pomme.height ); var monImage:Bitmap = new Bitmap ( donneesBitmap ); Chapitre 12 ? Programmation Bitmap ? version 0.1.2 72 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org // positionnement de la copie bitmap monImage.x += 310; monImage.y += pomme.y; addChild ( monImage ); // transformations actuelles de la pomme var transformationPomme:Matrix = pomme.transform.matrix; // repositionne les pixels transformationPomme.tx = 0; transformationPomme.ty = 0; var debordement:Rectangle = pomme.getBounds ( pomme ); // décalage répercuté sur la matrice de transformation transformationPomme.translate ( -debordement.x * pomme.scaleX, -debordement.y * pomme.scaleY ); // rend sous forme bitmap les donneés vectorielles en conservant les transformations // grâce à la translation dynamique réalisée, l'image bitmap n'est pas coupée donneesBitmap.draw ( pomme, transformationPomme ); Nous obtenons ainsi de manière dynamique le même résultat que précédemment : La figure 12-40. Rendu bitmap de l?instance avec évaluation dynamique du débordement. Comme nous l?avons vu précédemment, le symbole Pomme possède son point d?enregistrement en haut à gauche. Grâce à la détection du Chapitre 12 ? Programmation Bitmap ? version 0.1.2 73 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org débordement du symbole, nous pouvons ainsi gérer la capture de symboles ayant leur point d?enregistrement au centre. Si nous déplaçons le point d?enregistrement du symbole Pomme en son centre, la capture fonctionne parfaitement sans aucune modification du code excepté le positionnement de l?image bitmap : // positionnement de la copie bitmap monImage.x += 310; monImage.y = pomme.y - (pomme.height * .5); Nous allons à présent récupérer la couleur de chaque pixel composant notre image bitmap. Pour cela nous devons cliquer sur la pomme bitmap et accéder à la couleur du pixel cliqué. La classe Bitmap n?est pas une sous-classe de la classe InteractiveObject, ainsi si nous souhaitons interagir avec une image bitmap nous devons au préalable la placer dans un conteneur de type InteractiveObject. Pour cela, nous utilisons la classe flash.display.Sprite. Nous plaçons l?image bitmap au sein d?un conteneur : // création d'un conteneur pour l'image bitmap var conteneurImage:Sprite = new Sprite(); // ajout à la liste d'affichage addChild ( conteneurImage ); // création des données bitmaps aux dimensions de l'image source var donneesBitmap:BitmapData = new BitmapData ( pomme.width, pomme.height ); var monImage:Bitmap = new Bitmap ( donneesBitmap ); // ajout de l'image au conteneur conteneurImage.addChild ( monImage ); // positionnement de la copie bitmap conteneurImage.x += 310; conteneurImage.y += pomme.y - (pomme.height * .5); // transformations actuelles de la pomme var transformationPomme:Matrix = pomme.transform.matrix; // repositionne les pixels transformationPomme.tx = 0; transformationPomme.ty = 0; var debordement:Rectangle = pomme.getBounds ( pomme ); // décalage répercuté sur la matrice de transformation transformationPomme.translate ( -debordement.x * pomme.scaleX, -debordement.y * pomme.scaleY ); // rend sous forme bitmap les donneés vectorielles en conservant les transformations // grâce à la translation dynamique réalisée, l'image bitmap n'est pas coupée Chapitre 12 ? Programmation Bitmap ? version 0.1.2 74 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org donneesBitmap.draw ( pomme, transformationPomme ); Puis nous écoutons différents événements liés à l?interactivité sur le conteneur afin de récupérer la couleur du pixel survolé : // création d'un conteneur pour l'image bitmap var conteneurImage:Sprite = new Sprite(); // ajout à la liste d'affichage addChild ( conteneurImage ); // création des données bitmaps aux dimensions de l'image source var donneesBitmap:BitmapData = new BitmapData ( pomme.width, pomme.height ); var monImage:Bitmap = new Bitmap ( donneesBitmap ); // ajout de l'image au conteneur conteneurImage.addChild ( monImage ); // positionnement de la copie bitmap conteneurImage.x += 310; conteneurImage.y += pomme.y - (pomme.height * .5); // transformations actuelles de la pomme var transformationPomme:Matrix = pomme.transform.matrix; // repositionne les pixels transformationPomme.tx = 0; transformationPomme.ty = 0; var debordement:Rectangle = pomme.getBounds ( pomme ); // décalage répercuté sur la matrice de transformation transformationPomme.translate ( -debordement.x * pomme.scaleX, -debordement.y * pomme.scaleY ); // rend sous forme bitmap les donneés vectorielles en conservant les transformations // grâce à la translation dynamique réalisée, l'image bitmap n'est pas coupée donneesBitmap.draw ( pomme, transformationPomme ); // écoute des événements MouseEvent.MOUSE_DOWN et MouseEvent.MOUSE_UP // auprès du conteneur et de l'objet Stage (cela permet de capter le relâchement en dehors du conteneur) conteneurImage.addEventListener ( MouseEvent.MOUSE_DOWN, clicSouris ); stage.addEventListener ( MouseEvent.MOUSE_UP, relacheSouris ); function clicSouris ( pEvt:MouseEvent ):void { bougeSouris ( pEvt ); pEvt.currentTarget.addEventListener ( MouseEvent.MOUSE_MOVE, bougeSouris ); } function relacheSouris ( pEvt:MouseEvent ):void { conteneurImage.removeEventListener ( MouseEvent.MOUSE_MOVE, bougeSouris ); Chapitre 12 ? Programmation Bitmap ? version 0.1.2 75 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org } function bougeSouris ( pEvt:MouseEvent ):void { // accède à la couleur du pixel survolé var couleurPixel:Number = donneesBitmap.getPixel32 ( pEvt.localX, pEvt.localY ); // affiche : ffd8631f trace( couleurPixel.toString ( 16 ) ); } Enfin, nous ajoutons une image bitmap de taille réduite et une légende afin d?afficher la couleur survolée : // création d'un conteneur pour l'image bitmap var conteneurImage:Sprite = new Sprite(); // ajout à la liste d'affichage addChild ( conteneurImage ); // création des données bitmaps aux dimensions de l'image source var donneesBitmap:BitmapData = new BitmapData ( pomme.width, pomme.height ); var monImage:Bitmap = new Bitmap ( donneesBitmap ); // ajout de l'image au conteneur conteneurImage.addChild ( monImage ); // positionnement de la copie bitmap conteneurImage.x += 310; conteneurImage.y += pomme.y - (pomme.height * .5); // transformations actuelles de la pomme var transformationPomme:Matrix = pomme.transform.matrix; // repositionne les pixels transformationPomme.tx = 0; transformationPomme.ty = 0; var debordement:Rectangle = pomme.getBounds ( pomme ); // décalage répercuté sur la matrice de transformation transformationPomme.translate ( -debordement.x * pomme.scaleX, -debordement.y * pomme.scaleY ); // rend sous forme bitmap les donneés vectorielles en conservant les transformations // grâce à la translation dynamique réalisée, l'image bitmap n'est pas coupée donneesBitmap.draw ( pomme, transformationPomme ); // écoute des événements MouseEvent.MOUSE_DOWN et MouseEvent.MOUSE_UP // auprès du conteneur et de l'objet Stage (cela permet de capter le relâchement en dehors du conteneur) conteneurImage.addEventListener ( MouseEvent.MOUSE_DOWN, clicSouris ); stage.addEventListener ( MouseEvent.MOUSE_UP, relacheSouris ); function clicSouris ( pEvt:MouseEvent ):void Chapitre 12 ? Programmation Bitmap ? version 0.1.2 76 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org { bougeSouris ( pEvt ); pEvt.currentTarget.addEventListener ( MouseEvent.MOUSE_MOVE, bougeSouris ); } function relacheSouris ( pEvt:MouseEvent ):void { conteneurImage.removeEventListener ( MouseEvent.MOUSE_MOVE, bougeSouris ); } function bougeSouris ( pEvt:MouseEvent ):void { // accède à la couleur du pixel survolé var couleurPixel:Number = donneesBitmap.getPixel32 ( pEvt.localX, pEvt.localY ); // force le rafraichissement pEvt.updateAfterEvent(); // remplissage du bitmap d'aperçu apercuCouleur.fillRect ( apercuCouleur.rect, couleurPixel ); } // création d'une image bitmap d'aperçu de selection de couleur var apercuCouleur:BitmapData = new BitmapData ( 120, 60, false, 0 ); var imageApercu:Bitmap = new Bitmap ( apercuCouleur ); // positionnement imageApercu.x = 210; imageApercu.y = 320; // ajout à la liste d'affichage addChild ( imageApercu ); // création d'une légende var legende:TextField = new TextField(); legende.x = 210; legende.y = 300; // redimensionnement automatique legende.autoSize = TextFieldAutoSize.LEFT; // formatage du texte var formatage:TextFormat = new TextFormat(); formatage.font = "Arial"; formatage.size = 11; // affectation du formatage legende.defaultTextFormat = formatage Chapitre 12 ? Programmation Bitmap ? version 0.1.2 77 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org legende.text = "Couleur sélectionnée :"; addChild ( legende ); A la sélection d?une zone sur l?image bitmap nous obtenons un aperçu comme l?illustre la figure 12-41 : La figure 12-41. Sélection d?une couleur. La méthode getPixel32 nous permet de récupérer la couleur de chaque pixel survolé. Puis nous colorons l?image bitmap apercuCouleur afin d?obtenir un aperçu. La capture d?éléments vectoriels sous forme bitmap offre de nouvelles possibilités comme la compression d?images au format JPEG ou l?encodage au format PNG, GIF ou autres. Rendez-vous au chapitre 20 intitulé ByteArray pour plus de détails concernant l?encodage d?images sous différents formats. A retenir Chapitre 12 ? Programmation Bitmap ? version 0.1.2 78 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org ? La méthode draw permet de rendre sous forme bitmap un élément vectoriel. ? Afin d?appliquer des transformations au sein du bitmap rendu, nous utilisons une matrice de transformation. Optimiser les performances Nous allons utiliser la classe BitmapData afin d?optimiser les performances d?une animation traditionnelle. En créant une classe AnimationBitmap nous allons capturer chaque état de l?animation vectorielle sous forme d?image bitmap, puis simuler l?animation en les affichant successivement. Cela va nous permettre d?améliorer les performances de rendu et réduire l?occupation processeur. Dans un nouveau document Flash CS3 nous utilisons un personnage animé, la figure 12-42 illustre l?animation : La figure 12-42. Animation du personnage. Par le panneau Liaison nous lions le symbole animé à une classe Garcon puis nous l?instancions avec le code suivant : Chapitre 12 ? Programmation Bitmap ? version 0.1.2 79 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org // instancie l'animation var monPersonnage:Garcon = new Garcon(); A coté du document flash en cours nous créons un répertoire bitmap puis nous plaçons à l?intérieur une classe nommée AnimationBitmap. Celle-ci contient le code suivant : package bitmap { import flash.display.Bitmap; import flash.display.BitmapData; import flash.display.MovieClip; import flash.events.TimerEvent; import flash.geom.Matrix; import flash.utils.Timer; public class AnimationBitmap extends Bitmap { // tableau stockant chaque étape de l'animation sous forme bitmap private var tableauBitmaps:Array; // sauve une référence vers l'objet vectoriel à animer private var animationCible:MovieClip; // création d'un minuteur pour gérér l'animation private var minuteur:Timer; // variable d'incrémentation private var i:int; public function AnimationBitmap ( pCible:MovieClip, pVitesse:int=100 ) { i = 0; animationCible = pCible; tableauBitmaps = new Array(); minuteur = new Timer ( pVitesse, 0 ); minuteur.addEventListener ( TimerEvent.TIMER, anime ); genereEtapes (); } private function genereEtapes ():void { // récupère le nombre d?image totale var lng:int = animationCible.totalFrames; var etapeAnimation:BitmapData; for ( var i:int = 0; i< lng; i++ ) { // parcours l'animation animationCible.gotoAndStop(i); // crée un bitmap pour chaque étape Chapitre 12 ? Programmation Bitmap ? version 0.1.2 80 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org etapeAnimation = new BitmapData ( animationCible.width + 30, animationCible.height, true, 0 ); // rend l'image sous forme bitmap etapeAnimation.draw ( animationCible ); tableauBitmaps.push ( etapeAnimation ); } minuteur.start(); } private function anime ( pEvt:TimerEvent ):void { // met à jour l'affichage, l'animation se joue en boucle bitmapData = tableauBitmaps[i++%tableauBitmaps.length]; } } } Puis nous passons au constructeur de la classe AnimationBitmap l?objet vectoriel à animer sous forme bitmap puis sa vitesse de lecture : // instancie l'animation var monPersonnage:Garcon = new Garcon(); // crée l'animation var animPersonnage:AnimationBitmap = new AnimationBitmap ( monPersonnage, 30 ); // positionnement animPersonnage.x = 20; animPersonnage.y = 100; // ajout à la liste d'affichage addChild ( animPersonnage ); Si nous zoomons sur l?animation générée, nous pouvons apercevoir qu?il s?agit d?une succession d?image bitmaps, le rendu est pixelisé : Chapitre 12 ? Programmation Bitmap ? version 0.1.2 81 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org La figure 12-43. Animation bitmap. Il est possible de choisir la vitesse de chaque animation. Dans le code suivant nous créons trois personnages. Chacun d?entre eux possède sa propre vitesse : // instancie l'animation var monPersonnage:Garcon = new Garcon(); // crée l'animation var animPersonnage:AnimationBitmap = new AnimationBitmap ( monPersonnage, 30 ); // positionnement animPersonnage.x = 20; animPersonnage.y = 100; // ajout à la liste d'affichage addChild ( animPersonnage ); var deuxiemeAnimPersonnage:AnimationBitmap = new AnimationBitmap ( monPersonnage, 90 ); deuxiemeAnimPersonnage.x = 190; deuxiemeAnimPersonnage.y += 100; addChild ( deuxiemeAnimPersonnage ); var troisiemeAnimPersonnage:AnimationBitmap = new AnimationBitmap ( monPersonnage, 150 ); troisiemeAnimPersonnage.x = 370; troisiemeAnimPersonnage.y = 100; addChild ( troisiemeAnimPersonnage ); Chapitre 12 ? Programmation Bitmap ? version 0.1.2 82 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org La figure 12-44 illustre les trois animations produites : La figure 12-43. Animation bitmap. En utilisant des images bitmaps pour simuler l?animation nous avons amélioré la fluidité de l?animation et réduit l?occupation processeur. Une dernière astuce va nous permettre d?optimiser la vitesse de rendu des animations. Lorsque nous générons les différentes étapes de l?animation sous forme bitmap nous passons la scène en qualité optimale. Puis, une fois les images bitmaps rendues nous passons la scène en qualité réduite. Les bitmaps affichés restent ainsi lissées tout en ayant passé l?animation en qualité réduite : // passe la qualité du rendu en optimale stage.quality = StageQuality.HIGH; // instancie l'animation var monPersonnage:Garcon = new Garcon(); // crée l'animation var animPersonnage:AnimationBitmap = new AnimationBitmap ( monPersonnage, 30 ); // positionnement animPersonnage.x = 20; animPersonnage.y = 100; // ajout à la liste d'affichage addChild ( animPersonnage ); Chapitre 12 ? Programmation Bitmap ? version 0.1.2 83 / 83 Thibault Imbert pratiqueactionscript3.bytearray.org var deuxiemeAnimPersonnage:AnimationBitmap = new AnimationBitmap ( monPersonnage, 90 ); deuxiemeAnimPersonnage.x = 190; deuxiemeAnimPersonnage.y += 100; addChild ( deuxiemeAnimPersonnage ); var troisiemeAnimPersonnage:AnimationBitmap = new AnimationBitmap ( monPersonnage, 150 ); troisiemeAnimPersonnage.x = 370; troisiemeAnimPersonnage.y = 100; addChild ( troisiemeAnimPersonnage ); // une fois l'animation bitmap générée, nous repassons en qualité réduite stage.quality = StageQuality.LOW; Nous bénéficions ainsi des performances d?une animation lue en qualité réduite sans dégrader la qualité de l?animation. Nous verrons lors du chapitre 16 intitulé Gestion du texte d?autres techniques d?optimisations du rendu. Peut être avez-vous pensé activer la mise en cache des bitmaps sur chaque personnage afin d?optimiser le rendu ? Souvenez-vous, lorsque la tête de lecture d?un objet graphique est en mouvement et que la mise en cache des bitmaps est activée, pour chaque nouvelle image le lecteur met à jour le bitmap créé en mémoire et ralentit les performances de l?animation. L?intérêt majeur de la classe AnimationBitmap repose sur la création d?images bitmaps indépendantes qui ne sont pas mises à jour quelles que soient les transformations appliquées à l?animation générée. A retenir ? L?utilisation de filtres sur une instance de BitmapData ne crée aucune image bitmap supplémentaire en mémoire. Au cours du prochain chapitre nous découvrirons comment charger des données non graphiques dans nos applications. Chapitre 13 ? Chargement de contenu ? version 0.1.2 1 / 84 Thibault Imbert pratiqueactionscript3.bytearray.org 13 Chargement du contenu LA CLASSE LOADER............................................................................................ 1 CHARGER UN ÉLÉMENT EXTERNE............................................................................ 2 LA CLASSE LOADERINFO.................................................................................. 5 INTERAGIR AVEC LE CONTENU ................................................................... 18 CREER UNE GALERIE....................................................................................... 26 LA COMPOSITION .................................................................................................. 29 REDIMENSIONNEMENT AUTOMATIQUE.................................................................. 34 GESTION DU LISSAGE ............................................................................................ 44 PRECHARGER LE CONTENU .......................................................................... 48 INTERROMPRE LE CHARGEMENT............................................................... 57 COMMUNIQUER ENTRE DEUX ANIMATIONS ........................................... 58 MODELE DE SECURITE DU LECTEUR FLASH........................................... 61 PROGRAMMATION CROISÉE................................................................................... 62 UTILISER UN FICHIER DE RÉGULATION .................................................................. 63 CONTEXTE DE CHARGEMENT ...................................................................... 65 CONTOURNER LES RESTRICTIONS DE SECURITE.................................. 66 BIBLIOTHEQUE PARTAGEE ........................................................................... 72 DESACTIVER UNE ANIMATION CHARGEE................................................ 79 COMMUNICATION AVM1 ET AVM2.............................................................. 81 La classe Loader ActionScript 3 élimine les nombreuses fonctions et méthodes existantes dans les précédentes versions d?ActionScript telles loadMovie ou loadMovieNum et étend le concept de la classe MovieClipLoader introduite avec Flash MX 2004. Chapitre 13 ? Chargement de contenu ? version 0.1.2 2 / 84 Thibault Imbert pratiqueactionscript3.bytearray.org Afin de charger différents types de contenus tels des images ou des animations nous utilisons la classe flash.display.Loader. Celle- ci hérite de la classe DisplayObjectContainer et peut donc être ajoutée à la liste d?affichage. D?autres classes telles flash.net.URLLoader ou flash.net.URLStream peuvent être utilisées afin de charger tout type de contenu, mais leur utilisation s?avère plus complexe. Nous reviendrons en détail sur ces classes au cours du chapitre 14 intitulé Charger et envoyer des données. Nous allons commencer notre apprentissage en chargeant différents types de contenu, puis nous continuerons notre exploration de la classe Loader en créant une galerie photo. A retenir ? La classe Loader permet le chargement de contenu graphique seulement. ? Celle-ci hérite de la classe DisplayObjectContainer et peut donc être ajoutée à la liste d?affichage. ? Il faut considérer la classe Loader comme un objet d?affichage. Charger un élément externe La classe Loader permet de charger des images au format PNG, JPEG, JPEG progressif et GIF non animé. Des animations SWF peuvent aussi être chargées, nous verrons comment communiquer avec celles-ci en fin de chapitre. Afin de charger un de ces fichiers, nous instancions tout d?abord la classe Loader : var chargeur:Loader = new Loader(); Puis nous appelons la méthode load dont voici la signature : public function load(request:URLRequest, context:LoaderContext = null):void Celle-ci requiert deux paramètres dont voici le détail : ? request : un objet URLRequest contenant l?adresse de l?élément à charger. ? context : un objet LoaderContext définissant le contexte de l?élément chargé. La classe URLRequest est utilisée pour toute connexion externe HTTP liée aux classes URLLoader, URLStream, Loader et FileReference. Chapitre 13 ? Chargement de contenu ? version 0.1.2 3 / 84 Thibault Imbert pratiqueactionscript3.bytearray.org Dans le code suivant, nous enveloppons l?URL à atteindre dans un objet URLRequest : // création du chargeur var chargeur:Loader = new Loader(); // url à atteindre var maRequete:URLRequest = new URLRequest ("photo.jpg"); // chargement du contenu chargeur.load ( maRequete ); L?utilisation d?un objet URLRequest diffère des précédentes versions d?ActionScript où une simple chaîne de caractères était passée aux différentes fonctions liées au chargement externe. Certaines classes ActionScript 3 telles Socket, URLStream, NetConnection et NetStream font néanmoins exception et ne nécessitent pas d?objet URLRequest. Nous reviendrons en détail sur la classe URLRequest au cours du chapitre 14 intitulé Charger et envoyer des données. Nous ne nous intéressons pas pour le moment au paramètre context qui n?est pas utilisé par défaut. Nous verrons plus loin dans ce chapitre l?intérêt de ce dernier. Afin de rendre graphiquement le contenu chargé nous devons ajouter l?objet Loader à la liste d?affichage : // création du chargeur var chargeur:Loader = new Loader(); // url à atteindre var maRequete:URLRequest = new URLRequest ("photo.jpg"); // chargement du contenu chargeur.load( maRequete ); // ajout à la liste d'affichage addChild ( chargeur ); L?image est alors affichée : Chapitre 13 ? Chargement de contenu ? version 0.1.2 4 / 84 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 13-1. Image chargée dynamiquement. Il est important de signaler qu?une instance de la classe Loader ne peut contenir qu?un seul enfant. Ainsi, lorsque la méthode load est exécutée, le contenu précédent est automatiquement remplacé. Ainsi, les images bitmap sont automatiquement supprimées de la mémoire, lorsque celles-ci sont déchargées. Ce n?est pas le cas des animations dont nous devons gérer la désactivation complète. Nous reviendrons sur cette contrainte en fin de chapitre. Comme depuis toujours, le chargement des données externes en ActionScript est asynchrone, cela signifie que l?instruction load n?est pas bloquante. Le code continue d?être exécuté une fois le chargement démarré. Nous serons avertis de la fin du chargement des données plus tard dans le temps grâce à un événement approprié. A retenir Chapitre 13 ? Chargement de contenu ? version 0.1.2 5 / 84 Thibault Imbert pratiqueactionscript3.bytearray.org ? La méthode load permet de charger le contenu externe. ? Afin de voir le contenu chargé, l?objet Loader doit être ajouté à la liste d?affichage. La classe LoaderInfo Afin de pouvoir accéder au contenu chargé, ou d?être averti des différentes phases du chargement nous utilisons les événements diffusés par la classe flash.display.LoaderInfo. Celle-ci contient des informations relatives au contenu chargé de type bitmap ou SWF. Il existe deux moyens d?accéder à un objet LoaderInfo : ? Par la propriété contentLoaderInfo de l?objet Loader. ? Par la propriété loaderInfo de n?importe quel DisplayObject. Lorsque nous accédons à la propriété contentLoaderInfo de la classe Loader nous récupérons une référence à l?objet LoaderInfo gérant le chargement de l?élément. Si nous tentons d?écouter les différents événements liés au chargement directement auprès de l?objet Loader, aucun événement n?est diffusé : // création du chargeur var chargeur:Loader = new Loader(); // tentative d'écoute de l'événement Event.COMPLETE chargeur.addEventListener ( Event.COMPLETE, contenuCharge ); // url à atteindre var maRequete:URLRequest = new URLRequest ("photo.jpg"); // chargement du contenu chargeur.load( maRequete ); // ajout à la liste d'affichage addChild ( chargeur ); function contenuCharge ( pEvt:Event ):void { trace( pEvt ); } Ainsi, l?écoute des différents événements propres au chargement, se fait auprès de l?objet LoaderInfo accessible par la propriété contentLoaderInfo de l?objet Loader : // création du chargeur Chapitre 13 ? Chargement de contenu ? version 0.1.2 6 / 84 Thibault Imbert pratiqueactionscript3.bytearray.org var chargeur:Loader = new Loader(); // écoute de l'événement Event.COMPLETE chargeur.contentLoaderInfo.addEventListener ( Event.COMPLETE, contenuCharge ); // url à atteindre var maRequete:URLRequest = new URLRequest ("photo.jpg"); // chargement du contenu chargeur.load( maRequete ); // ajout à la liste d'affichage addChild ( chargeur ); function contenuCharge ( pEvt:Event ):void { // affiche : [Event type="complete" bubbles=false cancelable=false eventPhase=2] trace( pEvt ); // affiche : [object LoaderInfo] trace( pEvt.target ); } De nombreux événements sont diffusés par la classe LoaderInfo, voici le détail de chacun d?entre eux : ? Event.OPEN : diffusé lorsque le lecteur commence à charger le contenu. ? ProgressEvent.PROGRESS : diffusé lorsque le chargement est en cours. Celui-ci renseigne sur le nombre d?octets chargés et totaux. Sa fréquence de diffusion est liée à la vitesse de téléchargement de l?élément. ? Event.INIT : diffusé lorsqu?il est possible d?accéder au code d?un SWF chargé. Cet événement n?est pas utilisé dans le cas de chargement d?image bitmap. ? Event.COMPLETE : diffusé lorsque le chargement est terminé. ? Event.UNLOAD : diffusé lorsque la méthode unload est appelée ou qu?un nouvel élément va être chargé pour remplacer le précédent. ? IOErrorEvent.IO_ERROR : diffusé lorsque le chargement échoue. ? HTTPStatusEvent.HTTP_STATUS : indique le code d?état de la requête HTTP. Afin de gérer avec finesse le chargement nous pouvons écouter chacun des événements : // création du chargeur var chargeur:Loader = new Loader(); // référence à l'objet LoaderInfo var cli:LoaderInfo = chargeur.contentLoaderInfo; Chapitre 13 ? Chargement de contenu ? version 0.1.2 7 / 84 Thibault Imbert pratiqueactionscript3.bytearray.org // écoute des événements liés au chargement cli.addEventListener ( Event.OPEN, debutChargement ); cli.addEventListener ( Event.INIT, initialisation ); cli.addEventListener ( ProgressEvent.PROGRESS, chargement ); cli.addEventListener ( Event.COMPLETE, chargementTermine ); cli.addEventListener ( IOErrorEvent.IO_ERROR, echecChargement ); cli.addEventListener ( HTTPStatusEvent.HTTP_STATUS, echecHTTP ); cli.addEventListener ( Event.UNLOAD, suppressionContenu ); // url à atteindre var maRequete:URLRequest = new URLRequest ("photo.jpg"); // chargement du contenu chargeur.load( maRequete ); // ajout à la liste d'affichage addChild ( chargeur ); function debutChargement ( pEvt:Event ):void { // affiche : [Event type="open" bubbles=false cancelable=false eventPhase=2] trace( pEvt ); } function initialisation ( pEvt:Event ):void { // affiche : [Event type="init" bubbles=false cancelable=false eventPhase=2] trace( pEvt ); } function chargement ( pEvt:ProgressEvent ):void { // affiche : [ProgressEvent type="progress" bubbles=false cancelable=false eventPhase=2 bytesLoaded=0 bytesTotal=5696] trace( pEvt ); } function chargementTermine ( pEvt:Event ):void { // affiche : [Event type="complete" bubbles=false cancelable=false eventPhase=2] trace( pEvt ); } function echecChargement ( pEvt:IOErrorEvent ):void { Chapitre 13 ? Chargement de contenu ? version 0.1.2 8 / 84 Thibault Imbert pratiqueactionscript3.bytearray.org trace( pEvt ); } function echecHTTP ( pEvt:HTTPStatusEvent ):void { trace( pEvt ); } function suppressionContenu ( pEvt:Event ):void { // affiche : [Event type="unload" bubbles=false cancelable=false eventPhase=2] trace( pEvt ); } Si nous tentons d?accéder au contenu chargé avant l?événement Event.COMPLETE l?accès à la propriété content lève une erreur à l?exécution. Dans le code suivant nous tentons d?accéder au contenu durant l?événement ProgressEvent.PROGRESS : function chargement ( pEvt:ProgressEvent ):void { trace( pEvt.target.content ); } L?erreur suivante est levée à l?exécution : Error: Error #2099: L'objet en cours de chargement n'est pas suffisamment chargé pour fournir ces informations. Une fois le contenu totalement chargé, nous y accédons grâce à la propriété content de la classe LoaderInfo : function chargementTermine ( pEvt:Event ):void { // affiche : [object Bitmap] trace( pEvt.target.content ); } Mais aussi par la propriété content de l?objet Loader : // affiche : [object Bitmap] trace( chargeur.content ) ; A l?aide des différentes propriétés de la classe LoaderInfo nous obtenons différents types d?informations : Chapitre 13 ? Chargement de contenu ? version 0.1.2 9 / 84 Thibault Imbert pratiqueactionscript3.bytearray.org function chargementTermine ( pEvt:Event ):void { var objetLoaderInfo:LoaderInfo = LoaderInfo ( pEvt.target ); // accès aux informations liées au contenu chargé var contenu:Bitmap = Bitmap ( objetLoaderInfo.content ); var largeur:Number = objetLoaderInfo.width; var hauteur:Number = objetLoaderInfo.height; var urlContenu:String = objetLoaderInfo.url; var type:String = objetLoaderInfo.contentType; // affiche : [object Bitmap] trace ( contenu ); // affiche : 167 65 trace ( largeur, hauteur ); // affiche : file:///K|/Work/Bytearray.org/O%27Reilly/Pratique%20d%27ActionScript/Chapitre %2013/flas/photo.jpg trace ( urlContenu ); // affiche : image/jpeg trace ( type ); } Lors du chapitre 12 intitulé Programmation Bitmap, nous avons vu que toutes les données bitmap étaient assimilées à des objets BitmapData. Dans le cas d?une image chargée dynamiquement, les données bitmap sont enveloppées au préalable par un objet flash.display.Bitmap afin de pouvoir être rendues. Dans le code suivant, nous récupérons les dimensions de l?image chargée ainsi que les données bitmap la composant : function chargementTermine ( pEvt:Event ):void { // référence le contenu var contenu:DisplayObject = pEvt.target.content; // si le contenu chargé est une image if ( contenu is Bitmap ) { // transtype en tant qu'image var image:Bitmap = Bitmap ( contenu ); // affiche : 167 65 trace( image.width, image.height ); //affiche : [object BitmapData] Chapitre 13 ? Chargement de contenu ? version 0.1.2 10 / 84 Thibault Imbert pratiqueactionscript3.bytearray.org trace( image.bitmapData ); } } Nous pouvons effacer une partie de l?image chargée, en peignant directement dessus à l?aide de la méthode fillRect de la classe BitmapData : function chargementTermine ( pEvt:Event ):void { // référence le contenu var contenu:DisplayObject = pEvt.target.content; // si le contenu chargé est une image if ( contenu is Bitmap ) { // transtype en tant qu'image var image:Bitmap = Bitmap ( contenu ); var donneesBitmap:BitmapData = image.bitmapData; donneesBitmap.fillRect( new Rectangle ( 15, 15, 135, 30 ), 0xC8BCA4 ); } } La figure 13-2 illustre le résultat : Chapitre 13 ? Chargement de contenu ? version 0.1.2 11 / 84 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 13-2. Modification des données bitmap. La partie centrale de l?image chargée est peinte de pixels beiges. Souvenez-vous que les événements sont diffusés depuis l?objet LoaderInfo. Ainsi la propriété target ne référence pas l?objet Loader mais l?objet LoaderInfo associé à l?élément chargé. Afin d?accéder à l?objet Loader associé à l?objet LoaderInfo, nous utilisons sa propriété loader : function chargementTermine ( pEvt:Event ):void { var objetLoaderInfo:LoaderInfo = LoaderInfo ( pEvt.target ); // affiche : true trace( objetLoaderInfo.loader == chargeurImage ); } De la même manière, la propriété contentLoaderInfo de l?objet Loader pointe vers le même objet LoaderInfo que celui référencé par la propriété loaderInfo de l?élément chargé : function chargementTermine ( pEvt:Event ):void { var objetLoaderInfo:LoaderInfo = LoaderInfo ( pEvt.target ); var objetLoader:Loader = pEvt.target.loader; // affiche : true trace( objetLoader.contentLoaderInfo == objetLoaderInfo.content.loaderInfo ); } L?objet LoaderInfo est donc partagé entre deux environnements : L?animation chargeant le contenu et le contenu lui-même. Il est important de noter que bien qu?il s?agisse d?une sous-classe de DisplayObjectContainer, la classe Loader ne possède pas toutes les capacités propres à ce type. Certaines propriétés telles numChildren peuvent être utilisées : function chargementTermine ( pEvt:Event ):void { var objetLoader:Loader = pEvt.target.loader; // affiche : true Chapitre 13 ? Chargement de contenu ? version 0.1.2 12 / 84 Thibault Imbert pratiqueactionscript3.bytearray.org trace( objetLoader.numChildren ); } Il est en revanche impossible d?appeler les différentes méthodes de manipulation d?objets enfants sur celle-ci. Dans le code suivant nous tentons d?appeler la méthode removeChildAt sur l?objet chargeurImage : function chargementTermine ( pEvt:Event ):void { var objetLoader:Loader = pEvt.target.loader; // tentative de suppression du premier enfant de l'objet Loader objetLoader.removeChildAt(0); } L?erreur à l?exécution suivante est levée : // affiche : Error: Error #2069: La classe Loader ne met pas en oeuvre cette méthode. Afin de supprimer l?image chargée, nous devons appeler la méthode unload de la classe Loader. Dans le code suivant, nous supprimons l?image chargée aussitôt le chargement terminé : function chargementTermine ( pEvt:Event ):void { // référence le contenu var contenu:DisplayObject = pEvt.target.content; // si le contenu chargé est une image if ( contenu is Bitmap ) { // transtype en tant qu'image var image:Bitmap = Bitmap ( contenu ); var donneesBitmap:BitmapData = image.bitmapData; donneesBitmap.fillRect( new Rectangle ( 15, 15, 135, 30 ), 0x009900 ); pEvt.target.loader.unload(); } } Aussitôt le contenu supprimé, l?événement Event.UNLOAD est diffusé. Chapitre 13 ? Chargement de contenu ? version 0.1.2 13 / 84 Thibault Imbert pratiqueactionscript3.bytearray.org En cas d?erreur de chargement, nous devons gérer et indiquer à l?utilisateur qu?une erreur s?est produite. Pour cela nous devons obligatoirement écouter l?événement IOErrorEvent.IO_ERROR. Ce dernier est diffusé lorsque le contenu n?a pu être chargé, pour des raisons d?accès ou de compatibilité. Si nous tentons de charger un contenu non géré et que l?événement IOErrorEvent.IO_ERROR n?est pas écouté, une erreur à l?exécution est levée. Attention, si le bouton retour du navigateur est cliqué pendant le chargement de contenu externe par le lecteur Flash. L?événement IOErrorEvent.IO_ERROR est diffusé, alors que le contenu est pourtant compatible ou disponible. Il convient donc de toujours écouter cet événement. Dans le code suivant, un fichier non compatible est chargé, l?événement IOErrorEvent.IO_ERROR est diffusé : // création du chargeur var chargeur:Loader = new Loader(); // référence à l'objet LoaderInfo var cli:LoaderInfo = chargeur.contentLoaderInfo; // écoute des événements liés au chargement cli.addEventListener ( Event.OPEN, debutChargement ); cli.addEventListener ( Event.INIT, initialisation ); cli.addEventListener ( ProgressEvent.PROGRESS, chargement ); cli.addEventListener ( Event.COMPLETE, chargementTermine ); cli.addEventListener ( IOErrorEvent.IO_ERROR, echecChargement ); cli.addEventListener ( HTTPStatusEvent.HTTP_STATUS, echecHTTP ); cli.addEventListener ( Event.UNLOAD, suppressionContenu ); // tentative de chargement d'un document PDF // entraîne la diffusion de l'événement IOErrorEvent.IO_ERROR var maRequete:URLRequest = new URLRequest ("document.pdf"); // chargement du contenu chargeur.load( maRequete ); // ajout à la liste d'affichage addChild ( chargeur ); function debutChargement ( pEvt:Event ):void { trace( pEvt ); } function initialisation ( pEvt:Event ):void { Chapitre 13 ? Chargement de contenu ? version 0.1.2 14 / 84 Thibault Imbert pratiqueactionscript3.bytearray.org trace( pEvt ); } function chargement ( pEvt:ProgressEvent ):void { trace( pEvt ); } function chargementTermine ( pEvt:Event ):void { trace( pEvt ); } function suppressionContenu ( pEvt:Event ):void { trace( pEvt ); } function echecChargement ( pEvt:Event ):void { // affiche : [IOErrorEvent type="ioError" bubbles=false cancelable=false eventPhase=2 text="Error #2124: Le type du fichier chargé est inconnu. URL: file:///D|/Work/Bytearray.org/O%27Reilly/Pratique%20d%27ActionScript/Chapitre %2013/flas/document.pdf"] trace ( pEvt ); } Lorsqu?une animation est chargée par un objet Loader, l?objet LoaderInfo active de nouvelles propriétés liées au SWF. Voici le détail de chacune d?entre elles : ? LoaderInfo.frameRate : Indique la cadence du SWF chargé. ? LoaderInfo.applicationDomain : retourne une référence à l?objet ApplicationDomain contenant les définitions de classe du SWF chargé. ? LoaderInfo.actionScriptVersion : indique la version d?ActionScript du SWF chargé. ? LoaderInfo.parameters : objet contenant les FlashVars associés au SWF chargé. ? LoaderInfo.swfVersion : indique la version du SWF chargé. Chapitre 13 ? Chargement de contenu ? version 0.1.2 15 / 84 Thibault Imbert pratiqueactionscript3.bytearray.org Toute tentative d?accès, à l?une de ces propriétés lorsqu?une image est chargée, se traduit par un échec. Dans le code suivant, une image est chargée, nous tentons alors d?accéder à la propriété frameRate : function chargementTermine ( pEvt:Event ):void { // tentative d'accès à la cadence de l'élément chargé trace( pEvt.target.frameRate ); } L?erreur à l?exécution suivante est levée : Error: Error #2098: L'objet en cours de chargement n'est pas un fichier .swf, vous ne pouvez donc pas en extraire des propriétés SWF. Afin d?utiliser ces propriétés nous chargeons une animation : // url à atteindre var maRequete:URLRequest = new URLRequest ("animation.swf"); Puis nous accédons au sein de la fonction chargementTermine aux différentes informations liées au SWF : function chargementTermine ( pEvt:Event ):void { var objetLoaderInfo:LoaderInfo = LoaderInfo ( pEvt.target ); // affiche : 30 trace( objetLoaderInfo.frameRate ); // affiche : [object ApplicationDomain] trace( objetLoaderInfo.applicationDomain ); // affiche : 3 trace( objetLoaderInfo.actionScriptVersion ); // affiche : [object Object] trace( objetLoaderInfo.parameters ); // affiche : 9 trace( objetLoaderInfo.swfVersion ); } Attention, il convient de toujours utiliser les propriétés width et height de l?objet LoaderInfo afin de récupérer la largeur et hauteur du contenu chargé. Dans le cas de chargement d?animations, la propriété content référence le scénario principal du SWF. Ainsi, l?utilisation des propriétés width et height renvoient la taille du contenu de l?animation, et non les dimensions du SWF : function chargementTermine ( pEvt:Event ):void Chapitre 13 ? Chargement de contenu ? version 0.1.2 16 / 84 Thibault Imbert pratiqueactionscript3.bytearray.org { var objetLoaderInfo:LoaderInfo = LoaderInfo ( pEvt.target ); var contenu:DisplayObject = objetLoaderInfo.content; // accède aux dimensions du contenu de l'animation // affiche : 187.5 187.5 trace( contenu.width, contenu.height ); // accède aux dimensions du SWF // affiche : 550 400 trace( objetLoaderInfo.width, objetLoaderInfo.height ); } Nous pouvons tester lors de la fin du chargement le type de contenu chargé à l?aide du mot clé is afin de déterminer si le contenu chargé est une animation ou une image En testant le type de la propriété content nous pouvons déterminer le type de contenu chargé : function chargementTermine ( pEvt:Event ):void { var objetLoaderInfo:LoaderInfo = LoaderInfo ( pEvt.target ); var contenu:DisplayObject = objetLoaderInfo.content; if ( contenu is Bitmap ) { trace("Une image a été chargée !"); } else { trace("Une animation a été chargée !"); } } Nous venons de traiter l?utilisation de la classe LoaderInfo à travers la propriété contentLoaderInfo de l?objet Loader. A l?inverse lorsque nous accédons à la propriété loaderInfo d?un DisplayObject nous accédons aux informations du SWF contenant le DisplayObject. A l?instar des propriétés stage et root, la propriété loaderInfo renvoie null, tant que l?objet graphique n?est pas présent au sein de la liste d?affichage : Chapitre 13 ? Chargement de contenu ? version 0.1.2 17 / 84 Thibault Imbert pratiqueactionscript3.bytearray.org // création d'un objet graphique var monSprite:Sprite = new Sprite(); // affiche : null trace( monSprite.loaderInfo ); // ajout à la liste d'affichage addChild ( monSprite ); // affiche : [object LoaderInfo] trace( monSprite.loaderInfo ); // affiche : true trace( loaderInfo == monSprite.loaderInfo ); Afin d?accéder aux différentes propriétés de la classe LoaderInfo. Nous devons attendre la fin du chargement du SWF en cours. Si nous tentons d?accéder aux propriétés de la classe LoaderInfo avant l?événement Event.INIT : trace( monSprite.loaderInfo.width ); L?erreur suivante est levée à l?exécution : Error: Error #2099: L'objet en cours de chargement n'est pas suffisamment chargé pour fournir ces informations. A l?aide de l?événement Event.INIT ou Event.COMPLETE nous pouvons accéder correctement aux informations du SWF en cours : // création d'un objet graphique var monSprite:Sprite = new Sprite(); // ajout à la liste d'affichage addChild ( monSprite ); // écoute de l'événement Event.COMPLETE auprès de // l'objet LoaderInfo du SWF en cours monSprite.loaderInfo.addEventListener ( Event.COMPLETE, chargementTermine ); function chargementTermine ( pEvt:Event ):void { var objetLoaderInfo:LoaderInfo = LoaderInfo ( pEvt.target ); // affiche : [object LoaderInfo] trace( objetLoaderInfo ); // affiche : 550 400 trace( objetLoaderInfo.width, objetLoaderInfo.height ); // affiche : application/x-shockwave-flash trace( objetLoaderInfo.contentType ); // affiche : 12 trace( objetLoaderInfo.frameRate ); // affiche : 577 577 trace ( objetLoaderInfo.bytesLoaded, objetLoaderInfo.bytesTotal ); Chapitre 13 ? Chargement de contenu ? version 0.1.2 18 / 84 Thibault Imbert pratiqueactionscript3.bytearray.org } Nous pouvons aussi référencer l?objet LoaderInfo par la propriété loaderInfo du scénario principal : // écoute de l'événement Event.COMPLETE auprès de // l'objet LoaderInfo du SWF en cours loaderInfo.addEventListener ( Event.COMPLETE, chargementTermine ); De cette manière, un SWF peut gérer son préchargement de manière autonome. Ainsi, chaque SWF possède un objet LoaderInfo associé regroupant les informations relatives à celui-ci. Nous retrouvons avec le code précédent, l?équivalent de l?événement onLoad existant en ActionScript 1 et 2. A retenir ? La classe LoaderInfo contient des informations relatives à un élément chargé de type bitmap ou SWF. ? La propriété contentLoaderInfo de l?objet Loader référence l?objet LoaderInfo associé à l?élément chargé. ? Tous les objets de type DisplayObject possèdent une propriété loaderInfo associé au SWF en cours. ? Lorsqu?une image est chargée, seules certaines propriétés de la classe LoaderInfo sont utilisables. ? Lorsqu?un SWF est chargé, la totalité des propriétés de la classe LoaderInfo sont utilisables. Interagir avec le contenu Il est généralement courant, de devoir interagir avec le contenu chargé. La classe Loader est un DisplayObjectContainer et hérite donc de la classe InteractiveObject facilitant l?interactivité souris et clavier. Il est cependant impossible d?activer la propriété buttonMode auprès de l?objet Loader, l?interactivité est donc réduite. Une première technique consiste à déplacer le contenu de l?objet Loader à l?aide de la méthode addChild. Dans le code suivant, nous accédons au contenu chargé puis nous le déplaçons au sein d?un autre conteneur : function chargementTermine ( pEvt:Event ):void { // référence le contenu var contenu:DisplayObject = pEvt.target.content; Chapitre 13 ? Chargement de contenu ? version 0.1.2 19 / 84 Thibault Imbert pratiqueactionscript3.bytearray.org // affiche : [object Loader] trace( contenu.parent ); // réattribution de conteneur, l'image quitte l'objet Loader addChild ( contenu ); // affiche : [object MainTimeline] trace( contenu.parent ); } Nous référençons le contenu à l?aide de la propriété content de l?objet LoaderInfo. Il convient de rester le plus générique possible, nous utilisons donc le type commun DisplayObject propre à tout type de contenu chargé, puis nous déplaçons le contenu chargé à l?aide de la méthode addChild. Souvenez-vous, lorsqu?un DisplayObject déjà présent dans la liste d?affichage est passé à la méthode addChild, l?objet sur lequel la méthode est appelée devient le nouveau conteneur. C?est le concept de réattribution de parent couvert au sein du chapitre 4 intitulé Liste d?affichage. Afin de dupliquer l?image chargée, nous n?avons pas à recharger l?image au sein d?un autre objet Loader. Nous copions les données bitmap puis les associons à un nouvel objet Bitmap : function chargementTermine ( pEvt:Event ):void { // référence le contenu var contenu:DisplayObject = pEvt.target.content; // si le contenu chargé est une image if ( contenu is Bitmap ) { // transtype en tant qu'image var image:Bitmap = Bitmap ( contenu ); var donneesBitmap:BitmapData = image.bitmapData; // création d'une copie des données bitmaps var copieDonneesBitmap:BitmapData = donneesBitmap.clone(); var copieImage:Bitmap = new Bitmap ( copieDonneesBitmap ); copieImage.x = image.width + 5; addChild ( image ); addChild ( copieImage ); Chapitre 13 ? Chargement de contenu ? version 0.1.2 20 / 84 Thibault Imbert pratiqueactionscript3.bytearray.org } } La figure 13-3 illustre le résultat : Figure 13-3. Copie de l?image chargée. Afin de rendre les images cliquables, nous les ajoutons au sein d?objets interactifs tels Sprite : function chargementTermine ( pEvt:Event ):void { // référence le contenu var contenu:DisplayObject = pEvt.target.content; // si le contenu chargé est une image if ( contenu is Bitmap ) { // transtype en tant qu'image var image:Bitmap = Bitmap ( contenu ); var premierConteneur:Sprite = new Sprite(); premierConteneur.buttonMode = true; premierConteneur.addChild( image ); var donneesBitmap:BitmapData = image.bitmapData; Chapitre 13 ? Chargement de contenu ? version 0.1.2 21 / 84 Thibault Imbert pratiqueactionscript3.bytearray.org // création d'une copie des données bitmaps var copieDonneesBitmap:BitmapData = donneesBitmap.clone(); var copieImage:Bitmap = new Bitmap ( copieDonneesBitmap ); var secondConteneur:Sprite = new Sprite(); secondConteneur.buttonMode = true; secondConteneur.addChild( copieImage ); secondConteneur.x = premierConteneur.width + 5; addChild ( premierConteneur ); addChild ( secondConteneur ); premierConteneur.addEventListener( MouseEvent.CLICK, clicImage ); secondConteneur.addEventListener( MouseEvent.CLICK, clicImage ); } } function clicImage ( pEvt:MouseEvent ):void { // affiche : [MouseEvent type="click" bubbles=true cancelable=false eventPhase=2 localX=48 localY=36 stageX=220 stageY=36 relatedObject=null ctrlKey=false altKey=false shiftKey=false delta=0] trace( pEvt ); } Les deux images bitmaps sont placées dans un conteneur Sprite spécifique, facilitant l?interactivité. Afin d?activer le comportement bouton, nous utilisons la propriété buttonMode. Souvenez-vous, afin de rendre plus souple notre code, nous capturons l?événement MouseEvent.CLICK auprès du parent des événements réactifs. Nous préférons donc le code suivant : // création d'un conteneur pour les images var conteneurImages:Sprite = new Sprite(); addChild ( conteneurImages ); // capture de l'événement MouseEvent.MOUSE_CLICK auprès du conteneur conteneurImages.addEventListener( MouseEvent.CLICK, clicImages, true ); function clicImages ( pEvt:MouseEvent ):void { // [MouseEvent type="click" bubbles=true cancelable=false eventPhase=1 localX=87 localY=4 stageX=259 stageY=4 relatedObject=null ctrlKey=false altKey=false shiftKey=false delta=0] trace( pEvt ); } Chapitre 13 ? Chargement de contenu ? version 0.1.2 22 / 84 Thibault Imbert pratiqueactionscript3.bytearray.org function chargementTermine ( pEvt:Event ):void { // référence le contenu var contenu:DisplayObject = pEvt.target.content; // si le contenu chargé est une image if ( contenu is Bitmap ) { // transtype en tant qu'image var image:Bitmap = Bitmap ( contenu ); var premierConteneur:Sprite = new Sprite(); premierConteneur.buttonMode = true; premierConteneur.addChild( image ); var donneesBitmap:BitmapData = image.bitmapData; // création d'une copie des données bitmaps var copieDonneesBitmap:BitmapData = donneesBitmap.clone(); var copieImage:Bitmap = new Bitmap ( copieDonneesBitmap ); var secondConteneur:Sprite = new Sprite(); secondConteneur.buttonMode = true; secondConteneur.addChild( copieImage ); secondConteneur.x = premierConteneur.width + 5; conteneurImages.addChild ( premierConteneur ); conteneurImages.addChild ( secondConteneur ); } } Grâce à la capture de l?événement MouseEvent.CLICK, lorsque de nouvelles images seront placées ou supprimées du conteneur conteneurImages, aucune nouvelle souscription ou désinscription d?écouteurs ne sera nécessaire. A chaque clic sur les images nous la supprimons de la liste d?affichage, pour cela nous modifions la fonction clicImages : function clicImages ( pEvt:MouseEvent ):void { pEvt.currentTarget.removeChild ( DisplayObject ( pEvt.target ) ); } Chapitre 13 ? Chargement de contenu ? version 0.1.2 23 / 84 Thibault Imbert pratiqueactionscript3.bytearray.org Lorsque l?image est cliquée, celle-ci est supprimée de la liste d?affichage. Une référence vers l?image d?origine demeure cependant au sein de l?objet Loader, il nous est donc impossible de libérer correctement les ressources. De plus, en déplaçant le contenu de l?objet Loader nous modifions son fonctionnement interne. Ainsi, lors du prochain appel de la méthode load, celui-ci tentera de supprimer son contenu afin d?accueillir le nouveau. Celui-ci ayant été déplacé, l?objet Loader tente alors de supprimer un objet qui n?est plus son enfant, ce qui provoque alors une erreur à l?exécution. Ce comportement est un bug du lecteur Flash 9.0.115 qui sera corrigé au sein du prochain lecteur Flash 10. Il est donc fortement déconseillé de déplacer le contenu de l?objet Loader sous peine de perturber le mécanisme interne de celui-ci. De manière générale nous préférons créer un objet Loader pour chaque contenu chargé. Nous pouvons donc établir le bilan suivant : // bonne pratique addChild ( chargeur ); // mauvaise pratique addChild ( chargeur.content ); Dans le code suivant, nous chargeons plusieurs images au sein de différents objets Loader : var conteneurImages:Sprite = new Sprite(); addChild ( conteneurImages ); // capture de l'événement MouseEvent.MOUSE_CLICK auprès du conteneur conteneurImages.addEventListener( MouseEvent.CLICK, clicImages, true ); var chargeurImage:Loader; var requete:URLRequest = new URLRequest(); var tableauImages:Array = new Array ("photo1.jpg", "photo2.jpg", "photo3.jpg", "photo4.jpg", "photo5.jpg", "photo3.jpg"); var lng:int = tableauImages.length; var colonnes:int = 4; for ( var i:int = 0; i< lng; i++ ) { // création du chargeur chargeurImage = new Loader(); Chapitre 13 ? Chargement de contenu ? version 0.1.2 24 / 84 Thibault Imbert pratiqueactionscript3.bytearray.org requete.url = tableauImages[i]; chargeurImage.load ( requete ); chargeurImage.x = Math.round (i % colonnes) * 120; chargeurImage.y = Math.floor (i / colonnes) * 80 conteneurImages.addChild ( chargeurImage ); } function clicImages ( pEvt:MouseEvent ):void { // [MouseEvent type="click" bubbles=true cancelable=false eventPhase=1 localX=10 localY=49 stageX=250 stageY=49 relatedObject=null ctrlKey=false altKey=false shiftKey=false delta=0] trace( pEvt ); } Afin de rendre les images cliquables, nous les imbriquons dans des conteneurs de type Sprite : var conteneurImages:Sprite = new Sprite(); addChild ( conteneurImages ); // capture de l'événement MouseEvent.MOUSE_CLICK auprès du conteneur conteneurImages.addEventListener( MouseEvent.CLICK, clicImages, true ); var chargeurImage:Loader; var conteneurLoader:Sprite; var requete:URLRequest = new URLRequest(); var tableauImages:Array = new Array ("photo1.jpg", "photo2.jpg", "photo3.jpg", "photo4.jpg", "photo5.jpg", "photo3.jpg"); var lng:int = tableauImages.length; var colonnes:int = 4; for (var i:int = 0; i< lng; i++ ) { // création du chargeur chargeurImage = new Loader(); conteneurLoader = new Sprite(); conteneurLoader.addChild( chargeurImage ); requete.url = tableauImages[i]; chargeurImage.load ( requete ); conteneurLoader.x = Math.round (i % colonnes) * 120; Chapitre 13 ? Chargement de contenu ? version 0.1.2 25 / 84 Thibault Imbert pratiqueactionscript3.bytearray.org conteneurLoader.y = Math.floor (i / colonnes) * 80 conteneurImages.addChild ( conteneurLoader ); } function clicImages ( pEvt:MouseEvent ):void { // [MouseEvent type="click" bubbles=true cancelable=false eventPhase=1 localX=10 localY=49 stageX=250 stageY=49 relatedObject=null ctrlKey=false altKey=false shiftKey=false delta=0] trace( pEvt ); } L?activation du mode bouton est rendue possible grâce à l?activation de la propriété buttonMode sur les instances de Sprite contenant les objets Loader : conteneurLoader.buttonMode = true; Puis nous modifions la fonction clicImages : function clicImages ( pEvt:MouseEvent ):void { DisplayObject( pEvt.target ).alpha = .5; } La figure 13-4 illustre le résultat : Chapitre 13 ? Chargement de contenu ? version 0.1.2 26 / 84 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 13-4. Mini galerie. A chaque image cliquée, celle-ci passe en opacité réduite afin de mémoriser visuellement l?action utilisateur. A retenir ? Le contenu chargé est accessible par la propriété content de l?objet LoaderInfo. ? Bien qu?étant une sous-classe de DisplayObjectContainer, la classe Loader ne dispose pas de toutes les capacités propres à ce type. ? Il est techniquement possible de déplacer le contenu chargé et de réattribuer son parent, mais il est fortement recommandé de conserver le contenu au sein de l?objet Loader. ? Afin d?afficher le curseur main au survol d?un objet Loader, nous devons au préalable l?imbriquer au sein d?un conteneur de type Sprite. Créer une galerie Nous allons mettre en pratique un ensemble de notions abordées depuis le début de l?ouvrage ainsi que celles abordées en début de ce chapitre. Nous allons créer une galerie photo et ainsi voir comment la concevoir de manière optimisée. Ouvrez un nouveau document Flash CS3, puis définissez une classe de document au sein d?un paquetage org.bytearray.document : package org.bytearray.document { import org.bytearray.abstrait.ApplicationDefaut; public class Document extends ApplicationDefaut { public function Document () { trace( this ); } } } Chapitre 13 ? Chargement de contenu ? version 0.1.2 27 / 84 Thibault Imbert pratiqueactionscript3.bytearray.org Cette classe étend la classe ApplicationDefaut créée au cours du chapitre 11 intitulé Classe du document, nous récupérons ainsi toutes les fonctionnalités définies par celle-ci. Une fois créée, nous définissons celle-ci comme classe du document grâce au panneau approprié de l?inspecteur de propriétés : Figure 13-5. Classe du document. Nous définissons la classe ChargeurMedia au sein du paquetage org.bytearray.chargement. Cette classe va nous permettre de gérer facilement le chargement d?images. Nous pourrions utiliser simplement la classe Loader mais nous avons besoin d?augmenter les ses fonctionnalités. La classe ChargeurMedia se chargera de toute la logique de chargement et de redimensionnement des médias chargés. Ainsi pour chaque projet nécessitant un chargement de contenu notre classe ChargeurMedia pourra être réutilisée. Voici le code de base de la classe ChargeurMedia : package org.bytearray.chargement { import flash.display.Loader; import flash.display.Sprite; import flash.display.LoaderInfo; import flash.events.Event; import flash.events.ProgressEvent; import flash.events.IOErrorEvent; public class ChargeurMedia extends Sprite { private var chargeur:Loader; private var cli:LoaderInfo; public function ChargeurMedia () { chargeur = new Loader(); addChild ( chargeur ); Chapitre 13 ? Chargement de contenu ? version 0.1.2 28 / 84 Thibault Imbert pratiqueactionscript3.bytearray.org cli = chargeur.contentLoaderInfo; cli.addEventListener ( Event.INIT, initChargement ); cli.addEventListener ( Event.OPEN, demarreChargement ); cli.addEventListener ( ProgressEvent.PROGRESS, chargement ); cli.addEventListener ( Event.COMPLETE, chargementTermine ); cli.addEventListener ( IOErrorEvent.IO_ERROR, echecChargement ); } private function demarreChargement ( pEvt:Event ):void { trace ("démarre chargement"); } private function initChargement ( pEvt:Event ):void { trace ("initialisation du chargement"); } private function chargement ( pEvt:Event ):void { trace ("chargement en cours"); } private function chargementTermine ( pEvt:Event ):void { trace ("chargement terminé"); } private function echecChargement ( pEvt:Event ):void { trace ("erreur de chargement"); } } } Une fois la classe ChargeurMedia définie, nous pouvons l?instancier au sein de la classe Document : package org.bytearray.document { import org.bytearray.abstrait.ApplicationDefaut; Chapitre 13 ? Chargement de contenu ? version 0.1.2 29 / 84 Thibault Imbert pratiqueactionscript3.bytearray.org import org.bytearray.chargement.ChargeurMedia; public class Document extends ApplicationDefaut { // visionneuse private var visionneuse:ChargeurMedia; public function Document () { // création de l'objet Loader visionneuse = new ChargeurMedia (); addChild ( visionneuse ); } } } Certains d?entre vous seront peut-être étonnés de voir que nous n?héritons pas de la classe Loader. Pourtant, toutes les raisons paraissent ici réunies pour utiliser l?héritage. Notre classe ChargeurMedia doit augmenter les capacités de la classe Loader, l?héritage semble donc être la meilleure solution. Nous allons préférer ici une deuxième approche déjà utilisée au cours des chapitres précédents. La classe ChargeurMedia ne sera pas un objet Loader mais possèdera un objet Loader. Vous souvenez-vous de cette opposition ? La composition Nous allons préférer ici l?utilisation de la composition à l?héritage. Nous avons vu au cours du chapitre 8 intitulé Programmation orientée objet les avantages qu?offre la composition en matière d?évolutivité et de modification du code. Lorsque l?application est exécutée, le constructeur de la classe Document instancie un objet ChargeurMedia. Très généralement, les données à charger proviennent d?une base de données ou d?un fichier XML. Nous allons réaliser une première version à l?aide d?un tableau contenant les URL des images à charger. Nous créons ensuite un tableau contenant l?adresse de chaque image : package org.bytearray.document { Chapitre 13 ? Chargement de contenu ? version 0.1.2 30 / 84 Thibault Imbert pratiqueactionscript3.bytearray.org import org.bytearray.abstrait.ApplicationDefaut; import org.bytearray.chargement.ChargeurMedia; public class Document extends ApplicationDefaut { // visionneuse private var visionneuse:ChargeurMedia; // données private var tableauImages:Array; public function Document () { // tableau contenant les url des images tableauImages = new Array ( "imgs/photo1.jpg", "imgs/photo2.jpg", "imgs/photo3.jpg", "imgs/photo4.jpg", "imgs/photo5.jpg" ); // création de l'objet Loader visionneuse = new ChargeurMedia (); addChild ( visionneuse ); } } } Un répertoire imgs est créé contenant différentes images portant le nom de celles définies dans le tableau tableauImages. Pour le moment, la classe ChargeurMedia n?est pas utilisable, nous devons ajouter une méthode permettant de charger le media spécifié. Nous importons les classes URLRequest et LoaderContext dans la définition de la classe : import flash.display.Loader; import flash.display.Sprite; import flash.events.Event; import flash.events.ProgressEvent; import flash.events.IOErrorEvent; import flash.net.URLRequest; import flash.system.LoaderContext; Puis, nous ajoutons une méthode load. Celle-ci appelle en interne la méthode load de l?objet Loader : public function load ( pURL:URLRequest, pContext:LoaderContext=null ):void { chargeur.load ( pURL, pContext ); } Chapitre 13 ? Chargement de contenu ? version 0.1.2 31 / 84 Thibault Imbert pratiqueactionscript3.bytearray.org En délégant les fonctionnalités à une classe interne. La classe ChargeurMedia procède à une délégation. Ce mécanisme est l?un des plus puissants de la composition. Grâce à cela, la classe ChargeurMedia peut à l?exécution décider d?utiliser un autre type d?objet dans lequel charger les données. Chose impossible si la classe ChargeurMedia avait héritée de la classe Loader. C?est pour cette raison que la composition est considérée comme une approche dynamique, s?opposant au caractère statique de l?héritage lié à la compilation. Sur la scène nous posons deux boutons boutonSuivant et boutonPrecedent, auxquels nous donnons deux noms d?occurrences respectifs. La figure 13-6 illustre l?interface : Figure 13-6. Boutons de navigation. Nous définissons chaque bouton au sein de la classe Document : package org.bytearray.document { Chapitre 13 ? Chargement de contenu ? version 0.1.2 32 / 84 Thibault Imbert pratiqueactionscript3.bytearray.org import flash.display.SimpleButton; import flash.events.MouseEvent; import org.bytearray.abstrait.ApplicationDefaut; import org.bytearray.chargement.ChargeurMedia; public class Document extends ApplicationDefaut { // interface public var boutonSuivant:SimpleButton; public var boutonPrecedent:SimpleButton; // visionneuse private var visionneuse:ChargeurMedia; // données private var tableauImages:Array; public function Document () { // tableau contenant les url des images tableauImages = new Array ( "imgs/photo1.jpg", "imgs/photo2.jpg", "imgs/photo3.jpg", "imgs/photo4.jpg", "imgs/photo5.jpg" ); // création de l'objet Loader visionneuse = new ChargeurMedia (); addChild ( visionneuse ); boutonPrecedent.addEventListener ( MouseEvent.CLICK, clicPrecedent ); boutonSuivant.addEventListener ( MouseEvent.CLICK, clicSuivant ); } private function clicPrecedent ( pEvt:MouseEvent=null ):void { trace("Image précédente"); } private function clicSuivant ( pEvt:MouseEvent=null ):void { trace("Image suivante"); } } } Puis nous associons la logique spécifique à chaque bouton : package org.bytearray.document Chapitre 13 ? Chargement de contenu ? version 0.1.2 33 / 84 Thibault Imbert pratiqueactionscript3.bytearray.org { import flash.display.SimpleButton; import flash.events.MouseEvent; import flash.net.URLRequest; import org.bytearray.abstrait.ApplicationDefaut; import org.bytearray.chargement.ChargeurMedia; public class Document extends ApplicationDefaut { // interface public var boutonSuivant:SimpleButton; public var boutonPrecedent:SimpleButton; // visionneuse private var visionneuse:ChargeurMedia; // données private var tableauImages:Array; private var position:int; private var requete:URLRequest; public function Document () { position = -1; requete = new URLRequest(); // tableau contenant les url des images tableauImages = new Array ( "imgs/photo1.jpg", "imgs/photo2.jpg", "imgs/photo3.jpg", "imgs/photo4.jpg", "imgs/photo5.jpg" ); // création de l'objet Loader visionneuse = new ChargeurMedia (); addChild ( visionneuse ); boutonPrecedent.addEventListener ( MouseEvent.CLICK, clicPrecedent ); boutonSuivant.addEventListener ( MouseEvent.CLICK, clicSuivant ); } private function clicPrecedent ( pEvt:MouseEvent=null ):void { position = Math.max ( 0, --position ); requete.url = tableauImages [ position ]; visionneuse.load ( requete ); } private function clicSuivant ( pEvt:MouseEvent=null ):void { Chapitre 13 ? Chargement de contenu ? version 0.1.2 34 / 84 Thibault Imbert pratiqueactionscript3.bytearray.org position = ++position % tableauImages.length; requete.url = tableauImages [ position ]; visionneuse.load ( requete ); } } } A chaque clic, nous déclenchons la fonction clicPrecedent ou clicSuivant. Nous remarquons que lorsque nous cliquons sur le bouton boutonSuivant nous tournons en boucle, à l?inverse le boutonPrecedent bute à l?index 0. En créant une propriété position, nous incrémentons ou décrémentons celle-ci, afin de naviguer au sein du tableau tableauImages. Ne vous est-il jamais arrivé de charger des images de différentes dimensions, et de souhaiter que quelles que soient leurs tailles, celles- ci s?affiche correctement dans un espace donné ? Redimensionnement automatique L?une des premières fonctionnalités que nous allons ajouter à la classe Loader concerne l?ajustement automatique de l?image selon une dimension spécifiée. Les images chargées au sein de la galerie peuvent parfois déborder ou ne pas être ajustées automatiquement. Cela est généralement prévu par la partie serveur mais afin de sécuriser notre application, nous allons intégrer ce mécanisme côté client. Cela nous évitera de mauvaises surprises au cas où l?image n?aurait pas été correctement redimensionnée au préalable. Lorsque l?événement Event.COMPLETE est diffusé, nous ajustons automatiquement la taille de l?objet ChargeurMedia aux dimensions voulues tout en conservant les proportions afin de ne pas déformer le contenu chargé : package org.bytearray.chargement { import flash.display.Loader; import flash.display.Sprite; import flash.display.LoaderInfo; import flash.events.Event; import flash.events.ProgressEvent; Chapitre 13 ? Chargement de contenu ? version 0.1.2 35 / 84 Thibault Imbert pratiqueactionscript3.bytearray.org import flash.events.IOErrorEvent; import flash.net.URLRequest; import flash.system.LoaderContext; import flash.display.DisplayObject; public class ChargeurMedia extends Sprite { private var chargeur:Loader; private var cli:LoaderInfo; private var largeur:Number; private var hauteur:Number; public function ChargeurMedia ( pLargeur:Number, pHauteur:Number ) { largeur = pLargeur; hauteur = pHauteur; chargeur = new Loader(); addChild ( chargeur ) ; cli = chargeur.contentLoaderInfo; cli.addEventListener ( Event.INIT, initChargement ); cli.addEventListener ( Event.OPEN, demarreChargement ); cli.addEventListener ( ProgressEvent.PROGRESS, chargement ); cli.addEventListener ( Event.COMPLETE, chargementTermine ); cli.addEventListener ( IOErrorEvent.IO_ERROR, echecChargement ); } public function load ( pURL:URLRequest, pContext:LoaderContext=null ):void { chargeur.load ( pURL, pContext ); } private function demarreChargement ( pEvt:Event ):void { trace ("démarre chargement"); } private function initChargement ( pEvt:Event ):void { trace ("initialisation chargement"); } private function chargement ( pEvt:Event ):void Chapitre 13 ? Chargement de contenu ? version 0.1.2 36 / 84 Thibault Imbert pratiqueactionscript3.bytearray.org { trace ("chargement en cours"); } private function chargementTermine ( pEvt:Event ):void { var contenuCharge:DisplayObject = pEvt.target.content; var ratio:Number = Math.min ( largeur / contenuCharge.width, hauteur / contenuCharge.height ); scaleX = scaleY = 1; if ( ratio < 1 ) scaleX = scaleY = ratio; } private function echecChargement ( pEvt:Event ):void { trace ("erreur de chargement"); } } } Puis nous instancions l?objet ChargeurMedia en spécifiant les dimensions à respecter : // création de la visionneuse visionneuse = new ChargeurMedia ( 350, 450 ); Si nous testons la galerie nous remarquons que les images sont bien affichées et correctement redimensionnées si cela est nécessaire. Pour le moment, elles ne sont pas centrées : Chapitre 13 ? Chargement de contenu ? version 0.1.2 37 / 84 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 13-7. Galerie. Nous devons placer le code correspondant au centrage de l?image en dehors de la classe ChargeurMedia. Intégrer une telle logique à notre classe ChargeurMedia la rendrait rigide. N?oubliez pas un point essentiel de la programmation orientée objet ! Nous devons isoler ce qui risque d?être modifié d?un projet à un autre, nous plaçons donc le positionnement des images en dehors de la classe ChargeurMedia. Afin de pouvoir utiliser la classe ChargeurMedia comme la classe Loader, nous devons à présent diffuser tous les événements nécessaires à son bon fonctionnement. Afin de permettre cette diffusion nous devons d?abord les écouter en interne, puis les rediriger par la suite. Nous modifions pour cela chaque fonction écouteur, puis nous procédons à la redirection de chaque événement : package org.bytearray.chargement { import flash.display.Loader; import flash.display.Sprite; import flash.display.DisplayObject; import flash.display.LoaderInfo; import flash.events.Event; Chapitre 13 ? Chargement de contenu ? version 0.1.2 38 / 84 Thibault Imbert pratiqueactionscript3.bytearray.org import flash.events.ProgressEvent; import flash.events.IOErrorEvent; import flash.net.URLRequest; import flash.system.LoaderContext; public class ChargeurMedia extends Sprite { private var chargeur:Loader; private var cli:LoaderInfo; private var largeur:Number; private var hauteur:Number; public function ChargeurMedia ( pLargeur:Number, pHauteur:Number ) { largeur = pLargeur; hauteur = pHauteur; chargeur = new Loader(); addChild ( chargeur ) ; cli = chargeur.contentLoaderInfo; cli.addEventListener ( Event.INIT, initChargement ); cli.addEventListener ( Event.OPEN, demarreChargement ); cli.addEventListener ( ProgressEvent.PROGRESS, chargement ); cli.addEventListener ( Event.COMPLETE, chargementTermine ); cli.addEventListener ( IOErrorEvent.IO_ERROR, echecChargement ); } public function load ( pURL:URLRequest, pContext:LoaderContext=null ):void { chargeur.load ( pURL, pContext ); } private function demarreChargement ( pEvt:Event ):void { dispatchEvent ( pEvt ); } private function initChargement ( pEvt:Event ):void { dispatchEvent ( pEvt ); } private function chargement ( pEvt:Event ):void Chapitre 13 ? Chargement de contenu ? version 0.1.2 39 / 84 Thibault Imbert pratiqueactionscript3.bytearray.org { dispatchEvent ( pEvt ); } private function chargementTermine ( pEvt:Event ):void { var contenuCharge:DisplayObject = pEvt.target.content; var ratio:Number = Math.min ( largeur / contenuCharge.width, hauteur / contenuCharge.height ); scaleX = scaleY = 1; if ( ratio < 1 ) scaleX = scaleY = ratio; dispatchEvent ( pEvt ); } private function echecChargement ( pEvt:Event ):void { dispatchEvent ( pEvt ); } } } De cette manière, la classe ChargeurMedia, diffuse les mêmes événements que la classe Loader. Cela reste totalement transparent pour l?utilisateur de la classe ChargeurMedia et s?inscrit comme suite logique de la composition et de la délégation. Lorsqu?ils sont redirigés, les objets événementiels voient leur propriété target référencer l?objet diffuseur ayant relayé l?événement. La propriété target fait donc désormais référence à l?objet ChargeurMedia et non plus à l?objet LoaderInfo. En écoutant l?événement Event.COMPLETE auprès de la classe ChargeurMedia, nous sommes avertis de la fin du chargement du contenu : package org.bytearray.document { import flash.display.SimpleButton; import flash.events.Event; import flash.events.MouseEvent; Chapitre 13 ? Chargement de contenu ? version 0.1.2 40 / 84 Thibault Imbert pratiqueactionscript3.bytearray.org import org.bytearray.abstrait.ApplicationDefaut; import org.bytearray.chargement.ChargeurMedia; public class Document extends ApplicationDefaut { // interface public var boutonSuivant:SimpleButton; public var boutonPrecedent:SimpleButton; // visionneuse private var visionneuse:ChargeurMedia; public function Document () { // tableau contenant les url des images var tableauImages:Array = new Array ( "imgs/photo1.jpg", "imgs/anim1.swf", "imgs/photo3.jpg", "imgs/photo4.jpg", "imgs/photo5.jpg" ); // création de l'objet Loader visionneuse = new ChargeurMedia ( 350, 450 ); addChild ( visionneuse ); // écoute de l'événement Event.COMPLETE auprès de la visionneuse visionneuse.addEventListener ( Event.COMPLETE, chargementTermine ); boutonPrecedent.addEventListener ( MouseEvent.CLICK, clicPrecedent ); boutonSuivant.addEventListener ( MouseEvent.CLICK, clicSuivant ); } private function chargementTermine ( pEvt:Event ):void { // centre la visionneuse visionneuse.x = ( stage.stageWidth - visionneuse.width ) / 2; visionneuse.y = ( stage.stageHeight - visionneuse.height ) / 2; } private function clicPrecedent ( pEvt:MouseEvent=null ):void { position = Math.max ( 0, --position ); requete.url = tableauImages [ position ]; visionneuse.load ( requete ); } private function clicSuivant ( pEvt:MouseEvent=null ):void { Chapitre 13 ? Chargement de contenu ? version 0.1.2 41 / 84 Thibault Imbert pratiqueactionscript3.bytearray.org position = ++position % tableauImages.length; requete.url = tableauImages [ position ]; visionneuse.load ( requete ); } } } Afin de garantir un affichage aligné sur les pixels, il est recommandé de toujours positionner une image sur des coordonnées non flottantes. Pour cela, nous modifions la fonction écouteur chargementTermine en arrondissant les coordonnées de l?image centrée : private function chargementTermine ( pEvt:Event ):void { // centre la visionneuse visionneuse.x = int ( ( stage.stageWidth - visionneuse.width ) / 2); visionneuse.y = int ( ( stage.stageHeight - visionneuse.height ) / 2); } Si nous testons la galerie, les images sont correctement chargées et centrées, comme l?illustre la figure 13-8 : Chapitre 13 ? Chargement de contenu ? version 0.1.2 42 / 84 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 13-8. Galerie centrée. Afin de charger la première image, nous pouvons déclencher manuellement la fonction écouteur clicSuivant : public function Document () { // tableau contenant les url des images var tableauImages:Array = new Array ( "imgs/photo1.jpg", "imgs/photo2.jpg", "imgs/photo3.jpg", "imgs/photo4.jpg", "imgs/photo5.jpg" ); // création de l'objet Loader visionneuse = new ChargeurMedia ( tableauImages, 350, 450 ); addChild ( visionneuse ); // écoute de l'événement Event.COMPLETE auprès de la visionneuse visionneuse.addEventListener ( Event.COMPLETE, chargementTermine ); boutonPrecedent.addEventListener ( MouseEvent.CLICK, clicPrecedent ); boutonSuivant.addEventListener ( MouseEvent.CLICK, clicSuivant ); clicSuivant(); } Lorsque l?application est lancée, la première image est chargée automatiquement. Nous venons de déclencher ici, manuellement une fonction écouteur liée à l?événement MouseEvent.CLICK. Le code de la classe ChargeurMedia peut être optimisé en utilisant une seule fonction écouteur pour rediriger différents types d?événements. Dans le code suivant la méthode redirigeEvenement redirige plusieurs événements : package org.bytearray.chargement { import flash.display.Bitmap; import flash.display.Loader; import flash.display.Sprite; import flash.display.DisplayObject; import flash.display.LoaderInfo; import flash.events.Event; import flash.events.ProgressEvent; import flash.events.IOErrorEvent; import flash.net.URLRequest; import flash.system.LoaderContext; public class ChargeurMedia extends Sprite { private var chargeur:Loader; Chapitre 13 ? Chargement de contenu ? version 0.1.2 43 / 84 Thibault Imbert pratiqueactionscript3.bytearray.org private var cli:LoaderInfo; private var largeur:Number; private var hauteur:Number; public function ChargeurMedia ( pLargeur:Number, pHauteur:Number ) { largeur = pLargeur; hauteur = pHauteur; chargeur = new Loader(); addChild ( chargeur ) ; cli = chargeur.contentLoaderInfo; cli.addEventListener ( Event.INIT, redirigeEvenement ); cli.addEventListener ( Event.OPEN, redirigeEvenement ); cli.addEventListener ( ProgressEvent.PROGRESS, redirigeEvenement ); cli.addEventListener ( Event.COMPLETE, chargementTermine ); cli.addEventListener ( IOErrorEvent.IO_ERROR, redirigeEvenement ); } public function load ( pURL:URLRequest, pContext:LoaderContext=null ):void { chargeur.load ( pURL, pContext ); } private function redirigeEvenement ( pEvt:Event ):void { dispatchEvent ( pEvt ); } private function chargementTermine ( pEvt:Event ):void { var contenuCharge:DisplayObject = pEvt.target.content; var ratio:Number = Math.min ( largeur / contenuCharge.width, hauteur / contenuCharge.height ); scaleX = scaleY = 1; if ( ratio < 1 ) scaleX = scaleY = ratio; dispatchEvent ( pEvt ); } Chapitre 13 ? Chargement de contenu ? version 0.1.2 44 / 84 Thibault Imbert pratiqueactionscript3.bytearray.org } } Bien entendu, cette technique n?est viable que dans le cas où aucune logique ne doit être ajoutée en interne pour chaque événement diffusé. C?est pour cette raison que l?événement Event.COMPLETE est toujours écouté en interne par la méthode écouteur chargementTermine, car nous devons ajouter une logique de redimensionnement spécifique. Gestion du lissage Nous allons ajouter une seconde fonctionnalité permettant d?afficher les images chargées de manière lissée. Ainsi, lorsque l?image chargée est redimensionnée, grâce au lissage celle-ci n?est pas crénelée. Pour cela, nous activons la propriété smoothing si le contenu chargé est de type Bitmap : package org.bytearray.chargement { import flash.display.Bitmap; import flash.display.Loader; import flash.display.Sprite; import flash.display.DisplayObject; import flash.display.LoaderInfo; import flash.events.Event; import flash.events.ProgressEvent; import flash.events.IOErrorEvent; import flash.net.URLRequest; import flash.system.LoaderContext; public class ChargeurMedia extends Sprite { private var chargeur:Loader private var cli:LoaderInfo; private var largeur:Number; private var hauteur:Number; private var lissage:Boolean; public function ChargeurMedia ( pLargeur:Number, pHauteur:Number, pLissage:Boolean=true ) { largeur = pLargeur; hauteur = pHauteur; lissage = pLissage; chargeur = new Loader(); Chapitre 13 ? Chargement de contenu ? version 0.1.2 45 / 84 Thibault Imbert pratiqueactionscript3.bytearray.org addChild ( chargeur ) ; cli = chargeur.contentLoaderInfo; cli.addEventListener ( Event.INIT, redirigeEvenement ); cli.addEventListener ( Event.OPEN, redirigeEvenement ); cli.addEventListener ( ProgressEvent.PROGRESS, redirigeEvenement ); cli.addEventListener ( Event.COMPLETE, chargementTermine ); cli.addEventListener ( IOErrorEvent.IO_ERROR, redirigeEvenement ); } public function load ( pURL:URLRequest, pContext:LoaderContext=null ):void { chargeur.load ( pURL, pContext ); } private function redirigeEvenement ( pEvt:Event ):void { dispatchEvent ( pEvt ); } private function chargementTermine ( pEvt:Event ):void { var contenuCharge:DisplayObject = pEvt.target.content; if ( contenuCharge is Bitmap ) Bitmap ( contenuCharge ).smoothing = lissage; var ratio:Number = Math.min ( largeur / contenuCharge.width, hauteur / contenuCharge.height ); scaleX = scaleY = 1; if ( ratio < 1 ) scaleX = scaleY = ratio; dispatchEvent ( pEvt ); } } } Puis nous modifions l?instanciation de l?objet ChargeurMedia : // création de l'objet Loader visionneuse = new ChargeurMedia ( tableauImages, 350, 450, true ); Chapitre 13 ? Chargement de contenu ? version 0.1.2 46 / 84 Thibault Imbert pratiqueactionscript3.bytearray.org Les images sont désormais lissées automatiquement. Si nous souhaitons désactiver le lissage, nous le précisons lors de l?instanciation de l?objet ChargeurMedia : // création de l'objet Loader visionneuse = new ChargeurMedia ( tableauImages, 350, 450, false ); Souvenez-vous, nous devons toujours intégrer un mécanisme de desactivation des objets. Ainsi la classe ChargeurMedia ne déroge pas à la règle et intègre un mécanisme de désactivation. Aussitôt supprimée de la liste d?affichage, l?instance de ChargeurMedia est désactivée grâce à l?événement Event.REMOVED_FROM_STAGE. Nous utilisons l?événement Event.ADDED_TO_STAGE afin d?initialiser l?objet ChargeurMedia lorsque celui-ci est affiché : package org.bytearray.chargement { import flash.display.Bitmap; import flash.display.Loader; import flash.display.Sprite; import flash.display.DisplayObject; import flash.display.LoaderInfo; import flash.events.Event; import flash.events.ProgressEvent; import flash.events.IOErrorEvent; import flash.net.URLRequest; import flash.system.LoaderContext; public class ChargeurMedia extends Sprite { private var chargeur:Loader; private var cli:LoaderInfo; private var largeur:Number; private var hauteur:Number; private var lissage:Boolean; public function ChargeurMedia ( pLargeur:Number, pHauteur:Number, pLissage:Boolean=true ) { largeur = pLargeur; hauteur = pHauteur; lissage = pLissage; chargeur = new Loader(); addChild ( chargeur ); cli = chargeur.contentLoaderInfo; Chapitre 13 ? Chargement de contenu ? version 0.1.2 47 / 84 Thibault Imbert pratiqueactionscript3.bytearray.org addEventListener ( Event.ADDED_TO_STAGE, activation ); addEventListener ( Event.REMOVED_FROM_STAGE, desactivation ); } private function activation ( pEvt:Event ):void { cli.addEventListener ( Event.INIT, redirigeEvenement ); cli.addEventListener ( Event.OPEN, redirigeEvenement ); cli.addEventListener ( ProgressEvent.PROGRESS, redirigeEvenement ); cli.addEventListener ( Event.COMPLETE, chargementTermine ); cli.addEventListener ( IOErrorEvent.IO_ERROR, redirigeEvenement ); } private function desactivation ( pEvt:Event ):void { cli.removeEventListener ( Event.INIT, redirigeEvenement ); cli.removeEventListener ( Event.OPEN, redirigeEvenement ); cli.removeEventListener ( ProgressEvent.PROGRESS, redirigeEvenement ); cli.removeEventListener ( Event.COMPLETE, chargementTermine ); cli.removeEventListener ( IOErrorEvent.IO_ERROR, redirigeEvenement ); } public function load ( pURL:URLRequest, pContext:LoaderContext=null ):void { chargeur.load ( pURL, pContext ); } private function redirigeEvenement ( pEvt:Event ):void { dispatchEvent ( pEvt ); } private function chargementTermine ( pEvt:Event ):void { var contenuCharge:DisplayObject = pEvt.target.content; if ( contenuCharge is Bitmap ) Bitmap ( contenuCharge ).smoothing = lissage; var ratio:Number = Math.min ( largeur / contenuCharge.width, hauteur / contenuCharge.height ); Chapitre 13 ? Chargement de contenu ? version 0.1.2 48 / 84 Thibault Imbert pratiqueactionscript3.bytearray.org scaleX = scaleY = 1; if ( ratio < 1 ) scaleX = scaleY = ratio; dispatchEvent ( pEvt ); } } } La classe ChargeurMedia fonctionne sans problème. Nous verrons au cours du chapitre 14 intitulé Chargement de données comment remplacer les données par un flux XML chargé dynamiquement. Nous verrons comment réutiliser la classe ChargeurMedia tout en la faisant communiquer avec une classe permettant le chargement de données XML externe. Précharger le contenu Il est impératif de précharger tout contenu dynamique. Un utilisateur interrompt très rapidement sa navigation si aucun élément n?indique visuellement l?état du chargement des données. Dans le cas de notre galerie photo, nous devons précharger chaque image afin d?informer l?utilisateur de l?état du chargement. Nous allons utiliser une barre de préchargement classique puis utiliser chacun des événements afin de synchroniser son affichage. Nous allons tout d?abord créer un symbole contenant la barre de préchargement. La figure 13-9 illustre le symbole en bibliothèque : Chapitre 13 ? Chargement de contenu ? version 0.1.2 49 / 84 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 13-9. Symbole prechargeur. Puis nous définissons une classe Prechargeur auquel le symbole est lié : Figure 13-10. Classe Prechargeur associée. Une fois le symbole associé, nous l?instancions dès l?initialisation de l?application. Vous remarquerez que celui-ci n?est pas affiché pour l?instant : package org.bytearray.document { import flash.display.SimpleButton; import flash.display.Sprite; Chapitre 13 ? Chargement de contenu ? version 0.1.2 50 / 84 Thibault Imbert pratiqueactionscript3.bytearray.org import flash.events.MouseEvent; import flash.events.Event; import flash.events.ProgressEvent; import flash.net.URLRequest; import org.bytearray.abstrait.ApplicationDefaut; import org.bytearray.chargement.ChargeurMedia; public class Document extends ApplicationDefaut { // interface public var boutonSuivant:SimpleButton; public var boutonPrecedent:SimpleButton; public var prechargeur:Prechargeur; // visionneuse private var visionneuse:ChargeurMedia; // données private var tableauImages:Array; private var position:int; private var requete:URLRequest; public function Document () { position = -1; requete = new URLRequest(); // tableau contenant les url des images tableauImages = new Array ( "imgs/photo1.jpg", "imgs/photo2.jpg", "imgs/photo3.jpg", "imgs/photo4.jpg", "imgs/photo5.jpg" ); // création de l'objet Loader visionneuse = new ChargeurMedia ( 445, 335 ); addChild ( visionneuse ); prechargeur = new Prechargeur(); prechargeur.x = Math.round ( (stage.stageWidth - prechargeur.width) / 2); prechargeur.y = Math.round ( (stage.stageHeight - prechargeur.height) / 2); visionneuse.addEventListener ( Event.COMPLETE, chargementTermine ); boutonPrecedent.addEventListener ( MouseEvent.CLICK, clicPrecedent ); boutonSuivant.addEventListener ( MouseEvent.CLICK, clicSuivant ); clicSuivant(); } private function chargementTermine ( pEvt:Event ):void { Chapitre 13 ? Chargement de contenu ? version 0.1.2 51 / 84 Thibault Imbert pratiqueactionscript3.bytearray.org // centre la visionneuse visionneuse.x = int ( ( stage.stageWidth - visionneuse.width ) / 2); visionneuse.y = int ( ( stage.stageHeight - visionneuse.height ) / 2); } private function clicPrecedent ( pEvt:MouseEvent=null ):void { position = Math.max ( 0, --position ); requete.url = tableauImages [ position ]; visionneuse.load ( requete ); } private function clicSuivant ( pEvt:MouseEvent=null ):void { position = ++position % tableauImages.length; requete.url = tableauImages [ position ]; visionneuse.load ( requete ); } } } Puis nous écoutons chacun des événements nécessaires au préchargement : package org.bytearray.document { import flash.display.SimpleButton; import flash.display.Sprite; import flash.events.MouseEvent; import flash.events.Event; import flash.events.ProgressEvent; import flash.net.URLRequest; import org.bytearray.abstrait.ApplicationDefaut; import org.bytearray.chargement.ChargeurMedia; public class Document extends ApplicationDefaut { // interface public var boutonSuivant:SimpleButton; public var boutonPrecedent:SimpleButton; public var prechargeur:Prechargeur; Chapitre 13 ? Chargement de contenu ? version 0.1.2 52 / 84 Thibault Imbert pratiqueactionscript3.bytearray.org // visionneuse private var visionneuse:ChargeurMedia; // données private var tableauImages:Array; private var position:int; private var requete:URLRequest; public function Document () { position = -1; requete = new URLRequest(); // tableau contenant les url des images tableauImages = new Array ( "imgs/photo1.jpg", "imgs/photo2.jpg", "imgs/photo3.jpg", "imgs/photo4.jpg", "imgs/photo5.jpg" ); // création de l'objet Loader visionneuse = new ChargeurMedia ( 445, 335 ); addChild ( visionneuse ); prechargeur = new Prechargeur(); prechargeur.x = Math.round ( (stage.stageWidth - prechargeur.width) / 2); prechargeur.y = Math.round ( (stage.stageHeight - prechargeur.height) / 2); visionneuse.addEventListener ( Event.OPEN, chargementDemarre ); visionneuse.addEventListener ( ProgressEvent.PROGRESS, chargementEnCours ); visionneuse.addEventListener ( Event.COMPLETE, chargementTermine ); boutonPrecedent.addEventListener ( MouseEvent.CLICK, clicPrecedent ); boutonSuivant.addEventListener ( MouseEvent.CLICK, clicSuivant ); clicSuivant ( new MouseEvent ( MouseEvent.CLICK ) ); } private function chargementDemarre ( pEvt:Event ):void { } private function chargementEnCours ( pEvt:ProgressEvent ):void { } private function chargementTermine ( pEvt:Event ):void Chapitre 13 ? Chargement de contenu ? version 0.1.2 53 / 84 Thibault Imbert pratiqueactionscript3.bytearray.org { // centre la visionneuse visionneuse.x = int ( ( stage.stageWidth - visionneuse.width ) / 2); visionneuse.y = int ( ( stage.stageHeight - visionneuse.height ) / 2); } private function clicPrecedent ( pEvt:MouseEvent=null ):void { position = Math.max ( 0, --position ); requete.url = tableauImages [ position ]; visionneuse.load ( requete ); } private function clicSuivant ( pEvt:MouseEvent=null ):void { position = ++position % tableauImages.length; requete.url = tableauImages [ position ]; visionneuse.load ( requete ); } } } Lorsque le chargement démarre, nous ajoutons la barre de préchargement à l?affichage si celle-ci n?est pas déjà affichée : private function chargementDemarre ( pEvt:Event ):void { if ( !contains ( prechargeur ) ) addChild ( prechargeur ); } Puis nous adaptons sa taille selon le pourcentage chargé : private function chargementEnCours ( pEvt:ProgressEvent ):void { prechargeur.scaleX = pEvt.bytesLoaded / pEvt.bytesTotal; } Enfin, nous supprimons la barre de préchargement lorsque les données sont chargées : Chapitre 13 ? Chargement de contenu ? version 0.1.2 54 / 84 Thibault Imbert pratiqueactionscript3.bytearray.org private function chargementTermine ( pEvt:Event ):void { // centre la visionneuse visionneuse.x = int ( ( stage.stageWidth - visionneuse.width ) / 2); visionneuse.y = int ( ( stage.stageHeight - visionneuse.height ) / 2); if ( contains ( prechargeur ) ) removeChild ( prechargeur ); } En testant notre galerie, chaque image est préchargée, comme l?illustre la figure 13-11 : Figure 13-11. Préchargement des images. Libre à vous d?intégrer différents types d?éléments indiquant le niveau de chargement des données. Nous aurions pu utiliser une barre de préchargement accompagnée d?un champ texte affichant un pourcentage de 0 à 100, ou bien une animation se jouant selon le volume de données chargées. Afin d?ajouter un peu d?esthétisme à notre galerie, nous ajoutons un effet de fondu permettant aux photos d?être affichées progressivement : package org.bytearray.document { import flash.display.SimpleButton; Chapitre 13 ? Chargement de contenu ? version 0.1.2 55 / 84 Thibault Imbert pratiqueactionscript3.bytearray.org import flash.display.Sprite; import flash.events.MouseEvent; import flash.events.Event; import flash.events.ProgressEvent; import flash.net.URLRequest; import fl.transitions.Tween; import fl.transitions.easing.Strong; import org.bytearray.abstrait.ApplicationDefaut; import org.bytearray.chargement.ChargeurMedia; public class Document extends ApplicationDefaut { // interface public var boutonSuivant:SimpleButton; public var boutonPrecedent:SimpleButton; public var prechargeur:Prechargeur; // visionneuse private var visionneuse:ChargeurMedia; // données private var tableauImages:Array; private var position:int; private var requete:URLRequest; // fondu private var fondu:Tween; public function Document () { position = -1; requete = new URLRequest(); // tableau contenant les url des images tableauImages = new Array ( "imgs/photo1.jpg", "imgs/photo2.jpg", "imgs/photo3.jpg", "imgs/photo4.jpg", "imgs/photo5.jpg" ); // création de l'objet Loader visionneuse = new ChargeurMedia ( 445, 335 ); addChild ( visionneuse ); fondu = new Tween ( visionneuse, "alpha", Strong.easeOut, 1, 1, 0, true ); prechargeur = new Prechargeur(); prechargeur.x = int ( (stage.stageWidth - prechargeur.width) / 2); prechargeur.y = int ( (stage.stageHeight - prechargeur.height) / 2); visionneuse.addEventListener ( Event.OPEN, chargementDemarre ); visionneuse.addEventListener ( ProgressEvent.PROGRESS, chargementEnCours ); visionneuse.addEventListener ( Event.COMPLETE, chargementTermine ); Chapitre 13 ? Chargement de contenu ? version 0.1.2 56 / 84 Thibault Imbert pratiqueactionscript3.bytearray.org boutonPrecedent.addEventListener ( MouseEvent.CLICK, clicPrecedent ); boutonSuivant.addEventListener ( MouseEvent.CLICK, clicSuivant ); clicSuivant ( new MouseEvent ( MouseEvent.CLICK ) ); } private function chargementDemarre ( pEvt:Event ):void { if ( !contains ( prechargeur ) ) addChild ( prechargeur ); } private function chargementEnCours ( pEvt:ProgressEvent ):void { prechargeur.scaleX = pEvt.bytesLoaded / pEvt.bytesTotal; } private function chargementTermine ( pEvt:Event ):void { // centre la visionneuse visionneuse.x = Math.round ( ( stage.stageWidth - visionneuse.width ) / 2); visionneuse.y = Math.round ( ( stage.stageHeight - visionneuse.height ) / 2); if ( contains ( prechargeur ) ) removeChild ( prechargeur ); fondu.continueTo ( 1, 1 ); } private function clicPrecedent ( pEvt:MouseEvent=null ):void { fondu.continueTo ( 0, 1 ); position = Math.max ( 0, --position ); requete.url = tableauImages [ position ]; visionneuse.load ( requete ); } private function clicSuivant ( pEvt:MouseEvent=null ):void { fondu.continueTo ( 0, 1 ); position = ++position % tableauImages.length; Chapitre 13 ? Chargement de contenu ? version 0.1.2 57 / 84 Thibault Imbert pratiqueactionscript3.bytearray.org requete.url = tableauImages [ position ]; visionneuse.load ( requete ); } } } En testant notre galerie, nous remarquons un effet de fondu entre chaque image. Nous pouvons rendre le type de transition dynamique en passant en paramètre un type de comportement spécifique. A vous d?imaginer la suite, pour cela voici un indice : En privilégiant une approche par composition, une classe comportementale pourrait être définie puis passée en paramètre à la classe ChargeurMedia afin de terminer un type de transition. A retenir ? Le contenu doit toujours être préchargé afin d?offrir une expérience utilisateur optimale. Interrompre le chargement Il était impossible avec les précédentes versions d?ActionScript d?interrompre le chargement d?un élément externe. Le seul moyen était de fermer le lecteur Flash. ActionScript 3 intègre dorénavant une méthode close sur la classe Loader permettant de stopper instantanément le chargement d?un élément. Afin de rendre notre classe ChargeurMedia souple, nous pouvons ajouter une nouvelle méthode close : public function close ( ):void { chargeur.close(); } Celle-ci délègue cette fonctionnalité à la classe Loader interne. Lorsque la méthode load est appelée, tout chargement précédemment initié est interrompu. Nous n?avons pas abordé jusqu?à présent la notion de communication entre l?objet Loader et le contenu. Dans la partie suivante nous allons nous attarder sur la communication entre deux animations. Chapitre 13 ? Chargement de contenu ? version 0.1.2 58 / 84 Thibault Imbert pratiqueactionscript3.bytearray.org A retenir ? La méthode close de l?objet Loader permet d?interrompre le chargement en cours. Communiquer entre deux animations Dans un nouveau document Flash CS3 nous plaçons sur la scène une instance de symbole animé. L?animation est représentée par une instance du symbole Garcon utilisé lors du chapitre précédent. Nous lui donnons animation comme nom d?occurrence : Figure 13-12. Instance du symbole Garcon. Une fois l?animation exportée, nous la chargeons à l?aide de l?objet Loader depuis un autre SWF : var chargeur:Loader = new Loader(); chargeur.contentLoaderInfo.addEventListener ( Event.COMPLETE, termine ); chargeur.load ( new URLRequest ("chap-13-anim.swf") ); addChild ( chargeur ); function termine ( pEvt:Event ):void { // référence le scénario de l'animation chargée var scenario:DisplayObject = pEvt.target.content; // affiche : [object MainTimeline] trace( scenario ); Chapitre 13 ? Chargement de contenu ? version 0.1.2 59 / 84 Thibault Imbert pratiqueactionscript3.bytearray.org } Une fois l?animation chargée, nous pouvons stopper l?animation en utilisant la syntaxe pointée : function termine ( pEvt:Event ):void { // référence le scénario de l'animation chargée var scenario:DisplayObject = pEvt.target.content; // si le scénario est un MovieClip nous accédons // à l'animation et la stoppons if ( scenario is MovieClip ) MovieClip ( scenario ).animation.stop(); } Si l?accès au contenu de l?animation chargée s?avère aisée, la communication inverse s?avère plus complexe. Afin d?accéder au scénario du SWF initiant le chargement, nous utilisons la propriété root du scénario principal du SWF chargé, puis à la propriété parent de ce même scénario : // affiche : [object Loader] trace( root.parent ); Nous accédons ainsi à l?objet Loader chargeant actuellement notre animation. En ciblant la propriété root de ce même objet nous accédons au scénario principal du SWF chargeur : // affiche : [object MainTimeline] trace( root.parent.root ); Afin de cibler une animation posée sur ce même scénario, nous pouvons écrire le code suivant : // référence le scénario principal du SWF parent var scenarioPrincipal:DisplayObject = root.parent.root; // si celui-ci est un MovieClip if ( scenarioPrincipal is MovieClip ) { // alors nous transtypons et accédons au clip animation MovieClip ( scenarioPrincipal ).animation.stop(); } Notons que si l?objet Loader initiant le chargement n?est pas présent au sein de la liste d?affichage, sa propriété root renverra null. Dans l?exemple précédent, nous n?avons rencontré aucune difficulté à accéder au contenu l?animation chargée car les deux animations évoluent depuis le même domaine. Chapitre 13 ? Chargement de contenu ? version 0.1.2 60 / 84 Thibault Imbert pratiqueactionscript3.bytearray.org Une autre technique plus élégante consiste à diffuser un événement personnalisé depuis le SWF initiant le chargement. Le SWF chargé est à l?écoute de ce dernier et reçoit les informations de manière souple et élégante. Pour réaliser cet échange, nous utilisons l?objet EventDispatcher disponible par la propriété sharedEvents de la classe LoaderInfo. Au sein du SWF initiant le chargement, nous diffusons un événement personnalisé contenant les informations à transmettre : var chargeur:Loader = new Loader(); chargeur.contentLoaderInfo.addEventListener ( Event.COMPLETE, termine ); chargeur.load ( new URLRequest ("chap-13-anim.swf") ); addChild ( chargeur ); function termine ( pEvt:Event ):void { // nous diffusons un événement EvenementInfos.INFOS au SWF chargé pEvt.target.sharedEvents.dispatchEvent ( new EvenementInfos ( EvenementInfos.INFOS, "contenu !" ) ); } Au sein du SWF chargé nous écoutons ce même événement : loaderInfo.sharedEvents.addEventListener ( EvenementInfos.INFOS, ecouteur ); function ecouteur ( pEvt:EvenementInfos ):void { // affiche : contenu ! trace( pEvt.infos ); } Cette approche facilite grandement la communication entre deux animations et doit être considérée en priorité car elle ne souffre d?aucune restriction de sécurité même en contexte interdomaine. Dans un contexte de chargement de contenu externe, nous risquons d?être confrontés aux restrictions de sécurité du lecteur Flash. Dans la partie suivante nous allons faire le point sur ces limitations afin de mieux comprendre comment les appréhender et résoudre certains problèmes. Chapitre 13 ? Chargement de contenu ? version 0.1.2 61 / 84 Thibault Imbert pratiqueactionscript3.bytearray.org Modèle de sécurité du lecteur Flash Depuis le lecteur Flash 6, des restrictions de sécurité ont été ajoutées au sein du lecteur Flash lors du chargement ou d?envoi de données afin de protéger les auteurs de contenu divers. Le modèle de sécurité du lecteur Flash appelé Security Sandbox en Anglais distingue deux acteurs : ? Le créateur du contenu ? Le chargeur de contenu Ce modèle s?applique à tout type de contenu chargé au sein du lecteur. Il faut comprendre que par principe, lorsqu?un contenu graphique est chargé depuis un autre domaine, celui-ci est en lecture seule au sein du lecteur. Ce dernier refuse de scripter ou de modifier tout contenu graphique provenant d?un autre domaine. On dit alors que les fichiers évoluent dans un contexte interdomaine. Par le terme scripter nous entendons l?accès par ActionScript à des données contenues dans une autre animation. Adobe utilise le terme de programmation croisée pour exprimer ce mécanisme. Par le terme de modification, nous entendons par exemple l?activation de la propriété smoothing sur une image bitmap chargée, ou encore la capture d?une vidéo sous forme bitmap à l?aide de la méthode draw de la classe BitmapData. Afin de bien comprendre le modèle de sécurité, nous allons étudier différentes situations. Commençons par le cas de figure suivant : Nous développons un portail de jeux Flash censé charger des jeux provenant de différents sites. Si le lecteur Flash n?intégrait pas de modèle de sécurité, il serait possible d?ajouter du code aux différents jeux chargés et de pirater ces derniers. Si toutefois, nous devons accéder au code d?un jeu chargé, nous devons ajouter au sein de celui-ci une ligne de code autorisant le domaine spécifique à scripter l?animation. En d?autres termes, le contenu chargé doit autoriser le chargeur à le scripter. Dans le cas de chargement d?images provenant d?autres domaines, celles-ci n?ont pas la possibilité d?autoriser le chargeur par ActionScript. Nous utilisons dans cas des fichiers XML contenant la liste des domaines autorisés. Ces fichiers sont appelés fichiers de régulation. A retenir Chapitre 13 ? Chargement de contenu ? version 0.1.2 62 / 84 Thibault Imbert pratiqueactionscript3.bytearray.org ? Par défaut, le lecteur Flash empêche de scripter ou modifier tout contenu graphique provenant d?un domaine différent. ? En revanche, la lecture seule d?animation ou images est toujours autorisée quelque soit le contexte. ? Le chargement de données type XML ou autres est toujours interdit dans un contexte interdomaine. Programmation croisée Nous entendons par le terme de programmation croisée, l?accès par ActionScript au code contenu par un SWF. Nous avons vu au cours de la précédente partie comment faire communiquer deux animations situées sur un même domaine. Nous savons que par défaut, la communication est toujours autorisée entre deux SWF résidant sur le même domaine. A l?inverse, lorsque deux animations souhaitent communiquer mais ne résident par sur le même domaine, l?animation devant être scriptée doit autoriser l?animation ayant amorcé le chargement. La figure 13-13 illustre la situation : Figure 13-13. Animations en contexte interdomaine. Dans le schéma illustré par la figure 13-15 nous voyons deux animations en contexte interdomaine. L?animation SWF A souhaite scripter l?animation SWF B. Pour cela, cette dernière doit appeler la méthode allowDomain de la classe flash.system.Security dont voici la signature : Chapitre 13 ? Chargement de contenu ? version 0.1.2 63 / 84 Thibault Imbert pratiqueactionscript3.bytearray.org public static function allowDomain(... domains):void Nous pouvons passer en paramètre les domaines autorisés à scripter le SWF en cours. Ainsi nous placerons au sein de l?animation SWF B le code suivant : Security.allowDomain("monserveur.fr"); Il n?est pas nécessaire d?ajouter http devant le domaine, nous ne spécifions généralement que le nom et le domaine. Il est aussi possible de spécifier une adresse IP. A retenir ? Dans un contexte interdomaine, l?accès par ActionScript au code contenu par un SWF est appelé programmation croisée. Utiliser un fichier de régulation Comme expliqué précédemment, dans le cas de chargement d?images bitmap provenant d?un serveur distant, il est impossible d'ajouter au sein de celles-ci un appel à la méthode allowDomain. Afin de pallier à ce problème, nous pouvons créer des fichiers de régulation. Ces derniers doivent être placés sur le serveur hébergeant les images et sont en réalité de simples fichiers XML sauvés sous le nom de crossdomain.xml. La figure 13-14 illustre l?idée : Figure 13-14. Fichier de régulation. Chapitre 13 ? Chargement de contenu ? version 0.1.2 64 / 84 Thibault Imbert pratiqueactionscript3.bytearray.org Lorsque le lecteur Flash charge une image, celui-ci tentait dans ses précédentes versions de charger automatiquement le fichier de régulation depuis la racine du serveur. Depuis le lecteur 9, il est nécessaire d?indiquer s?il est nécessaire de charger le fichier de régulation avant de commencer le chargement de l?élément. L?utilisation de fichiers de régulation est fondamentale pour les sites hébergeant des images utilisées au sein d?applications Flash. Voici une liste de sites utilisant un fichier de régulation : ? http://www.facebook.com/crossdomain.xml ? http://www.adobe.com/crossdomain.xml ? http://www.youtube.com/crossdomain.xml ? http://static.flickr.com/crossdomain.xml En analysant le contenu d?un fichier de régulation nous découvrons un simple fichier XML contenant la liste des domaines autorisés à modifier les images. Ces derniers sont appelés plus couramment domaines de confiance. Voici le contenu du fichier de régulation situé sur le serveur YouTube : <cross-domain-policy> <allow-access-from domain="*.youtube.com"/> <allow-access-from domain="*.google.com"/> </cross-domain-policy> Ce fichier de régulation indique que seuls les SWF hébergés dans des sous-domaines de youtube.com ou google.com peuvent scripter les images hébergées sur youtube.com. En analysant le fichier de régulation de flickr, nous remarquons que ces derniers sont beaucoup plus permissifs : <cross-domain-policy> <allow-access-from domain="*"/> </cross-domain-policy> Nous découvrons que tous les domaines peuvent modifier les images hébergées sur flickr.com. A retenir Chapitre 13 ? Chargement de contenu ? version 0.1.2 65 / 84 Thibault Imbert pratiqueactionscript3.bytearray.org ? Dans le cas de chargement d?images distantes, des fichiers de régulation peuvent être créés afin d?autoriser les domaines de confiance. ? Un fichier de régulation est un simple fichier XML. ? Celui-ci doit être nommé par défaut crossdomain.xml. Contexte de chargement Lorsque les méthodes load et loadBytes de la classe Loader sont appelées, nous pouvons passer en deuxième paramètre un contexte de chargement exprimé par la classe flash.system.LoaderContext. La classe LoaderContext permet d?indiquer le domaine d?application et de sécurité dans lequel sera placé le contenu. Le constructeur de la classe LoaderContext accepte trois paramètres dont voici le détail : ? checkPolicyFile : indique si un fichier de régulation doit être chargé avant de commencer à charger le contenu. ? applicationDomain : sert à préciser le domaine d?application à utiliser une fois le contenu chargé. La notion de domaine d?application est traitée dans la partie intitulée Bibliothèque partagée. ? securityDomain : représente le modèle de sécurité. Il est seulement utilisé lors du chargement de fichiers SWF. Son rôle est traité dans la partie intitulée Bibliothèque partagée. Ainsi, afin de pouvoir modifier une image hébergée depuis un domaine distant nous demandons au lecteur Flash de charger un fichier de régulation : var chargeur:Loader = new Loader(); var requete:URLRequest = new URLRequest ("http://www.serveurdistant.com/images/wiiflash.jpg"); var contexte:LoaderContext = new LoaderContext ( true ); chargeur.load ( requete, contexte ); Automatiquement, le lecteur Flash tente de charger un fichier de régulation stocké à la racine du serveur serveurdistant. Si un tel fichier nommé crossdomain.xml est présent et autorise notre domaine alors nous pouvons modifier l?image chargée. Si aucun fichier de régulation n?est trouvé, le lecteur Flash empêche toute modification de l?image chargée. Pour des questions de pratique, il vous est peut-être impossible de placer un fichier de régulation à la racine du domaine distant. Si le Chapitre 13 ? Chargement de contenu ? version 0.1.2 66 / 84 Thibault Imbert pratiqueactionscript3.bytearray.org fichier de régulation est placé à un emplacement différent de la racine du domaine distant. Nous spécifions son chemin à l?aide de la méthode loadPolicyFile de la classe Security. Dans le code suivant, nous spécifions au lecteur de ne pas chercher le fichier de régulation à la racine du domaine mais au sein du répertoire images : var chargeur:Loader = new Loader(); Security.loadPolicyFile("http://serveurdistant.com/images/regulation.xml"); var requete:URLRequest = new URLRequest ("http://www.serveurdistant.com/images/wiiflash.jpg"); var contexte:LoaderContext = new LoaderContext ( true ); chargeur.load ( requete, contexte ); Notons que grâce à la méthode loadPolicyFile nous pouvons aussi spécifier le nom du fichier de régulation, ici regulation.xml. Attention, l?emplacement du fichier de régulation a une importance. A la racine, celui-ci autorise l?accès à tous les fichiers du site. S?il se trouve plus loin dans l?arborescence, il n?autorisera que les fichiers de ce dossier ainsi que ceux des dossiers enfants. Contourner les restrictions de sécurité Nous avons vu dans la partie intitulée Programmation croisée qu?il était possible d?autoriser la manipulation d?images hébergées sur des domaines distants par la création de fichiers de régulation. Malheureusement, dans certains cas, l?ajout de tels fichiers est impossible. Certains sites prévoient quelquefois leur installation, mais ils demeurent minoritaires. Il peut donc être intéressant de savoir contourner les restrictions de sécurité du lecteur Flash. C?est ce que nous allons appendre dès maintenant. Imaginons le cas suivant : Vous venez d?apprendre que vous devez développer une application Flash basée sur des images provenant de différentes sources. En d?autres termes, chaque image devra être chargée tout en étant hébergée sur n?importe quel domaine. Pour l?instant, cela ne pose aucun problème car le simple chargement d?images en situation interdomaine est autorisé. En cours de développement vous vous rendez compte qu?un lissage est nécessaire et qu?il serait intéressant de pouvoir l?activer auprès des images chargées. Chapitre 13 ? Chargement de contenu ? version 0.1.2 67 / 84 Thibault Imbert pratiqueactionscript3.bytearray.org Un premier réflexe vous incite à activer la propriété smoothing sur l?objet Bitmap chargé : // création de l'objet Loader var chargeur:Loader = new Loader(); // écoute de la fin du chargement chargeur.contentLoaderInfo.addEventListener ( Event.COMPLETE, termine ); // image google map var requete:URLRequest = new URLRequest ( "http://kh0.google.fr/kh?n=404&v=22&t=trtqttqqrrqrqssts" ); // chargement de l'image chargeur.load ( requete ); // ajout à la liste d'affichage addChild ( chargeur ); function termine ( pEvt:Event ):void { var objetLoaderInfo:LoaderInfo = LoaderInfo ( pEvt.target ); // accès à l'image bitmap var image:Bitmap = Bitmap ( objetLoaderInfo.content ); // activation du lissage image.smoothing = true; } En testant l?application en local, le lissage fonctionne, la figure 13-15 illustre le résultat : Chapitre 13 ? Chargement de contenu ? version 0.1.2 68 / 84 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 13-15. Image lissée provenant de Google Map. Malheureusement, une fois l?application publiée sur votre serveur tout accès au contenu chargé lève une erreur à l?exécution de type SecurityError : SecurityError: Error #2122: Violation de la sécurité Sandbox : LoaderInfo.content : http://www.bytearray.org/pratique-as3/chap-13-proxy.swf ne peut pas accéder à http://kh0.google.fr/kh?n=404&v=22&t=trtqttqqrrqrqssts. Un fichier de régulation est nécessaire, mais l'indicateur checkPolicyFile n'a pas été défini lors du chargement de ce support. Ceci est dû au fait que le serveur Google ne possède aucun fichier de régulation nous autorisant à manipuler ses images. L?utilisation de la propriété checkPolicyFile est donc dans ce cas précis inutile. En d?autres termes, le contenu chargé est en lecture seule. Nous allons donc utiliser une astuce consistant à faire croire au lecteur Flash que nous chargeons un élément provenant du même domaine. Pour cela, nous utilisons un relais, plus couramment appelé serveur mandataire. La figure 13-16 illustre le concept : Chapitre 13 ? Chargement de contenu ? version 0.1.2 69 / 84 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 13-16. Chargement d?images par serveur mandataire. L?astuce consiste à charger l?image depuis le serveur mandataire, qui est ensuite chargé par le lecteur Flash. Ce dernier pense charger un élément provenant du même domaine, sans penser que le serveur mandataire contient l?image provenant du domaine distant. La création du serveur mandataire se limite à deux lignes de code PHP. Nous utilisons dans notre exemple le langage serveur PHP qui s?avère être un des langages les plus efficace pour travailler avec Flash. Au sein d?un fichier intitulé proxy.php nous ajoutons le script suivant : <?php $chemin = $_POST["chemin"]; readfile($chemin); ?> Nous passons par le tableau $_POST le chemin d?image. Puis la fonction PHP readfile renvoie le flux d?image directement au lecteur Flash. Puis nous modifions le code, afin de charger l?image par le serveur mandataire : // création de l'objet Loader var chargeur:Loader = new Loader(); // écoute de la fin du chargement chargeur.contentLoaderInfo.addEventListener ( Event.COMPLETE, termine); Chapitre 13 ? Chargement de contenu ? version 0.1.2 70 / 84 Thibault Imbert pratiqueactionscript3.bytearray.org // image google map var requete:URLRequest = new URLRequest ( "proxy.php" ); // création d'un objet URLVariables permettant // de passer des variables au serveur mandataire var variables:URLVariables = new URLVariables(); // création de la variable chemin variables.chemin = "http://kh0.google.fr/kh?n=404&v=22&t=trtqttqqrrqrqssts"; // les variables doivent être passées par la requete HTTP requete.data = variables; // les variables sont envoyées au sein du tableau POST requete.method = URLRequestMethod.POST; // chargement de l'image chargeur.load ( requete ); // ajout à la liste d'affichage addChild ( chargeur ); function termine ( pEvt:Event ):void { var objetLoaderInfo:LoaderInfo = LoaderInfo ( pEvt.target ); // accès à l'image bitmap chargée var image:Bitmap = Bitmap ( objetLoaderInfo.content ); } Nous utilisons la classe URLVariables afin de passer l?adresse de l?image à charger au serveur mandataire. Nous reviendrons en détail sur cette classe au cours du chapitre 14 intitulé Chargement et envoi de données. Une fois publiée, si nous lançons l?application, celle-ci ne lève plus d?erreur à l?exécution lorsque nous accédons à la propriété content. Nous pouvons ainsi activer le lissage en passant la valeur booléenne true à la propriété smoothing de l?objet Bitmap chargé : function termine ( pEvt:Event ):void { var objetLoaderInfo:LoaderInfo = LoaderInfo ( pEvt.target ); // accès à l'image bitmap chargée var image:Bitmap = Bitmap ( objetLoaderInfo.content ); // activation du lissage image.smoothing = true; } La figure 13-17 illustre la différence entre l?image lissée et non lissée : Chapitre 13 ? Chargement de contenu ? version 0.1.2 71 / 84 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 13-17. Image non lissée et lissée. De la même manière, il peut être nécessaire de rendre sous forme bitmap un objet Loader contenant une image provenant d?un domaine distant : function termine ( pEvt:Event ):void { var objetLoaderInfo:LoaderInfo = LoaderInfo ( pEvt.target ); // création d'une instance de BitmapData var donneesBitmap:BitmapData = new BitmapData ( objetLoaderInfo.width, objetLoaderInfo.height ); // l'objet Loader est rendu sous forme bitmap donneesBitmap.draw ( pEvt.target.loader ); var image:Bitmap = new Bitmap ( donneesBitmap ); image.x = objetLoaderInfo.width + 5; addChild ( image ); } Ce qui génère le résultat illustré en figure 13-18 : Chapitre 13 ? Chargement de contenu ? version 0.1.2 72 / 84 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 13-18. Image dupliquée. Sans serveur mandataire, l?appel de la méthode draw aurait levé une erreur de sécurité à l?exécution. Cette technique de serveur mandataire est une solution efficace qui possède malheureusement un inconvénient. Au lieu d?être directement chargée depuis le client, l?image est d?abord chargée par le serveur puis chargée par le client. La charge serveur peut donc être plus importante et à surveiller sur un grand projet destiné à un trafic important. A retenir ? L?utilisation d?un serveur mandataire permet de charger et modifier tout type de contenu provenant d?un différent domaine. ? C?est une solution simple et efficace mais qui peut entraîner une charge serveur plus importante. Bibliothèque partagée Dans le cas de chargement d?animations, il peut être parfois utile d?extraire une classe utilisée au sein d?un SWF afin de l?utiliser au sein de l?animation procédant au chargement. Cela est rendu possible grâce au mécanisme de bibliothèque partagée à l?exécution apporté par la classe flash.system.ApplicationDomain. Dans un nouveau document Flash nous créons un symbole clip comme illustré en figure 13-19 : Chapitre 13 ? Chargement de contenu ? version 0.1.2 73 / 84 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 13-19. Symbole clip. Puis nous lions le symbole à une classe Ninja grâce au panneau de Propriétés de liaisons : Figure 13-20. Panneau de propriétés de liaison. Une fois définie, nous exportons simplement l?animation sous le nom de bibliotheque.swf. Nous allons maintenant charger ce fichier SWF et accéder dynamiquement à la classe Ninja. Dans un nouveau document Flash, nous créons un objet Loader puis nous chargeons l?animation bibliotheque.swf : // création de l'objet Loader var chargeur:Loader = new Loader(); // écoute de la fin du chargement Chapitre 13 ? Chargement de contenu ? version 0.1.2 74 / 84 Thibault Imbert pratiqueactionscript3.bytearray.org chargeur.contentLoaderInfo.addEventListener ( Event.COMPLETE, chargementTermine ); // chargement de l'animation contenant la classe Ninja chargeur.load ( new URLRequest ("bibliotheque.swf") ); function chargementTermine ( pEvt:Event ):void { var objetLoaderInfo:LoaderInfo = LoaderInfo ( pEvt.target ); // affiche : [object LoaderInfo] trace( objetLoaderInfo ); } Une fois l?animation chargée, nous pouvons accéder à toutes les classes définies au sein de celle-ci grâce à la méthode getDefinition de la classe. Celle-ci peut être appelée dès lors que l?événement Event.INIT est diffusé. Nous n?ajoutons pas volontairement l?objet Loader à a liste d?affichage car nous souhaitons simplement extraire une classe partagée. Souvenez-vous, nous avons vu précédemment que la classe LoaderInfo possède une propriété applicationDomain utilisée lors du chargement de SWF. Celle-ci référence un objet appelé Domaine d?application : function chargementTermine ( pEvt:Event ):void { var objetLoaderInfo:LoaderInfo = LoaderInfo ( pEvt.target ); // référence le domaine d'application du SWF chargé var domaineApplication:ApplicationDomain = objetLoaderInfo.applicationDomain; // affiche : [object ApplicationDomain] trace( domaineApplication ); } Le domaine d?application est un objet dans lequel sont placés toutes les définitions de classe d?un SWF. Ainsi, le domaine d?application de l?animation bibliotheque.swf contient une classe Ninja. La classe ApplicationDomain possède deux méthodes dont voici le détail : ? getDefinition : Extrait une définition de classe spécifique. ? hasDefinition : Indique si la définition de classe existe. Deux propriétés peuvent aussi être utilisées : Chapitre 13 ? Chargement de contenu ? version 0.1.2 75 / 84 Thibault Imbert pratiqueactionscript3.bytearray.org ? currentDomain : Référence le domaine d?application du SWF en cours. ? parentDomain : Référence le domaine d?application parent. Nous allons extraire la classe Ninja du domaine d?application du SWF chargé puis l?instancier et afficher le symbole au sein de l?animation procédant au chargement : function chargementTermine ( pEvt:Event ):void { var objetLoaderInfo:LoaderInfo = LoaderInfo ( pEvt.target ); // référence le domaine d'application du SWF chargé var domaineApplication:ApplicationDomain = objetLoaderInfo.applicationDomain; // extrait la définition de classe Ninja var importNinja:Class = Class ( domaineApplication.getDefinition( "Ninja" ) ); // création d'une instance de Ninja var instanceNinja:DisplayObject = new importNinja(); // ajout à la liste d'affichage addChild ( instanceNinja ); } La figure 13-21 illustre le résultat : Chapitre 13 ? Chargement de contenu ? version 0.1.2 76 / 84 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 13-21. Affichage du symbole Ninja. Si nous tentons d?extraire une classe inexistante : // tente d?extraire une définition classe nommée Nina var importNinja:Class = Class ( domaineApplication.getDefinition( "Nina" ) ); Une erreur de type ReferenceError est levée : ReferenceError: Error #1065: La variable Nina n'est pas définie. A l?aide d?un bloc, try catch nous pouvons gérer l?erreur et ainsi réagir : function chargementTermine ( pEvt:Event ):void { var objetLoaderInfo:LoaderInfo = LoaderInfo ( pEvt.target ); // référence le domaine d'application du SWF chargé var domaineApplication:ApplicationDomain = objetLoaderInfo.applicationDomain; try { // tentative d' extraction de la définition de classe var importNinja:Class = Class ( domaineApplication.getDefinition( "Nina" ) ); // création d'une instance de Ninja var instanceNinja:DisplayObject = new importNinja(); // ajout à la liste d'affichage addChild ( instanceNinja ); } catch ( pError:Error ) { trace("La définition de classe spécifiée n'est pas disponible"); } } Si l?utilisation d?un bloc try catch ne vous convient pas, l?appel de la méthode hasDefinition offre un résultat équivalent : // création de l'objet Loader var chargeur:Loader = new Loader(); // écoute de la fin du chargement chargeur.contentLoaderInfo.addEventListener ( Event.COMPLETE, chargementTermine ); // chargement de l'animation contenant la classe Ninja chargeur.load ( new URLRequest ("librairie.swf") ); Chapitre 13 ? Chargement de contenu ? version 0.1.2 77 / 84 Thibault Imbert pratiqueactionscript3.bytearray.org var definitionClasse:String = "Ninja"; function chargementTermine ( pEvt:Event ):void { var objetLoaderInfo:LoaderInfo = LoaderInfo ( pEvt.target ); // référence le domaine d'application du SWF chargé var domaineApplication:ApplicationDomain = objetLoaderInfo.applicationDomain; // vérifie si la définition de classe Ninja est disponible if ( domaineApplication.hasDefinition( definitionClasse ) ) { // tentative d' extraction de la définition de classe var importNinja:Class = Class ( domaineApplication.getDefinition( definitionClasse ) ); // création d'une instance de Ninja var instanceNinja:DisplayObject = new importNinja(); // ajout à la liste d'affichage addChild ( instanceNinja ); } else trace ("La définition de classe " + definitionClasse + " n'est pas disponible"); } L?extraction de classes est rendue possible car l?animation contenant les classes à extraire provient du même domaine que l?animation procédant au chargement. Bien entendu, le modèle de sécurité du lecteur Flash empêche par défaut l?extraction de classes entre deux SWF évoluant dans un contexte interdomaine. Dans ce cas, nous devons explicitement demander au lecteur Flash de placer le SWF chargé dans le même domaine de sécurité afin de pouvoir extraire les classes. Une première approche consiste à appeler la méthode allowDomain de la classe Security depuis le SWF dont les classes sont extraites. Security.allowDomain("monserveurDeConfiance.fr"); La seconde requiert le placement d?un fichier de régulation sur le domaine du SWF à charger, puis de passer un objet LoaderContext à la méthode load de l?objet Loader en spécifiant le domaine de sécurité en cours par la propriété securityDomain : // création de l'objet Loader var chargeur:Loader = new Loader(); // écoute de la fin du chargement chargeur.contentLoaderInfo.addEventListener ( Event.COMPLETE, chargementTermine ); Chapitre 13 ? Chargement de contenu ? version 0.1.2 78 / 84 Thibault Imbert pratiqueactionscript3.bytearray.org // création d'un objet de contexte var contexte:LoaderContext = new LoaderContext(); // nous demandons de placer le SWF chargé dans le même domaine // de sécurité afin de pouvoir extraire ses classes contexte.securityDomain = SecurityDomain.currentDomain; // chargement de l'animation contenant la classe Ninja // en spécifiant le contexte chargeur.load ( new URLRequest ("http://serveurdistant.com/swf/bibliotheque.swf"), contexte ); function chargementTermine ( pEvt:Event ):void { var objetLoaderInfo:LoaderInfo = LoaderInfo ( pEvt.target ); // référence le domaine d'application du SWF chargé var domaineApplication:ApplicationDomain = objetLoaderInfo.applicationDomain; // extrait la définition de classe Ninja var importNinja:Class = Class ( domaineApplication.getDefinition( "Ninja" ) ); // création d'une instance de Ninja var instanceNinja:DisplayObject = new importNinja(); // ajout à la liste d'affichage addChild ( instanceNinja ); } Si pour des questions de pratique, vous n?avez pas la possibilité d?appeler la méthode allowDomain de la classe Security ou de placer un fichier de régulation sur le domaine distant, l?utilisation d?un fichier serveur mandataire est ici aussi envisageable. Grâce à ce concept d?import dynamique de classes, nous pouvons imaginer toutes sortes d?applications tirant profit d?une telle fonctionnalité. Une application Flash pourrait importer, dès son initialisation un SWF contenant les définitions de classe nécessaires. Celles-ci seraient dynamiquement instanciées puis utilisées dans l?application. L?application reposerait donc entièrement sur ces classes importées dynamiquement. Afin de mettre à jour l?application, nous pourrions simplement régénérer le SWF contenant les définitions de classe. L?application pourrait être mise à jour de la même manière que des .dll dans d?autres langages comme C++ ou C#. A retenir Chapitre 13 ? Chargement de contenu ? version 0.1.2 79 / 84 Thibault Imbert pratiqueactionscript3.bytearray.org ? La méthode getDefinition de la classe ApplicationDomain permet d?extraire une définition de classe contenue dans un SWF. ? Cette extraction est soumise au modèle de sécurité du lecteur Flash. ? Afin de pouvoir extraire une classe d?un SWF distant, celui-ci doit autoriser l?animation ayant amorcé le chargement par la méthode allowDomain de la classe Security ou la création d?un fichier de régulation. Désactiver une animation chargée Très souvent, un site Flash est constitué d?une application principale chargeant différents modules, séparés en plusieurs SWF. Chacun d?entre eux est ensuite chargé afin de naviguer dans le site. Le fonctionnement de la classe Loader nous réserve encore quelques surprises. En réalité, la méthode unload vide le contenu chargé mais ne le désactive pas. Cela diffère du traditionnel loadMovie utilisé en ActionScript 1 et 2, qui remplaçait le contenu précédemment chargé en désactivant tous les objets contenus. En ActionScript 3, lorsque la méthode unload est exécutée, le contenu chargé est simplement supprimé de la liste d?affichage. La seule référence à l?animation est celle que possède l?objet Loader. Si nous supprimons son contenu il n?existe alors plus aucune référence vers l?animation. Celle-ci va donc demeurer et vivre en mémoire jusqu?à ce que le ramasse miettes intervienne et la supprime définitivement. Ainsi, au chargement d?une nouvelle rubrique, le son de la précédente continuerait de jouer. De la même manière, tous les événements souscrits continueraient d?être diffusés. Il faut donc prévoir obligatoirement un mécanisme de désactivation comme nous l?avons fait jusqu?à présent pour les objets d?affichage. Afin de correctement désactiver une animation, nous utilisons l?événement Event.UNLOAD diffusé par l?objet LoaderInfo associé au SWF en cours. Le code suivant doit donc être placé au sein du SWF à désactiver : // écoute la suppression de l'animation loaderInfo.addEventListener ( Event.UNLOAD, desactivation ); function desactivation ( pEvt:Event ):void { // logique de désactivation de l'animation // désactivation des événements, du son, objets videos etc Chapitre 13 ? Chargement de contenu ? version 0.1.2 80 / 84 Thibault Imbert pratiqueactionscript3.bytearray.org } Lorsque la méthode unload est exécutée, ou qu?une nouvelle animation est chargée, l?événement Event.UNLOAD est diffusé au sein de l?animation chargée. Nous intégrons au sein de la fonction écouteur desactivation la logique nécessaire afin de désactiver totalement l?animation en cours. Dans le code suivant, nous stoppons le son en cours de lecture : // création d'un objet Sound var monSon:Sound = new Sound(); // chargement du son monSon.load ( new URLRequest ("son.mp3") ); // création d'un objet SoundChannel par l'appel de la méthode Sound.play() var canalAudio:SoundChannel = monSon.play(); // écoute la suppression de l'animation loaderInfo.addEventListener ( Event.UNLOAD, desactivation ); function desactivation ( pEvt:Event ):void { // logique de désactivation de l'animation // désactivation des événements, du son, objets videos etc canalAudio.stop(); } L?animation chargée étant supprimée de la liste d?affichage, l?écoute de l?événement Event.REMOVED_FROM_STAGE est aussi envisageable : // création d'un objet Sound var monSon:Sound = new Sound (); // chargement du son monSon.load ( new URLRequest ("son.mp3") ); // création d'un objet SoundChannel par l'appel de la méthode Sound.play() var canalAudio:SoundChannel = monSon.play(); // écoute la suppression de l'animation addEventListener ( Event.REMOVED_FROM_STAGE, desactivation ); function desactivation ( pEvt:Event ):void { // logique de désactivation de l'animation // désactivation des événements, du son, objets videos etc canalAudio.stop(); } Chapitre 13 ? Chargement de contenu ? version 0.1.2 81 / 84 Thibault Imbert pratiqueactionscript3.bytearray.org Ce comportement peut poser de graves problèmes lorsque vous n?êtes pas l?auteur du contenu chargé. Vous êtes donc dans l?incapacité d?intégrer un mécanisme de désactivation. Il n?existe aujourd?hui aucune solution viable permettant de désactiver automatiquement un contenu tiers. A retenir ? Lorsque la méthode unload est appelée, le contenu est supprimé de la liste d?affichage mais n?est pas désactivé. ? Il est impératif de prévoir un mécanisme de désactivation au sein des animations chargées. ? Il n?est pas possible de désactiver automatiquement un contenu tiers. Communication AVM1 et AVM2 La machine virtuelle ActionScript 3 (AVM2) offre la possibilité de lire des animations développées en ActionScript 1 et 2 (AVM1). Celles-ci sont alors considérées comme des objets de type flash.display.AVM1Movie. Dans le cas d?un portail de jeux vidéo développé en ActionScript 3, la majorité des jeux chargés seront d?ancienne génération, développés en ActionScript 1 ou 2. Malheureusement, l?échange entre les deux animations n?est pas simplifié. Si nous tentons d?accéder au contenu de ces derniers, le lecteur lève une erreur indiquant que l?accès est impossible. Il faut considérer un objet AVM1Movie comme un objet hermétique ne pouvant être pénétré. Afin de mettre en évidence ce comportement, nous allons créer une animation ActionScript 1 ou 2 et y intégrer une simple animation. L?animation est représentée par une instance du symbole Garcon utilisé lors du chapitre précédent. Nous lui donnons animation comme nom d?occurrence. Chapitre 13 ? Chargement de contenu ? version 0.1.2 82 / 84 Thibault Imbert pratiqueactionscript3.bytearray.org Figure 13-22. Instance du symbole Garcon. Nous allons depuis l?animation ActionScript 3, communiquer avec le contenu l?animation d?ancienne génération pour stopper l?animation du clip animation. Dans le code suivant, nous chargeons l?animation d?ancienne génération : var chargeur:Loader = new Loader(); chargeur.contentLoaderInfo.addEventListener ( Event.COMPLETE, chargementTermine ); chargeur.load ( new URLRequest ("anim-vm1.swf" )); addChild ( chargeur ); function chargementTermine ( pEvt:Event ):void { var objetLoaderInfo:LoaderInfo = LoaderInfo ( pEvt.target ); // affiche : [object AVM1Movie] trace( objetLoaderInfo.content ); } Nous remarquons que la propriété content de l?objet LoaderInfo nous renvoie un objet de type AVM1Movie. Si nous tentons de pénétrer à l?intérieur de l?animation : function chargementTermine ( pEvt:Event ):void { var objetLoaderInfo:LoaderInfo = LoaderInfo ( pEvt.target ); Chapitre 13 ? Chargement de contenu ? version 0.1.2 83 / 84 Thibault Imbert pratiqueactionscript3.bytearray.org var contenu:DisplayObject = objetLoaderInfo.content; if ( contenu is AVM1Movie ) { var animationVM1:AVM1Movie = AVM1Movie ( contenu ); // tentative d'accès à l'occurrence animation trace( animationVM1.animation ); } } L?erreur à la compilation suivante est générée : 1119: Accès à la propriété animation peut-être non définie, via la référence de type static flash.display:AVM1Movie. Afin d?accéder au contenu de l?animation chargée, nous devons passer par un moyen détourné. Deux classes vont nous permettre de communiquer : ? flash.net.LocalConnection : la classe LocalConnection permet d?échanger des données entre différents SWF distincts. ? flash.external.ExternalInterface : la classe ExternalInterface permet la communication entre le code ActionScript et la page contenant le lecteur Flash. Nous allons utiliser pour cet exemple la classe LocalConnection qui s?avère être la solution la plus souple, en ne nécessitant pas de code JavaScript contrairement à la classe ExternalInterface. Nous commençons par créer une instance de la classe LocalConnection dans l?animation dans laquelle nous souhaitons accéder : // création de l?objet récepteur var recepteur:LocalConnection = new LocalConnection(); // connexion au canal utilisé par l?émetteur recepteur.connect ("canalCommunication"); // définition de la méthode appelée par l?émetteur recepteur.stopAnimation = function ( ) { animation.stop(); } Puis au sein de l?animation souhaitant initier la communication, nous ajoutons une nouvelle instance de la classe LocalConnection afin d?émettre les messages : Chapitre 13 ? Chargement de contenu ? version 0.1.2 84 / 84 Thibault Imbert pratiqueactionscript3.bytearray.org var chargeur:Loader = new Loader(); chargeur.load ( new URLRequest ("anim-vm1.swf" )); addChild ( chargeur ); // création de l?objet émetteur var emetteur:LocalConnection = new LocalConnection(); boutonStop.addEventListener ( MouseEvent.CLICK, clicBouton ); function clicBouton ( pEvt:MouseEvent ):void { // émission d?un message pour exécuter la méthode stopAnimation par le canal canalCommunication emetteur.send ("canalCommunication ", "stopAnimation"); } Lorsque nous cliquons sur le bouton boutonStop, un message est envoyé à l?animation chargée par l?appel de la méthode send de l?objet LocalConnection. A retenir ? La communication entre deux animations AVM1 et AVM2 est possible par l?intermédiaire de la classe LocalConnection ou ExternalInterface. Nous savons désormais comment charger du contenu graphique au sein du lecteur Flash. Passons maintenant au chargement et à l?envoi de données en ActionScript 3.

PARTAGER SUR

Envoyer le lien par email
2338
READS
6
DOWN
7
FOLLOW
5
EMBED
DOCUMENT # TAGS
#flash  #action script  #développement flash 

licence non indiquée


DOCUMENT # INDEX
Divers 
img

Partagé par  mistigri

 Suivre

Auteur:
Source:Non communiquée