vendredi 27.02.2009, 13:30
Symboles liés et LoaderContext
Par Cédric Tabin - ActionScript - Lien permanent
Actuellement sur un gros projet, je me suis vu confronté à un souci du aux symboles graphiques héritant d'une classe et de leur utilisation dans un autre SWF. Et c'est la que survient l'erreur #1056 (ReferenceError) qui n'a (presque) aucun rapport avec la véritable source du problème...
Il est généralement facile de trouver une solution à l'erreur 1056. Le fait est que dans mon cas, cela n'a rien à voir avec une question de variable publique/privée...
Pour illustrer cela, voici les fichiers que j'ai créé :
- une classe MySymbol.as héritant de MovieClip
- un FLA library.fla qui va contenir un clip lié à MySymbol (un clip avec un TextField nommé 'fieldContent')
- un FLA loader.fla s'occupant de charger library et de créer une instance de MySymbol
Ci-dessous, le code de la classe, tout ce qu'il y a de plus basique :
package { import flash.display.*; import flash.text.*; public class MySymbol extends MovieClip { public function MySymbol():void { trace("MySymbol created"); } } }
Le fichier library.fla ne contient que le MovieClip lié à MySymbol. Et le fichier loader.fla a le code suivant en première frame :
var ldr:Loader = new Loader(); ldr.contentLoaderInfo.addEventListener(Event.COMPLETE, onComplete); var frt:URLRequest = new URLRequest("library.swf"); var ctx:LoaderContext = new LoaderContext(false, ApplicationDomain.currentDomain); ldr.load(frt, ctx); function onComplete(evt:Event):void { var f:MySymbol = new MySymbol(); addChild(f); }
A noter qu'il faut charger le fichier library.swf dans le contexte courant afin de pouvoir utiliser le MovieClip lié à MySymbol par la suite dans d'autres fichiers chargés. Mais ici, cela n'est (pour l'instant) pas très important.
Il suffit d'exécuter le fichier loader.fla pour se retrouver avec cette erreur :
ReferenceError: Error #1056: Cannot create property fieldContent on MySymbol. at flash.display::Sprite/constructChildren() at flash.display::Sprite() at flash.display::MovieClip() at MySymbol() at loader_fla::MainTimeline/onComplete()
Visiblement, flash n'arrive pas à créer le TextField contenu dans le clip MySymbol... ce qui ne veut absolument rien dire, car au premier abord il n'y a aucun conflit de nom ou de variable privée pouvant provoquer cela.
En fait, ce qui se passe est un bête problème de compilation de classe : en faisant un new MySymbol(), flash va compiler la classe MySymbol dans loader.fla (et qui n'a donc aucun clip lié). Ensuite, lorsque library.swf est chargé, tout d'un coup un symbole est lié à cette classe ce qui fait que flash s'emmêle complètement les pinceaux...
Pour contourner cela, il est possible d'utiliser le code suivant pour éviter que MySymbol ne soit compilé :
var ldr:Loader = new Loader(); ldr.contentLoaderInfo.addEventListener(Event.COMPLETE, onComplete); var frt:URLRequest = new URLRequest("library.swf"); var ctx:LoaderContext = new LoaderContext(false, ApplicationDomain.currentDomain); ldr.load(frt, ctx); function onComplete(evt:Event):void { var mySymbol:Class = getDefinitionByName("MySymbol") as Class addChild(new mySymbol()); }
Dans cet exemple, l'utilisation d'un LoaderContext spécifique prend toute son importance ! Si celui-ci n'est pas utilisé, la classe MySymbol ne sera pas trouvée au runtime et il en résultera une erreur de ce type :
ReferenceError: Error #1065: Variable MySymbol is not defined. at global/flash.utils::getDefinitionByName() at loader_fla::MainTimeline/onComplete()
Le dernier souci est que dans le code ci-dessus, il n'est plus possible d'utiliser le typage fort pour créer une instance de MySymbol. Pour solutionner ce dernier point, il suffit de créer un troisième fichier qui lui va s'occuper de ça ! Au final, le code dans loader.fla est le suivant :
var ldr:Loader = new Loader(); ldr.contentLoaderInfo.addEventListener(Event.COMPLETE, onComplete); var frt:URLRequest = new URLRequest("library.swf"); var ctx:LoaderContext = new LoaderContext(false, ApplicationDomain.currentDomain); ldr.load(frt, ctx); function onComplete(evt:Event):void { var usr:Loader = new Loader(); usr.load(new URLRequest("libraryUser.swf")); addChild(usr); }
et le fichier libraryUser.fla contient simplement :
var f:MySymbol = new MySymbol(); addChild(f);
Il est intéressant de noter qu'il n'est pas nécessaire de spécifier un LoaderContext pour libraryUser.swf car celui-ci va automatiquement être chargé das un ApplicationDomain enfant de loader.swf, ce qui lui permet d'utiliser le clip lié à la classe MySymbol !
En conclusion, lors d'utilisation de symboles liés au travers de différents fichiers il est toujours préférable d'avoir un fichier parent qui va s'occuper de charger les différentes librairies dans le bon contexte. Pour ceux qui utilisent Masapi, c'est un mécanisme qui est automatiquement intégré lors du chargement de fichiers SWF.
Pour ceux qu veulent approfondir le sujet, j'ai mis en annexe les fichiers servant à l'exemple ci-dessus !
9 commentaires
Salut!
Merci du tuyau, effectivement, j'ai moi-même du faire face à ce genre de problème. Par contre, je me demande s'il est bien nécessaire de passer par un 3ème swf...
Ca fait faire un appel http en plus et c'est ptet un appel de trop quand on bosse sur des sites où le moindre ko supplémentaire coûte cher (très cher...) '^^
Pour le coup, dans un de mes projets, j'ai réussi à contourner le problème d'une autre manière.
En fait il m'a suffit de passer le domaine d'application à ma classe chargée (désolé mais c'est pas facile à expliquer comme ça...)
Exemple:
1- je suis dans la classe mère de mon appli, et je veux charger un swf contenant des éléments liés en bibliothèque. Donc, on fait un Loader.load() classique, mais sans LoaderContext... Ensuite, on tombe dans le listener Complete. Attention, il faut que le fla de la librairie soit attaché à une classe document que l'on nommera très intelligemment "MaLibrary"...
private function onMachinComplete(e:Event):void
{var domain:ApplicationDomain = _monLdr.contentLoaderInfo.applicationDomain;
_maLibrary = (_monLdr.content as MaLibrary );
_maLibrary.init( domain );
addChild(_maLibrary);
}
En fait toute la subtilité (hmmm...) réside dans le fait de rajouter une petite fonction 'init' à la classe document de la librairie, qui va récupérer le domaine d'application...
Ensuite, pour appeler un objet de la bibliothèque de la librairie chargée, dans notre méthode init (ou ailleurs), il suffit de:
public function init(pdomain:ApplicationDomain)
{
var _symbol:MonSymbol= new MaClass();
addChild(_symbol);
}
Et donc voilà que pour notre librairie, nous avons la classe document et une classe toute bête pour gérer le symbol en question.
Bon, je sais pas si c'est plus clair, mais en tous cas c'est une solution évitant de charger un swf en plus...
Qu'en dites vous?
(euh, si j'ai mal expliqué, je peux envoyer des fichiers d'exemples)
Hello,
Oui cela revient à faire comme je l'ai montré avec le getDefinitionByName
Le souci est que tu ne peux plus utiliser le typage fort
Ou alors tu passes par une interface, ce qui peut être aussi très bien !
Comme toujours, il faut voir les contraintes... pour ma part je n'en ai aucune vis-à-vis du poids, donc ca pose aucun problème d'ajouter un swf qui va gérer la librairie
@++
Salut Cédric,
Lorsque je créé un projet Flash, je décoche toujours la case "Déclarer auto les occurences de scène" (j'ai fait un jsfl qui créé et configure le fla).
De cette manière je dois déclarer le fieldContent en propriété publique dans ma classe liée et je n'ai pas le problème dont tu parles. Je trouve d'ailleurs préférable de voir dans ma classe (au niveau des var) ce qui est posé sur la scène du symbole, ce qui permet aussi de disposer de l'autocompletion. Cette option de déclaration auto de Flash est une saloperie qu'ils n'auraient jamais dû mettre. :D
Et pour le typage strict de l'objet chargé, j'utilise en général une interface, lorsqu'un typage précis est nécessaire.
++
Salut dada
Oui effectivement, je préfère aussi avoir la variable dans ma classe. Ceci dit, dans mon billet ça n'a rien à voir avec le problème... J'ai bien essayé de chercher de ce coté-la au début ! C'est simplement flash qui se broute méchamment car tout d'un coup, une classe est liée à un symbole alors qu'elle était déjà chargée.
@++
Ha ? J'ai peut-être lu un peu vite alors. Pourtant ce message d'erreur : "Cannot create property fieldContent on MySymbol." n'apparaît pas dans le cas que tu présentes si on a manuellement déclaré le TextFIeld au lieu de laisser flash faire son auto-déclaration.
Re,
Ben j'ai fait le test (en mettant la variable en public) et visiblement l'erreur apparait quand même... Essaie avec les sources en annexe
@++
Re
Justement j'avais testé.
Je t'ai mis un Zip ici : http://dada.media-box.net/temp/Symb...
Je n'ai pas d'erreur, le MySymbol est bien instancié dans le loader.
++
Hello !
J'ai été confronté a ce problème très récemment, je l'ai contourné en créant une interface pour l'objet chargé, de telle manière que la classe principale importe l'interface et non l'objet
Je ne saurais dire si c'est propre ou non de faire comme ca , qu'en pensez vous ?
Salut tlecoz
Oui évidemment, ça marche pour autant que tu crées tes objets dans le swf chargé, sinon la classe sera quand-même importée... Enfin à moins de passer par le getDefinitionByName
@++