Introduction
Classe de base (#1)
Classe de base (#2)
Héritage et surcharge (#1)
Héritage et surcharge (#2)
Diffuser des événements
Classe liée
Classe de document
Classe externe : une visionneuse (#1)
Classe externe : une visionneuse (#2)
Classe externe : méthodes statiques
Héritage et surcharge (#1)
Pour illustrer les points qui vont suivre, je vous propose d'écrire quelques classes destinées à gérer des boites de dialogue modales.
On dit d'une boite de dialogue (et d'une fenêtre en général) qu'elle est modale quand sa fermeture est obligatoire pour pouvoir donner le focus à une autre fenêtre. En gros quand on ne peut pas accéder à d'autres éléments d'interface aussi longtemps qu'elle est affichée (ça empêche l'utilisateur de cliquer partout )
Typiquement il s'agit des boites d'alerte et autres boites de type Oui/Non.
Merci Lilive pour l'illustration bien moins laide plus jolie que celle dont je m'étais contentée.
Conception
Comme vous le voyez au dessus, la ruse consiste à créer un écran translucide entre la boite de dialogue et les autres éléments de l'animation.
Le principe de l'écran est commun à toutes les boites, qu'elles soient de type Alerte (un bouton), Question (Oui-Non) ou encore Boite de saisie.
Principe commun, quand on pense classe ça revient à dire héritage.
Toutes les boites, si elles sont modales, devront hériter des même caractéristiques, donc d'une même classe qui se chargera d'afficher un écran translucide entre la boite et l'animation.
Appelons cette classe BoiteModale, elle héritera de MovieClip et chacune des classes (BoiteAlerte, BoiteOuiNon…) héritera de BoiteModale.
Dans un premier temps je vous propose de construire une boite d'alerte modale, ensuite ce sera jeu d'enfant de décliner le principe pour d'autres types de boites.
On est d'accord sur le fait qu'il va nous falloir, outre un fla de mise en œuvre, une classe BoiteAlerte et une classe BoiteModale.
Si on anticipe le fait que plus tard viendront s'ajouter des classes BoiteSaisie, BoiteOuiNon, et d'autres, on ne peut plus se satisfaire de tout ranger dans le même répertoire (le fla et les classes auxquelles il fait appel). D'autant moins que ces classes vont nous être utiles chaque fois que, dans un projet ou un autre, nous auront recours à une boite de type modal.
Organisation du disque et chemin de classe
Le .fla de test
Le .fla de test c'est pure formalité :
Dans sa bibliothèque il faut un clip qui sera une boite d'alerte. Faire aussi moche que sur la demo ne devrait pas vous prendre trop de temps, pour l'instant même un simple rectangle (pour y voir quelque chose) suffirait.
Organiser les classes
En revanche pour ce qui est des classes, cette fois on ne va pas les laisser dans le même répertoire que le fla.
Il est de tradition d'avoir dans un coin de disque un répertoire Outils, ou Utils, voire Utilitaires dans lequel on range ses classes perso.
Afin de ne pas risquer de confusions avec des répertoires pré-existants, ce dossier je vais le nommer Mezoutils
.
Dedans, un sous-répertoire Interface et dedans encore un autre répertoire BoitesModales, comme ça ce sera bien propre.
A terme donc nous aurons quelque chose qui ressemblera à ça :
On est d'accord que ce clip (Mv_BoiteAlerte
, par exemple) est destiné à être associé à la future classe BoiteAlerte qui étendra la future classe BoiteModale. Ces deux classes seront rangées - empaquetées - dans le répertoire Interface/BoiteModale. C'est cette hiérarchie de dossier qui détermine le nom du paquetage, ici : Interface.BoitesModales
.
On sait tout, yapuka…
Créons les classes avec seulement un trace dans le constructeur :
package Interface.BoitesModales { import flash.display.MovieClip; public class BoiteModale extends MovieClip { public function BoiteModale ():void { trace("passage constructeur BoiteModale "); } } }
BoitesModales (avec des “s”) le nom du répertoire1) - repris dans la déclaration du paquetage et BoiteModale sans “s” le nom de la classe repris dans le nom de classe et le constructeur.
package Interface.BoitesModales { public class BoiteAlerte extends BoitesModales { public function BoiteAlerte():void { trace("passage constructeur BoiteAlerte"); } } }
La seule chose nouvelle c'est le nom du paquetage : pour “atteindre” la classe BoiteAlerte on utilisera donc la syntaxe Interface.BoitesModales.BoiteAlerte
, au même titre qu'on utilise par expemple flash.display.MovieClip
quand il s'agit de la classe MovieClip.
Il ne reste plus qu'à associer Interface.BoitesModales.BoiteAlerte
et le symbole Mv_BoiteAlerte via le champ Classe de base.
Si ce n'est que, par défaut, le compilateur va chercher la classe ou le paquetage dans le même répertoire que le .fla, où il ne le trouve pas, et pour cause on l'a rangé dans le répertoire Mezoutils…
Il faut donc préciser dans les préférences de publication (menu Fichier) le chemin vers ce répertoire Mezoutils :
Et voilà :
Vérifier
Cette fois c'est bon, on va pouvoir avancer.
Pour être certains que le compilateur retrouve ses petits, testons l'histoire d'une simple ligne dans le fla :
var bteAlerte:Mv_BoiteAlerte=new Mv_BoiteAlerte();
passage constructeur BoiteModale passage constructeur BoiteAlerte
Je suis bien d'accord, vérifier que ça fonctionne juste en s'extasiant sur quelques lignes dans le panneau de sortie ce n'est toujours pas plus satisfaisant. Temporairement, pour voir ce qu'on fait, on va donc ajouter l'instance de bteAlerte à la liste d'affichage avec un addChild dans le .fla.
Puisque l'objectif est de fabriquer une boite modale qui en s'affichant proscrira l'accès aux autres éléments de l'animation, profitons en pour ajouter sur la scène quelques boutons et un champ de saisie.
var bteAlerte:Mv_BoiteAlerte=new Mv_BoiteAlerte(); addChild(bteAlerte);
Pour l'instant ça ne proscrit rien du tout, c'est le contraire qui aurait été étonnant on n'a rien fait pour
La classe mère
Il s'agit, pour toutes les boites modales, de fabriquer - quoiqu'il arrive - un écran (une forme) aux dimensions de la scène et de l'ajouter. Ça concerne donc la classe BoiteModale dont dériveront (hériteront) toutes les autres, et ça se passe dans le constructeur.
Avant même de se lancer dans les grandes manœuvres, une chose est sûre : pour dessiner un truc aux dimensions de la scène, il faut les avoir, les dimensions. Trop facile ! C'est stage.stageWidth, et stage.stageHeight, clame Ronchon dans un élan de bonne humeur retrouvée.
Bonne humeur qui ne dure pas car justement, si on teste stage.width dans le constructeur on est désappointé : l'éternelle erreur 1009 (Il est impossible d'accéder à la propriété ou à la méthode d'une référence d'objet nul) est renvoyée.
propriétés stage, root, et parent d'une instance
Regardons ça de plus près, c'est qui le nul ?
public function BoiteModale():void { trace("passage constructeur BoiteModale "+this); trace("scène "+stage) }
passage constructeur BoiteModale [object Mv_BoiteAlerte] scène null passage constructeur BoiteAlerte
Et bien c'est la scène semble-t-il… Comment ça ??
En fait c'est logique, souvenons nous : le constructeur est invoqué à la création de l'instance, avant donc que la-dite instance soit ajoutée. D'ailleurs, le premier test on l'a fait sans même invoquer un addChild. var monObjet:MaClasse = new MaClasse()
hop ! ça invoque le constructeur, et à ce moment là l'objet n'étant pas dans liste d'affichage les propriétés, parent, root, et stage renvoient tout naturellement null…
Zutalors, comment faire ?
Tout bêtement en attendant que l'instance soit ajoutée pour utiliser ses propriétés parent, root ou stage. Dès qu'une instance est ajoutée, l'événement ADDED_TO_STAGE est diffusé (l'instance crie “Ayé ! Je suis dans la liste d'affichage”). Pour nous il suffit donc d'écouter cet événement (importez le package flash.events.Event)
import flash.events.Event;
public function BoiteModale():void { trace("passage constructeur BoiteModale "+this); this.addEventListener(Event.ADDED_TO_STAGE,QdAjouté); } private function QdAjouté(e:Event) { trace("qd ajouté "); trace(stage.stageWidth); }
passage constructeur BoiteModale [object Mv_BoiteAlerte] passage constructeur BoiteAlerte qd ajouté 500
Notez bien qui est this
Dessiner dans une forme
Pour dessiner dans une forme il faut d'abord créer une forme :
var uneForme:Shape=new Shape ;
… puis utiliser sa propriété graphics qui permet d'avoir recours à un ensemble de méthodes pour dessiner des formes vectorielles. Par exemple pour dessiner un rectangle rouge et opaque (alpha=1) de 300 sur 100 (largeur/hauteur) on écrirait :
// remplissage rouge opaque uneForme.graphics.beginFill(0xFF0000,1); // dessiner un rectangle en 0/0 300 large, 100 haut uneForme.graphics.drawRect(0,0,300,100);
Appliqué à ce qui nous préoccupe ça donne :
• Créer (et ajouter) une forme dans le constructeur, une bonne fois pour toutes.
• Dessiner dans la forme une fois l'instance ajoutée à la liste d'affichage pour avoir accès aux dimensions de scène.
Puisque la forme destinée à faire écran sera crée dans une fonction et utilisée (pour y dessiner) dans une autre, il nous faut ne variable globale privée, nommons la _ecran.
public class BoiteModale extends MovieClip { private var _ecran:Shape; [...]
public function BoiteModale():void { // a l'initialisation, créer la forme _ecran=new Shape(); // ajouter _ecran à liste d'affichage (tout en bas) addChildAt(_ecran,0); // attendre que this soit ajouté sur la scène // pour avoir accès à ses dimensions this.addEventListener(Event.ADDED_TO_STAGE,qdAjouté); }
On a bien pris soin d'utiliser addChildAt avec une profondeur 0, un addChild (tout court) aurait placé la forme au dessus de tout le reste, et proscrit aussi l'accès aux boutons de la boite…
private function QdAjouté(e:Event) { trace("qd ajouté "); trace(stage.stageWidth); // gris en alpha 0.1 _ecran.graphics.beginFill(0x333333,0.1); _ecran.graphics.drawRect(0,0,stage.stageWidth,stage.stageHeight); }
Initialiser et modifier les caractéristiques de l'écran
Au point où on en est, autant permettre à l'utilisateur de définir la couleur et l'opacité (l'alpha) de l'écran. Il suffit de sortir deux variables.
Soit on les déclare publiques et globales, et hop le tour est joué ; soit on décide d'utiliser des accesseurs…
C'est le cas de conscience typique.
Définir des accesseurs qui ne feront rien d'autres que modifier la valeur d'une variable globale (certes privée)…
Mouais…
Arguer que oui mais ça permettrait de modifier la couleur de l'écran après l'affichage…
Je réponds à nouveau d'un borborygme pas convaincu. Ça n'a pas grand sens, ce sont des boites destinées à être affichées pour prévenir de toute autre action utilisateur que celle qu'on attend… Quand va-t-on avoir besoin de modifier la couleur ou l'alpha, ou ce qu'on voudra, après l'affichage de la dite boite, pendant que l'utilisateur n'a d'autre recours que cliquer sur le bouton OK ?
En plus, pour ceux qui s'y essaieront quand même (si, si, c'est de bonne guerre), il y aura des surprises, pas incontournables, mais des surprises. Alors écrire une usine pour offrir une fonctionnalité inutile, sur ce coup là, c'est sans moi
Donc deux globales publiques !
public class BoiteModale extends MovieClip { public var coulEcran:uint=0x333333; public var alphaEcran:Number=0.1; private var _ecran:Shape; [...]
private function qdAjouté(e:Event) { _ecran.graphics.beginFill(coulEcran,alphaEcran); _ecran.graphics.drawRect(0,0,stage.stageWidth,stage.stageHeight); }
package Interface.BoitesModales{ import flash.display.MovieClip; import flash.display.Shape; import flash.display.DisplayObject;
Appel depuis le fla :
var bteAlerte:Mv_BoiteAlerte=new Mv_BoiteAlerte(); bteAlerte.coulEcran=0xFF0000; bteAlerte.alphaEcran=0.5 addChild(bteAlerte);
Déterminer les coordonnées de la boite
Cette fois ça suffit, notre boite de dialogue toute coincée là haut, ça ne peut plus durer.
Il faut pouvoir disposer la boite de dialogue là où on veut sur la scène à l'aide des propriétés x et y :
bteAlerte.y=50;
Si ce n'est qu'en l'état ça descend tout en 50. La boite et l'écran.
Normal.
L'écran est ajouté à l'instance - via la classe mère - il est déplacé avec elle
reprenons le processus :
1) on crée une instance (new Mv_Alerte()
)
2) on invoque affiche –> passage dans qdAjouté –> dessin d'un rectangle dans de _ecran en 0/0 :
_ecran.graphics.drawRect(0,0,stage.stageWidth,stage.stageHeight);
Dans le cas qui nous occupe il faudrait qu'il soit créé en 0/-50, pour compenser le décalage vertical. - 50, c'est -y… Pour x c'est même combat… Modifions donc :
private function qdAjouté(e:Event) { _ecran.graphics.clear(); _ecran.graphics.beginFill(coulEcran,alphaEcran); // répercuter le décalage x/y _ecran.graphics.drawRect(-x,-y,stage.stageWidth,stage.stageHeight); }
C'est un bon début. Maintenant on va faire en sorte que la boite s'auto-affiche : on veut pouvoir l'utiliser comme suit :
var bteAlerte:Mv_BoiteAlerte=new Mv_BoiteAlerte(); bteAlerte.affiche();
… ou presque… Bon d'accord on va rencontrer deux trois subtilités… Et, je sais pas vous, mais moi je ferais bien une pause, alors on se retrouve là tout de suite après ?
