Forums Développement Multimédia

Les formations Mediabox
Les formations Mediabox

Lier des informations à des Clips (ou d'autres objets)

Compatible ActionScript 3. Cliquer pour en savoir plus sur les compatibilités.Compatible Flash CS3. Cliquer pour en savoir plus sur les compatibilités.Compatible Flash CS4. Cliquer pour en savoir plus sur les compatibilités.Compatible Flash CS5. Cliquer pour en savoir plus sur les compatibilités.Par billyben (Billyben), le 20 juillet 2011

Bonjour! Il est très courant de vouloir lier des informations à des objets, et notamment à des objets avec lesquels l'utilisateur peut interagir. Il existe de nombreuses manières de résoudre ce problème, et tout dépend des objets et de votre projet (notamment de la version du lecteur). Nous allons donc voir quelques façons d'y parvenir.

Prérequis : Niveau débutant

dans tout les cas : Pratique d'actionScript 3, Thibault IMBERT

La problématique

Que va-t-on traiter ?

Couramment, nous avons besoin de conserver et de retrouver différentes informations (à prendre au sens large) correspondant à un objet précis. Par exemple, vous chargez un fichier xml dont les nœuds contiennent l'url d'une image, avec un titre, un texte informatif sur l'image et une adresse d'une page vers laquelle il faut diriger votre visiteur au clic sur cette image. Dans un autre cas, ces informations proviennent d'une base de données. Bref, vous pouvez imaginer beaucoup de situations où vous devez créer un lien entre des informations et un objet. Or, comment retrouver ces informations à partir du clip (de la miniature ?) que vous affichez ?

L'exemple choisi

Je ne vais pas traiter ici de toute la partie “récupération” de ces informations (le chargement d'un xml, la consultation d'une base de données, etc.). En revanche, je vais simuler un traitement similaire avec un code très simple et quelques objets.
Dans la majorité des cas, il est de bon ton de passer par une boucle pour parcourir les données issues du chargement que l'on a effectué. Souvent, dans cette boucle on crée des objets graphiques, ainsi que des objets qui contiennent les informations. Ce peut être une chaine de caractères (String) pour une url, un texte ou autre, ou encore un nombre (Number, int, etc.) pour des positions ou toute autre chose.

Pour simuler ces comportements, je vais passer par 2 tableaux (Array) qui vont contenir 3 clips créés au préalable et 3 objets de type “Object” qui vont correspondre aux informations. Les 2 tableaux sont à voir ici comme source de données, et remplacent les résultats obtenus dans des URLLoader par exemple. J'utilise ici des objets “Object” comme conteneurs de l'information, car dans notre langage préféré tout est Objet, ce qui sous entend que vous pourrez mettre n'importe quoi en lieu et place de ces objets “Object”.

La simulation

Dans le 1er tableau nous avons les 3 occurrences de clip que j'ai posées sur la scène, nommées “b1”, “b2” et “b3” :

var boutonArray:Array=[b1, b2, b3];

Puis le second tableau contient trois objets “Object” d'information, qui possèdent chacun deux propriétés : nom et valeur.

var infoArray:Array=[{nom:"bouton 1", valeur:0},
		     {nom:"bouton 2", valeur:5},
		     {nom:"bouton 3", valeur:10}]

Ces 2 tableaux vont nous permettre de simuler un parcours de données, issues d'un chargement externe par exemple. La boucle aura deux rôles dans les exemples ci-dessous :

  • Créer le lien entre les 3 clips et les objets d'information.
  • Ajouter un écouteur sur l'action souris “CLICK” pour chacun des trois clip.

Ceci se résume par :

//on parcours le tableau des clips
for (var i:int=0; i<boutonArray.length; i++){
	//Lien CLIP/Information – différentes méthodes vu plus loin
            // Ajout des écouteurs.
	boutonArray[i].addEventListener(MouseEvent.CLICK, clicHandler);
}

Note : on peut s'arranger pour n'avoir qu'un seul écouteur du clic sur le conteneur des boutons (ici la scène), mais c'est un autre sujet.

Dans la fonction d'écoute du clic sur les clips, nous allons :

  • Retrouver l'objet d'information correspondant au clip cliqué (ici ev.currentTarget), que nous référencerons dans un objet Object “info”.
  • Afficher ces informations dans un champ texte TextField que j'ai nommé : “monTextField”

Ceci peut se résumer par :

function clicHandler(ev:MouseEvent):void{
	var info:Object=??// Selon les différentes méthodes vues plus loin
	monTextField.text="je suis "+info.nom+" de valeur :"+info.valeur;
}

Bref, ceci nous mènera à :

L"extension Adobe Flash Plugin est nécessaire pour afficher ce contenu.

Dans la suite du tuto, je ne reporterai plus les 2 tableaux dans le code, car ils ne sont là que comme source d'information.

Approche classique

Si vous utilisez un objet qui hérite de MovieClip

Créer un symbole en choisissant clip dans la liste “type” revient à créer un objet de type MovieClip, c'est donc quelque chose de relativement courant.

La classe MovieClip a la particularité d'être une classe dynamique (et il n'y en a pas beaucoup), ce qui signifie que l'on peut lui ajouter des propriétés à la volée. C'est-à-dire :

monMovieClip.maProp="maValeur";
trace (monMovieClip.maProp);

sort

"maValeur"

Alors que 'maProp' n'est pas une propriété originelle de la classe MovieClip.

le code

Ici, j'ai donc créé un symbole de type “clip”, et j'en ai posé trois instances 3 sur la scène. Nous allons donc tirer parti de cette particularité, en modifiant la boucle et la fonction d'écoute que nous avons vues précédemment :

for (var i:int=0; i<boutonArray.length; i++){
	boutonArray[i].info=infoArray[i];// on créé et renseigne une propriété sur chaque clip
	boutonArray[i].addEventListener(MouseEvent.CLICK, clicHandler);
}
function clicHandler(ev:MouseEvent):void{
	var info:Object=ev.currentTarget.info;//on va récupérer la valeur de la propriété renseigné au dessus
	monTextField.text="je suis "+info.nom+" de valeur :"+info.valeur;
}

le résultat

L"extension Adobe Flash Plugin est nécessaire pour afficher ce contenu.

Sources : movieclip.fla

Si vous utilisez un objet qui n'hérite pas de MovieClip

Ici je regroupe des objets “graphiques” qui ne sont pas dynamiques, auxquels ne peut donc pas ajouter de propriétés à la volée, ce qui vous amènerait, avec le code précédent, à une belle erreur #1056 :

Error #1056: Impossible de créer la propriété info sur XXX.

Le Fla

J'ai créé un symbole de type Bouton, (un objets de type “SimpleBouton”) qui n'est donc pas issu d'une classe dynamique.

J'en ai posé trois instances sur la scène, avec pour nom “b1”, “b2” et “b3”. Je vais vous présenter plusieurs codes permettant d'arriver au même résultat.

les sources

les sources du chapitre : simplebutton_test.zip

Utilisation de test conditionnels

Une première possibilité est l'utilisation de tests dans la fonction d'écoute. On teste une des propriétés de l'objet qui émet l'événement et on va chercher la bonne info. Ici je teste dans un switch (un équivalent de if/else if/ enchainés) le nom de l'objet cliqué par sa propriété monClip.name, et je vais chercher la bonne info dans le tableau correspondant.

Ici, le principe réside dans le fait que je connaisse la position des objets d'information dans le tableau correspondant aux noms de clips.

for (var i:int=0; i<boutonArray.length; i++){
              // ici on ne lie pas les objet d'info au clip…..
	boutonArray[i].addEventListener(MouseEvent.CLICK, clicHandler);
}
 
function clicHandler(ev:MouseEvent):void{
	var info:Object;
	switch (ev.currentTarget.name){
		case "b1" :
		 	info=infoArray[0];// b1 correspond à la 1ere valeur dans infoArray
		break;
		case "b2" :
			info=infoArray[1];// b2 correspond à la 2ième valeur dans infoArray
		break;
		case "b3" :
			info=infoArray[2];// b3 correspond à la 3ième valeur dans infoArray
		break;
	}	
	monTextField.text="je suis "+info.nom+" de valeur :"+info.valeur;
}

C'est de loin la méthode que j'aime le moins, ça n'est pas souple pour un caillou, mais ça peut dépanner se voit.

Utilisation de 2 tableaux

Nous pouvons faire correspondre les clips avec les objets d'information par le biais de 2 tableaux.

L'important ici est que les objets d'information soient référencés dans le même ordre que les clips dans leurs tableaux respectifs.

var clipArray:Array=new Array();// pour réferencer les clips
var objetInfoArray:Array=new Array();// pour référencer  les objet d'information
for (var i:int=0; i<boutonArray.length; i++){
	clipArray.push(boutonArray[i]);// on réference les clips
	objetInfoArray.push(infoArray[i]);// on référence les objets d'information
	boutonArray[i].addEventListener(MouseEvent.CLICK, clicHandler);
}
 
function clicHandler(ev:MouseEvent):void{
	var btnId:int= clipArray.indexOf(ev.currentTarget);
	var info:Object= objetInfoArray [btnId];	
	monTextField.text="je suis "+info.nom+" de valeur :"+info.valeur;
}


Donc on récupère la position du clip dans son tableau par “clipArray.indexOf(ev.currentTarget);” et l'on va chercher son correspondant dans le tableau des informations ” objetInfoArray” par “objetInfoArray [btnId];”.

Vous noterez ici que les deux tableaux “clipArray” et “objetInfoArray” correspondent exactement aux deux tableaux qui nous servent de source d'information : “boutonArray” et ” infoArray”. Nous aurions donc pu les utiliser, mais nous voyons ici comment lier les objets d'information et les clips dans la boucle.

utilisation d'un seul tableau

Nous pouvons également n'utiliser qu'un seul tableau. Le principe ici est que la clé d'entrée de ce tableau (le nom d'une entrée dans le tableau) est une donnée issue d'un clip et que la valeur correspondante correspond à un objet d'information. Pour l'exemple, je vais utiliser un tableau associatif, dont les clés seront des chaines de caractères (String), ici le nom des boutons.

var linkArray:Array=[];// le tableau
for (var i:int=0; i<boutonArray.length; i++){
	linkArray[boutonArray[i].name]=infoArray[i];
	boutonArray[i].addEventListener(MouseEvent.CLICK, clicHandler);
}
 
function clicHandler(ev:MouseEvent):void{
	var info:Object=linkArray[ev.currentTarget.name];	
	monTextField.text="je suis "+info.nom+" de valeur :"+info.valeur;
}
  • linkArray[boutonArray[i].name]=infoArray[i] : permet d'entrer une nouvelle valeur dans le tableau. On utilise le nom du bouton comme entrée, et l'objet d'information comme valeur.
  • linkArray[ev.currentTarget.name] : permet de récupérer cette dernière grâce au nom du bouton cliqué.


L'utilisation d'un tableau associatif semblerait être dépréciée, nous pouvons remplacer le tableau “linkArray” par un objet de type “Object”. Il suffit simplement de remplacer dans le code précédent:

var linkArray:Array=[];

par

var linkArray: Object=new Object();


en effet, la ligne :

linkArray[boutonArray[i].name]=infoArray[i];

est équivalente dans le code à :

linkArray["b1"]=infoArray[1];

peut se traduire dans la syntaxe à points par (successivement) pour les Object :

linkArray.b1=infoArray[1];

Utilisation d'un Dictionary

Le Dictionary est un objet très utile apparu avec la version 9 du lecteur, qui permet de lier directement deux objets quels qu'ils soient.

qu'est ce qu'un Dictionary

La définition de la doc :

La classe Dictionary vous permet de créer un ensemble dynamique de propriétés, qui utilise l’opérateur d’égalité stricte === pour comparer les clés. Lorsqu’un objet sert de clé, son identité est utilisée pour le rechercher, plutôt que la valeur renvoyée par l’appel de toString() sur son entrée.
Doc Adobe (Dictionary)

Peut être obscure, mais une fois vu c'est simple à souhait. En gros, l'objet Dictionary ressemble beaucoup à un tableau, si ce n'est que les clés d'entrée (la valeur qui nous permet d'aller piocher dedans) peuvent être n'importe quel objet et non plus un nombre (de type uint) ou une chaine de caractères uniquement. Le constructeur des objets Dictionary est tel que :

Dictionary(weakKeys:Boolean = false)

Le Paramètre “weakKeys” valorisé à true permet de signifier la référence des objets contenus comme “faible”. Une référence faible permet (à l'inverse d'une référence forte) à l'objet référencé d'être éligible au ramasse miettes (garbage collector) qui est en charge de nettoyer la mémoire des objets qui ne sont plus utilisés. Personnellement, je passe toujours ce paramètre à true.

utilisation dans notre cas

Au dessus nous avons utilisé le nom des boutons comme clé du tableau ( linkArray[boutonArray[i].name] ). Ici nous allons directement utiliser le clip lui-même! Donc le code :

var dico:Dictionary=new Dictionary(true);
 
for (var i:int=0; i<boutonArray.length; i++){
	dico[boutonArray[i]]=infoArray[i];
	boutonArray[i].addEventListener(MouseEvent.CLICK, clicHandler);
}
 
function clicHandler(ev:MouseEvent):void{
	var info:Object=dico[ev.currentTarget];	
	monTextField.text="je suis "+info.nom+" de valeur :"+info.valeur;
}

L'emploi du Dictionary ici est plus puissant que celle d'un tableau (ou d'un objet) car il permet de discriminer tout les objets qui servent de clé d'entrée. Par exemple, si vous aviez deux clip qui portent le même nom, avec un tableau vous auriez une seule entrée, avec le Dictionary vous en avez deux.

L'approche POO

Le langage avec lequel nous nous amusons est un langage dit “orienté objet”, et donc nous allons en tirer parti.
Cela peut paraitre rebutant au début, mais une fois pris en main on a du mal à faire autrement (et c'est tellement plus pratique !).

Nous allons voir deux approches différentes, dans l'une nous allons lier nos objets boutons à une classe capable de contenir les informations, et dans la seconde nous allons construire une classe qui va uniquement se charger de créer le lien entre un bouton et une info.

Lier une classe à notre clip

Ici, le principe va être de créer de nouvelles propriétés à nos clips, donc contourner le fait que nous n'avons pas une classe dynamique.
Nous allons donc lier le symbole de bouton dans notre bibliothèque à une classe externe. Pour plus de renseignements sur les classes, je vous laisse le soin de consulter ce tuto, par exemple, je ne rentrerai pas dans le détail.

la classe

La classe va être écrite dans un fichier externe, qui a pour extension ”.as”, et pour laquelle nous allons créer deux propriétés supplémentaires : “nom” et “valeur” pour coller aux infos définies précédemment. On pourrait directement référencer l'objet “Object” qui contient les infos, mais par la suite nous verrons un court exemple où nous manipulerons celle-ci dans la classe, histoire de montrer un petit bout de la puissance de cette approche.

Notre classe va étendre “SimpleButton” car je souhaite conserver les boutons que nous avons définis dans le chapitre précédent. Mais libre à vous d'étendre d'autres classes telles que Sprite (et au minimum DisplayObject puisque ce sont des objets d'affichage), mais vous perdrez la prise en charge des états repos/survol/cliqué des boutons….

Donc ici un exemple basique, deux variables déclarées en public, donc accessibles directement depuis l'extérieur de la classe. On peut donc les renseigner depuis notre fla, de même que les lire.

La classe :

package  
{
	import flash.display.SimpleButton;
 
	public class MonBouton extends SimpleButton 
	{
		public var nom:String;
		public var valeur:int;
 
		public function MonBouton(){}
 
	}
}


A enregistrer dans “MonBouton.as” à coté de votre fla.

Alors, ça ne mord pas non ?
÷÷ source : monbouton.as

lier la classe

Maintenant nous allons lier cette classe à l'objet dans notre bibliothèque. Pour ce faire :

  • clic droit sur celui-ci dans la bibliothèque sur “propriétés”
  • cocher la case “exporter pour Actionscript”
  • “Classe” : vous mettez ce que vous voulez ou laissez ce qu'il y a par défaut. C'est ce nom que vous pourrez utiliser pour instancier par le code de nouveaux objets de ce type.
  • “Classe de base” : vous mettez “MonBouton”. C'est ici que nous lions notre classe à notre objet dans la bibliothèque.
  • “Ok”, et le tour est joué!!

le code dans le fla

Ici, nous allons renseigner les propriétés “nom” et “valeur” de nos clips sur la scène qui héritent désormais du type “MonBouton” :

for (var i:int=0; i<boutonArray.length; i++){
	var bouton: MonBouton =boutonArray[i] as MonBouton;	
	bouton.nom=infoArray[i].nom;
	bouton.valeur=infoArray[i].valeur;
 
	bouton.addEventListener(MouseEvent.CLICK, clicHandler);
}
 
function clicHandler(ev:MouseEvent):void{
	var bouton: MonBouton =ev.currentTarget as MonBouton;	
	monTextField.text="je suis "+bouton.nom+" de valeur :"+bouton.valeur;
}


J'ai choisi de passer par un objet (var bouton:MonBouton) que je force à être du type “MonBouton”, ici ce n'est pas nécessaire, mais ça le sera souvent dans des projets plus complexes.

source : poo_classe_liee.fla

(la classe est au dessus)

exemple de travail dans la classe

Pour l'exemple, nous allons rajouter une fonction à notre classe, qui va nous permettre d'obtenir directement la chaine de caractères à afficher dans le TextField. Pour ce faire la classe devient :

package  
{
	import flash.display.SimpleButton;
 
	public class MonBouton extends SimpleButton 
	{
		public var nom:String;
		public var valeur:int;
 
		public function MonBouton() { }
 
		public function getInfoString():String {
			return "je suis "+nom+" de valeur :"+valeur+" dont la moitié est "+String(valeur/2);
		}		
	}
}

La fonction getInfoString() retourne la chaine à afficher, dans le même format que précédemment, avec une petite info en plus, la moitié de la valeur (vraiment histoire de dire…).
La fonction d'écoute devient alors :

function clicHandler(ev:MouseEvent):void{
	var bouton: MonBouton =ev.currentTarget as MonBouton;	
	monTextField.text=bouton.getInfoString();
}

le résultat

L"extension Adobe Flash Plugin est nécessaire pour afficher ce contenu.

Utiliser une classe qui va gérer le lien Bouton/info

Ici, nous allons dissocier les infos - abstraites - des objets d'affichage (les boutons), et créer un objet qui va lier les infos à ces derniers. L'exemple ne sera peut être pas du plus pertinent, mais il montre simplement une autre possibilité.

Donc nous n'allons plus lier les boutons à une classe perso, on les conserve tels quel, mais nous allons créer une petite classe qui va conserver le lien info/bouton. Pour l'occasion, nous allons également créer une fonction similaire à “getInfoString()” précédente.

Le principe repose sur la même mécanique que ce que nous avons vu au premier chapitre, ici j'utiliserai un Dictionary.
Donc notre classe doit :

  • Ajouter un lien entre le bouton et les infos
  • Supprimer ce lien (on n'en aura pas besoin ici, mais c'est toujours utile)
  • Retrouver les infos à partir du bouton
  • Retourner la chaine à afficher dans le TextField (juste pour l'exemple)

Allons-y :
A enregistrer dans un fichier “LinkManager.as” à coté de votre fla :

package  
{
	import flash.utils.Dictionary;
 
	public class LinkManager 
	{
		private var dico:Dictionary;
 
		public function LinkManager() {
			dico = new Dictionary(true);
		}
// pour ajouter un lien
		public function addLink(bouton:Object, info:Object):void {
			dico[bouton] = info;
		}
// pour supprimer un lien
		public function removeLink(bouton:Object):void {
			delete(dico[bouton]);
		}
// pour retrouver l'objet d'info à partir du bouton
		public function getInfo(bouton:Object):Object {
			return dico[bouton];
		}
// pour retrouver  la chaine à afficher à partir du bouton
		public function getInfoString(bouton:Object):String {
			var info:Object = getInfo(bouton);			
			return "je suis "+info.nom+" de valeur :"+info.valeur+" dont la moitié est "+String(info.valeur/2);
		}	
	}
}

Voilà, on utilise juste une occurrence de Dictionary pour créer le lien et on l'exploite. Pour ajouter un lien, on utilise la fonction/méthode : addLink qui prend 2 arguments :

  • Le bouton
  • L'objet qui contient les infos

Pour retrouver les infos on utilise la fonction : getInfo qui prend pour argument le bouton.

Vous noterez que dans la fonction “getInfoString” (qui prend en argument le bouton) on utilise la méthode ”getInfo”, puisque nous l'avons écrite, mais surtout au cas où celle-ci traiterait au préalable les infos.

Ici l'utilisation de cette classe n'est pas vraiment justifié, mais elle pourra se révéler fort utile avec un projet plus complexe.

Pour utiliser cette classe :

// on instancie un nouvel objet "LinkManager"
var linkManager:LinkManager=new LinkManager();
for (var i:int=0; i<boutonArray.length; i++){
	// on ajouter les liens dans la classe
	linkManager.addLink(boutonArray[i], infoArray[i])	
	boutonArray[i].addEventListener(MouseEvent.CLICK, clicHandler);
}
 
function clicHandler(ev:MouseEvent):void{
	// pour retrouver l'objet des info
	var info:Object=linkManager.getInfo(ev.currentTarget);
	monTextField.text="je suis "+info.nom+" de valeur :"+info.valeur;
}

Ou encore

function clicHandler(ev:MouseEvent):void{
        // pour retrouver directement la chaine à afficher
	monTextField.text=linkManager.getInfoString(ev.currentTarget);
}

sources : linkmanager_test.zip
Et voilà !!!!

La meilleurs méthode


Eh bien, comme souvent en programmation, il n'existe pas de méthode miracle, tout dépend du projet, du temps, du niveau du développeur et de l'humeur de ce dernier…
Toutefois, pour aller vite, ma préférence va à la 1ère méthode “POO”. C'est une méthode simple, rapide à mettre en place (cinq lignes de code tout même !!!), facile d'entretien, puissante, et si l'on a besoin de traiter les informations à conserver ce peut être fait facilement avec une fonction type “setter” avant de l'enregistrer, ou un getter pour la retrouver. En outre, on peut facilement étendre cette classe, et donc créer des objets qui héritent de cette dernière et leur ajouter d'autres propriétés au besoin.

Sinon, je pense que la méthode utilisant un Dictionary supplante les autres dans le chapitre “classique”, c'est vraiment un objet qui est fait pour ça ! Mais il faut coder pour la version 9 du lecteur au minimum…

Le mot de la fin

Le principe est donc là, toutefois, pour faire bien, il faudrait ajouter des vérifications pour éviter des erreurs (que les objets existent bien, que les fonctions ne renvoient pas null, etc…). A vos claviers !

Comme vous l'avez vu, j'ai utilisé des objets de type “Object” pour référencer les informations à lier aux clips. Ceci pour dire que vous pouvez utiliser n'importe quel type d'objet qui peut contenir des informations telles que “XML”, “XMLList”, “DataProvider” ou même une classe personnalisée qui se charge de gérer l'information (et pourquoi pas conjointement à une classe qui gère ces conteneurs d'information), vous pouvez même lier un autre clip !!! Bref à vous de jouer !

Toutes les sources : lier_info_bouton_sources.zip