Forums Développement Multimédia

Les formations Mediabox
Les formations Mediabox
Compatible ActionScript 3. Cliquer pour en savoir plus sur les compatibilités.Par Nataly, le 30 mai 2010

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éritages et surcharge (#2)

Allez ! J'espère que vous êtes tout frais pétillants : on va aborder les choses sérieuses. Pas de panique, j'ai pas dit difficiles, juste qui nécessitent un zeste de concentration, mais comme on revient de pause, tout va bien :)

L'objectif c'est de pouvoir utiliser toute boite héritant de BoiteModale de façon “autonome”. Comprendre qu'elles exposent toutes une méthode affiche et… non, pas de méthode fermer puisque la fermeture est toujours conséquence d'un clic sur l'un des boutons de la boite. Quand je dis pas de méthode fermer, j'entends pas de méthode exposée, il faudra bien qu'à un moment ou à un autre on en programme la fermeture ;)

Méthode ''affiche''

Dans nos rêves on veut donc utiliser comme suit :

// on crée l'objet une bonne fois pour toutes
var bteAlerte:Mv_BoiteAlerte=new Mv_BoiteAlerte();
 
// on l'affiche autant de fois que nécessaire
btT1.addEventListener(MouseEvent.CLICK,test);
function test(me:MouseEvent) {
	bteAlerte.affiche();
}

La boite se fermera automatiquement quand l'utilisateur cliquera sur le bouton (seul et unique en cas de boite d'alerte, mais même chose pour les boites avec plusieurs boutons).

Cette méthode affiche (tant pis je me répète) sera commune à toutes les boites de dialogue, par conséquent elle concerne la classe mère BoiteModale.

Zou ! Direction la classe, on écrit une fonction publique (puisque exposée) qui contient une ligne utilisant addChild pour ajouter à la liste d'affichage.
Ajouter qui ? Et bien this puisque (vous l'avez tracé) c'est Mv_BoiteAlerte, dans notre exemple.

On écrirait bien un truc du genre root.addChild(this), mais… on n'a pas encore accès à la propriété root, on l'aura quand ce sera affiché… Le serpent se mord la queue :(

Pas d'autres solution1) qu'avoir préalablement valorisé une variable racine. On va donc le faire (ce sera une contrainte d'utilisation) avant l'appel à la méthode affiche, depuis le .fla, ce qui donnera :

// le .fla
var bteAlerte:Mv_BoiteAlerte=new Mv_BoiteAlerte();
bteAlerte.racine=root;
 
btT1.addEventListener(MouseEvent.CLICK,test);
function test(me:MouseEvent) {
	bteAlerte.affiche();
}

Dans la classe on déclare une variable publique racine de type DisplayObject, et on pense à importer le paquetage flash.display.DisplayObject.

// classe BoiteModale
package Interface.BoitesModales{
	import flash.display.MovieClip;
	import flash.display.Shape;
	import flash.display.DisplayObject;
	import flash.events.Event;
 
	public class BoiteModale extends MovieClip {
 
		public var racine:DisplayObject;
 
        // … suite

Et on écrit la bête fonction affiche, qui doit être publique (on l'appelle de l'extérieur).

		public function affiche():void {
			trace("méthode affiche ds modale");
			MovieClip(racine).addChild(this);
		}

Pour se prévenir de toute mauvaise surprise, quand plus tard on aura (peut-être) oublié que la propriété racine doit impérativement être valorisée, et afin d'éviter un message d'erreur abscons :

		public function affiche():void {
			trace("affiche modale");
			if (racine==null) {
				trace("ATTENTION la propriété racine doit être valorisée (root)");
				return;
			}
			MovieClip(racine).addChild(this);
		}

C'est bien joli, mais on ne peut plus s'en débarrasser.
Il nous faut une fonction ferme qui sera invoquée depuis le bouton OK de la boite d'alerte.

Méthode ''ferme''

Toutes les boites disposeront de cette fonction, c'est une caractéristique commune, c'est donc la classe mère qui est concernée.

		internal function ferme():void {
			trace("ferme Mère ");
			MovieClip(racine).removeChild(this);
		}

Heuhhh ! Qu'est ce que c'est que ce internal ???
Jusqu'alors en s'en était sorti avec les mots clé attribut (c'est comme ça que ça s'appelle :-?) private ou public. D'un certain point de vue cette fonction n'est pas publique puisqu'on ne l'appellera pas depuis le .fla, mais si on la déclare privée, on ne pourra pas l'invoquer depuis la classe fille… Et bien, voilà, dans ce cas on utilise internal qui rend la fonction (la variable ou la classe considérée) disponible pour tous les appels au sein du même paquetage.

Ne reste plus qu'à l'appeler dans BoiteAlerte au clic sur bouton. C'est le moment d'ajouter le bouton en question, si ce n'est déjà fait.

 
// ******** requiert ******************
//btOK :bouton
//*************************************
 
 
package Interface.BoitesModales{
	import flash.events.MouseEvent;
 
	public class BoiteAlerte extends BoiteModale {
 
		public function BoiteAlerte():void {
			trace("passage constructeur BoiteAlerte");
			btOK.addEventListener(MouseEvent.CLICK,btOKQdClick);
		}
		private function btOKQdClick(me:MouseEvent):void {
			trace("ferme ");
			ferme();
		}
	}
}

Oui : c'est pas plus mal de prendre l'habitude, quand vous écrivez des classes de base, de préciser en commentaire les objets requis dans le symbole associé. Vous en serez bien contents un jour ou l'autre ;)

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

Youpi ! On assure grave !

Raa… Non ! Pas encore tout à fait : si on appelle la boite plusieurs fois de suite, à chaque appel l'écran est un peu plus opaque… Normal, à chaque fois qdAjouté est invoquée et un rectangle supplémentaire est dessiné dans la forme _ecran. Les rectangles se superposant l'opacité l'est chaque fois plus - opaque ;)
Ça c'est rien du tout, peut être même avez vous anticipé d'une ligne qui efface juste avant de dessiner :

		private function qdAjouté(e:Event) {
                        // effacer
			_ecran.graphics.clear();
			_ecran.graphics.beginFill(coulEcran,alphaEcran);
			_ecran.graphics.drawRect(0,0,stage.stageWidth,stage.stageHeight);
		}



Surcharge

Surcharger la méthode affiche de BoiteModale

Occupons nous maintenant d'afficher un texte de notre choix dans la boite d'alerte.

On souhaite pouvoir initialiser le champ texte avant d'afficher la boite de cette façon :

function test(me:MouseEvent) {
 
	if (txtSaisie.text=="") {
		bteAlerte.prompt="le champ ne doit pas être vide";
	} else {
		bteAlerte.prompt="saisie : "+txtSaisie.text;
	}
	bteAlerte.affiche();
}

Déclarer une variable globale publique prompt dans la classe BoiteAlerte ce n'est pas le plus difficile, en revanche comment se sortir de cette méthode affiche que l'on a définie dans la classe BoiteModale ? C'est une méthode commune, elle doit rester dans la classe mère.
On veut qu'elle s'enrichisse d'une fonctionnalité supplémentaire pour les boites d'alerte : la méthode doit, pour tout le monde, ajouter le clip à la racine, et en plus, pour les boites d'alerte, écrire dans le champ texte. On va donc re-écrire la fonction affiche dans la classe fille en la surchargeant. Les anglophones pensent “prendre le dessus” et écrivent override.

Pour surcharger une méthode (ou une propriété),
il faut respecter la même signature et faire précéder du mot clé override.

Ça donne ça :

// Classe BoiteAlerte
	public class BoiteAlerte extends BoiteModale {
		public var prompt:String="";
 
// [...]
 
		override public function affiche():void {
			trace("alerte affiche");
                        // écrire dans le champ texte
			txtPrompt.text=prompt;
                        // invoquer la méthode de la classe mère
			super.affiche();
		}

Comme vous le constatez, pour exécuter une méthode surchargée on utilise le mot clé super.

Surcharger les propriété ou des méthodes natives

Là, je suis désolée : impossible de phosphorer une illustration du principe de la surcharge des fonctions natives d'une classe, appliquée à cet exemple, qui ne soit pas complètement capilotractée… J'en appelle donc à votre grande mansuétude pour ce qui va suivre…

Ce qu'il faut savoir c'est que le principe de surcharge n'est pas réservé aux fonctions écrites par nous même dans nos propres classes. On peut surcharger n'importe quelle méthode ou propriété d'une classe native, pour peu qu'on en ait la signature. Imaginons que nous souhaitions interdire à l'utilisateur de boites modale l'utilisation de la propriété alpha parce que… parce que… parce qu'il me faut bien exemple…

Consultons la doc à propos de la propriété alpha.
Regardez l'entrée implémentation :

Implémentation
  public function get alpha():Number
  public function set alpha(value:Number):void

Oh ! Un couple d'accesseurs :)
Parfait, ce sont des fonctions, on peut donc surcharger le setter d'une petite ligne à nous (comme on l'a fait avec notre fonction affiche à l'instant).

		override public function set alpha(pA:Number):void {
			trace("Utilisation de l'alpha proscrite");
		}

En vrai elle ne fait rien cette fonction, mais rien de rien si on considère qu'une ligne de trace c'est rien. Elle n'appelle même pas la super classe. C'est ce qu'on veut… Pas d'appel, pas de modification de l'alpha.
Si on avait voulu l'invoquer tout de même cette propriété alpha de la classe mère (super-classe) on aurait écrit super.alpha=….
A noter :
Pour aller vite j'ai dit, à l'instant, qu'on pouvait surcharger les propriétés des classes natives, pour peu qu'elles soient définies par des accesseurs, bien sûr.

Résultat des courses

Et voilà ce que vous pourrez obtenir :

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

Appel dans le .fla :

// le .fla
var bteAlerte:Mv_BoiteAlerte=new Mv_BoiteAlerte();
bteAlerte.racine=root;
bteAlerte.y=50;
bteAlerte.x=100;
bteAlerte.alpha=0
 
btT1.addEventListener(MouseEvent.CLICK,test);
function test(me:MouseEvent) {
	bteAlerte.alphaEcran=0.1;
	if (txtSaisie.text=="") {
		bteAlerte.coulEcran=0xFF0000;
		bteAlerte.prompt="le champ ne doit pas être vide";
	} else {
		bteAlerte.coulEcran=0x00FF00;
		bteAlerte.prompt="saisie : "+txtSaisie.text;
	}
	bteAlerte.affiche();
}

La classe BoiteModale :

// BoitesModales.as
 
package Interface.BoitesModales{
	import flash.display.MovieClip;
	import flash.display.Shape;
	import flash.display.DisplayObject;
	import flash.events.Event;
 
	public class BoiteModale extends MovieClip {
 
		public var racine:DisplayObject;
		public var coulEcran:uint=0x333333;
		public var alphaEcran:Number=0.1;
 
		//
		private var _ecran:Shape;
		private var _largeur:Number;
		private var _hauteur:Number;
 
		public function BoiteModale():void {
			//trace("passage constructeur BoiteModale ");
                        // lire hauteur et largeur de la boite AVANT dessin de l'écran
			_largeur=width;
			_hauteur=height;
			_ecran=new Shape();
			addChildAt(_ecran,0);
			this.addEventListener(Event.ADDED_TO_STAGE,qdAjouté);
		}
		//-------------------------------------------------
		private function qdAjouté(e:Event) {
			_ecran.graphics.clear();
			_ecran.graphics.beginFill(coulEcran,alphaEcran);
			_ecran.graphics.drawRect(-x,-y,stage.stageWidth,stage.stageHeight);
		}
 
 
		public function affiche():void {
			trace("affiche modale");
			if (racine==null) {
				trace("ATTENTION la propriété racine doit être valorisée (root)");
				return;
			}
			MovieClip(racine).addChild(this);
		}
		internal function ferme():void {
			//trace("ferme Mère ");
			MovieClip(racine).removeChild(this);
		}
 
               // on en aura bien besoin un jour ;)
		public function get largeurBoite():Number {
			return _largeur;
		}
		public function get hauteurBoite():Number {
			return _hauteur;
		}
 
		// Surcharge alpha
		override public function set alpha(pA:Number):void {
			trace("Utilisation de l'alpha proscrite");
		}
	}
}

Classe BoiteAlerte :

// BoiteAlerte.AS
 
// ******** requiert ******************
//btOK : bouton
//txtPrompt : TextField 
//*************************************
 
 
 
package Interface.BoitesModales{
 
	import flash.events.MouseEvent;
 
	public class BoiteAlerte extends BoiteModale {
		public var prompt:String="";
 
		public function BoiteAlerte():void {
			trace("passage constructeur BoiteAlerte");
			btOK.addEventListener(MouseEvent.CLICK,btOKQdClick);
		}
		private function btOKQdClick(me:MouseEvent):void {
			trace("ferme ");
			ferme();
		}
		override public function affiche():void {
			trace("alerte affiche");
			txtPrompt.text=prompt;
			super.affiche();
		}
	}
}

Pour résumer

Récapitulons nous :

Le concept de surcharge (override en anglais) intervient lorsqu'on a besoin de modifier certaines fonctionnalités héritées. (Ça c'est T.Imbert qui le dit, je vois pas l'intérêt de formuler plus mal sous couvert de dire autrement ;))

Pour surcharger une fonction héritée on utilise le mot clé override et on respecte la signature.
On trouve la signature des méthodes natives dans la documentation sous l'entrée implémentation.
On invoque la fonction de la classe mère à l'aide de l'instruction super.

Vocabulaire

Ici sans le savoir, nous avons mis en place les techniques d'encapsulation et de polymorphisme.

Encapsuler c'est “cacher” ou proscrire l'accès direct à certaines méthodes ou propriétés d'une classe. A chaque fois qu'on utilise des accesseurs pour modifier une propriété (plutôt qu'en faire une simple variable publique) on encapsule. A chaque fois qu'on définit une méthode comme privée ou interne (internal) on l'encapsule aussi (ici la méthode ferme, par exemple).

Le polymorphisme c'est le fait de définir des comportements différents pour une même méthode, selon la classe dont elle dépend au sein d'un même arbre d'héritage. La doc l'explique comme suit :

Par exemple, vous pouvez commencer par une classe appelée Mamifere qui comporte les méthodes play() et sleep(). Vous créez ensuite les sous-classes Chat, Singe et Chien pour étendre la classe Mamifere. Les sous-classes supplantent la méthode play() de la classe Mamifere, de façon à représenter de façon plus réaliste ces types d'animaux. La classe Singe implémente la méthode play() pour se balancer aux arbres ; la classe Chat implémente la méthode play() pour courir après une pelote ; la classe Chien implémente la méthode play() pour rapporter une balle. Dans la mesure où la fonctionnalité sleep() reste similaire quelque soit l'animal, vous utiliseriez l'implémentation de super-classe.

La super-classe c'est la classe mère.

En gros le simple fait de surcharger une méthode ou une propriété c'est du polymorphisme, donc en surchargeant affiche nous avons fait du polymorphisme, tous seuls comme des grands, plus fort que Jourdain !

Cliquez pour continuer

Le titre c'est surtout pour marquer la transition, séparer ce paragraphe du résumé qui précède et introduire la suite.
Parce que suite il y a.
On pourrait croire en être quitte pour ce qui est de la boite d'alerte, mais il manque encore quelque chose…
Imaginez que vous utilisiez ce type de boite d'alerte après avoir mis en en pause une animation, quand l'utilisateur clique, l'animation reprend… Et comment le sait-on que l'utilisateur a cliqué ?

Diffuser des événements -->

1) Si il y a d'autres solutions, par exemple passer la variable en argument. j'ai fait le choix qui m'arrangeait, vous ferez le votre…