17 septembre 2003

Programmation évenementielle : mon Broadcaster pour ActionScript 2

La nouvelle méthode de programmation d'ActionScript 2 semble poser beaucoup de problèmes aux utilisateurs de la programmation évenementielle (dont je fais partie). En fait, mon problème essentiel avec l'AsBroadcaster existant porte sur deux points :

  • On ne peut plus initialiser le prototype, mais uniquement l'instance (dans le constructeur)
  • Si on veut une classe non-dynamique, il faut déclarer les fonctions addListener, removeListener et broadcastMessage.
C'est à ces deux problèmes que je me suis attaqué.

Ma petite solution perso ne règle pas tous ces problèmes, dans le sens ou on ne peut pas faire de l'héritage dynamique, mais j'ai crée une classe nommée EventBroadcaster (je sais, le nom est pas top, mais j'ai pas osé LAlexBroadcaster, ca fait un peu mégalo ... :)).
class EventBroadcaster {
        var _listeners:Array;
        // -- Constructeur
        // Initialise le tableau '_listeners'
        private function EventBroadcaster() {
                this._listeners = new Array();
        }
        // Emet un évenement passé en paramètre
        public function broadcastMessage (msg:String) {
                // Parcoure le tableau '_listeners'
                for (var i=0 ; i<this._listeners.length ; i++) {
                        // Si l'évenemet est présent dans un écouteur, on l'applique
                        // Avec les arguments passés aprés le nom de l'évenement
                        if (typeof this._listeners[i][msg] == "function") {
                                this._listeners[i][msg].apply(this._listeners[i],arguments.slice(1));
                        }
                }
        }
        // Rajoute un écouteur a un objet
        public function addListener(o:Object) {
                // Supprime d'abord l'écouteur
                this.removeListener(o);
                // Ajoute l'objet en tant qu'écouteur
                this._listeners.push(o);
        }
        // Supprime un écouteur
        public function removeListener(o:Object) {
                // Parcoure le tableau '_listeners'
                // Si l'objet est trouvé, on le supprime et on arrete la boucle
                for (var i=0 ; i<this._listeners.length ; i++) {
                        if (this._listeners[i] == o) {
                                this._listeners.splice(i,1);
                                break;
                        }
                }
        }
        // Initialisation d'une instance de classe DYNAMIQUE
        public static function initialize(o:Object) {
                // On crée tout simplement le tableau '_listeners'
                // et les méthodes pour broadcaster
                o._listeners = new Array();
                o.addListener = EventBroadcaster.prototype.addListener;
                o.removeListener = EventBroadcaster.prototype.removeListener;
                o.broadcastMessage = EventBroadcaster.prototype.broadcastMessage;
        }
        // Initialisation d'une classse DYNAMIQUE
        // ou initialisation de la classe d'un instance DYNAMIQUE
        // La classe doit être dinamique, tout simplement pour
        // pouvoir faire appel a this.addListener ou this.broadcastMessage
        public static function initializeClass(o:Object) {
                var curParent:Object;
                if (typeof o == "function") {
                        curParent = o.prototype;
                } else {
                        curParent = o.__proto__;
                }
                do {
                        switch (curParent.__proto__) {
                                // La classe hérite deja de EventBroadcaster
                                case EventBroadcaster.prototype :
                                        return;
                                // On est au niveau le plus bas de la chaine de protos
                                // On glisse le prototype dans la chaine
                                case Object.prototype :
                                        curParent.__proto__ = EventBroadcaster.prototype;
                                        return;
                                // Aucun des cas précédents, on remonte d'un cran dans
                                // la chaîne de prototypes
                                default :
                                        curParent = curParent.__proto__;
                        }
                } while (true);
        }
}
Elle peut être utilisée de deux manières :

  • Par héritage C'est de loin la méthode que je préfère, car elle ne nécessite pas une classe dynamique, ni une déclaration des méthodes de broadcast. Le seul inconvénient est que on n'a pas toujours le choix car l'héritage multiple n'existe pas en AS2. Mais on peut toujours faire hériter la classe mère de EventBroadcaster ... class CustomClass extends EventBroadcaster {
            var _prop:Number = 10;
            function CustomClass(prop:Number) {
                    this.addListener(this);
                    this._prop = prop;
            }
            function sendEvent() {
                    this.broadcastMessage("onEvent","Test d'évenement");
            }
            function onEvent(str) {
                    trace(str + " : " + this._prop);
            }
    }
    Et on l'utilise ainsi :var myClass = new CustomClass(20);
    myClass.sendEvent();
  • Par l'utilisation de initialize ou initializeClass
    • La méthode statique initialize fonctionne comme pour AsBroadcaster. Bien évidemment, il faut que la classe soit dynamique, pour pouvoir lui ajouter les fonction, et pouvoir appeler la méthode broadcastMessage dans les méthodes de la classe.
    • La méthode initializeClass prend en paramètre soit une classe et fait hériter cette classe de EventBroadcaster, soit un objet et fait hériter la classe de cet objet de EventBroadcaster : c'est de l'héritage dynamique à l'aide de la chaine de prototypes.
    dynamic class CustomClass {
            var _prop:Number = 10;
            function CustomClass(prop:Number) {
                    EventBroadcaster.initializeClass(this);
                    this._listeners = [];
                    this.addListener(this);
                    this._prop = prop;
            }
            function sendEvent() {
                    this.broadcastMessage("onEvent","Test d'évenement");
            }
            function onEvent(str) {
                    trace(str + " : " + this._prop);
            }
    }

    Et l'utilisation est la même que celle donnée précédemment. L'avantage est que les méthodes addListener, removeListener et broadcastMessage ne se retrouvent pas dans chaque instance ...

Voila pour mon étude personnelle sur la question ! 8)

::Télécharger EventBroadcaster.as::

16 septembre 2003

Ecrire du bytecode directement dans Flash

Robin Debreuil a trouvé un moyen d'écrire du code directement en ActionScript, et ce grâce à la fonction __bytecode__. Pour information, le bytecode est le code compilé qui fait partie du SWF. Elle est analogue à une fonction C qui permettait d'écrire de l'assembleur dans du code C (j'avais un copain de promotion qui n'utilisait que ca en cours de C, c'en était stressant ! :D)

Robin nou donne un exemple de code (enlever les retours à la ligne) __bytecode__("88240002006900486F6C7920736869742C206279
7465636F646520696E2074686520666C612100960B000800060000
0000000000003C96020008001C960500070A00000048129D020015
00960200080126960400080008001C501D990200D6FF00"
);

Cette instruction pourrait amener vers des brouilleurs de code trés puissants, qui protegeraient de manière plus efficace le code dans les fichiers .fla ! 8)

12 septembre 2003

Classes virtuelles en ActionScript 2

Les interfaces, c'est bien joli, mais il faut bien avouer que les classes virtuelles sont quand-même bien pratiques ... Pour ceux qui ne connaissent pas, il s'agit en fait d'un mélange entre les classes et les interfaces : on ne peut pas créer d'instance d'une classe virtuelle, mais elle contient du code, et pas uniquement les signatures des méthodes comme les interfaces...

Pour pouvoir les implémenter, il y a deux solutions

  • Créer une classe normale, et faire confiance au programmeur pour ne pas l'instancier ... (je vous dis tout de suite : c'est la mauvaise solution :D)
  • Créer une classe avec un constructeur privé (private). Le seul problème est que la classe peut être instanciée quand même grace à une petite astuce qui permet l'instanciation dynamique.

Voici un exemple de classe virtuelle en AS2 :
class VirtualClass {
private var _p:Number;

// Constructeur privé
private function VirtualClass (p:Number) {
_p = p;
}

public function myMethod() {
return _p;
}
}

class FinalClass extends VirtualClass {
// Constructeur public faisant appel au constructeur
// de la super classe.
public function FinalClass(p:Number) {
super(p);
}
}

// Fonctionne trés bien
myFinal = new FinalClass(10);
trace(myFinal.myMethod());

// Erreur à la compilation
myVirtual = new VirtualClass(10);

// Astuce pour instancier la classe virtuelle
myVirtual = new _global["VirtualClass"](10);
trace(myVirtual.myMethod());

L'astuce vient du fait que le compilateur ne peut pas détecter quelle est la classe qui va être instanciée quand on utilise la notation entre crochets, car la valeur entre les crochets peut être dynamique (le contenu d'une variable par exemple), et donc ne sait pas que le constructeur est privé. Car le compilateur n'est en fait qu'un traductauer en code AS1, qui contient des points de blocage si on essaie d'accéder à une propriété ou méthode privée ...

Interpréteur BBCode personnalisable en PHP

J'ai enfin finalisé mon parseur BBCode (en fait, depuis hier je l'ai commenté), afin de pouvoir le mettre à disposition ici. Vous pouvez donc l'utiliser à votre guise. Tout ce que je demande, c'est un petit mail pour me signaler les sites où vous l'utilisez (voire un lien vers mon site, c'est encore mieux :D)...

Un des gros avantages de ce moteur est qu'il se configure au moyen d'un fichier XML, que l'on charge à la volée avant de l'utiliser. Cela permet d'utiliser différents BBCode selon la section de la page ou l'on se trouve. Par exemple, pour la page de detail d'un post du blog (cliquez sur "Lire la suite ..."), la partie "post" est en "full BBCode" (mes BBCode, que je ne veux pas forcément rendre dispos pour les commentaires), et les commentaires sont parsés grâce à un autre fichier XML ...

Toutes les configurations possibles n'ont pas forcément été testées (il y en a pas mal, décrites dans le fichier 'lisezmoi.txt'), donc si vous trouvez des bugs pour une certaine combinaison des paramètres, n'hésitez pas à m'en parler. De plus, je ne suis pas un codeur PHP (je ne l'ai même pas fait en POO), donc il y a peut-être des incohérences dans le code ou des constructions qui peuvent être améliorées. Vous pouvez également me les signaler, ca me fera toujours progresser.

::Télécharger BBCode Parser::


18 août 2003

Petite MAJ : TweenMenu v2.0.1

Une petite mise du TweenMenu, qui passe en v2.0.1 : au menu des changements :


  • On ne peut plus cliquer sur un bouton pendant le mouvement (ca peut provoquer un bug).
  • On peut aussi régler le décalage des sous-niveaux grâce à un nouveau paramètre : Items indentation.

Les autres fonctionnalités sont toujours disponibles sur ce topic.

::Télécharger TweenMenu v2::

13 août 2003

TweenMenu v2 : le nouveau XML Menu

Voici un peu plus qu'un prototype. Il s'agit de la v2 de mon menu XML, renommé pour la peine en TweenMenu.
Ce menu basé sur du XML est maintenant sous forme de composant (un vrai, qu'on installe avec Extension Manager et tout, et tout ...).
Ce menu utilise, pour le paramétrage de ses mouvements, des equations d'interpolation ayant la même signature ques les "easing equations" de Robert PENNER. Elles sont d'ailleurs fournies par défaut avec le composant (pour plus d'infos, aller voir sur : http://www.robertpenner.com/easing/easing_demo.html).

Voici les possibilités du TweenMenu :


  • Création du menu via un fichier XML
  • Possibilité d'ouvrir une adresse Web (URL)
  • Possibilité de charger un clip externe (SWF)
  • Possibilité d'appeler une fonction avec des paramètres
  • Interpolations configurables que ce soit pour déplier ou replier un élément du menu, grace aux équations de PENNER
  • Possibilité de mettre une icône sur un élément
  • Paramétrage des couleurs de remplissage et des contours
  • Paramétrage des formats de texte et de l'utilisation de polices intégrées (de la bibliothèque)
  • Possibilité de surveiller le chargement du fichier XML
  • Evenements déclenché lors du depliage/repliage d'un élément du menu.
  • Et d'autres ...

D'autres fonctionnalités sont à venir pour plus tard, mais n'hésitez pas à me contacter si une fonction vous viens à l'esprit ...

::Télécharger TweenMenu v2::

11 août 2003

createTextField retournant le TextField créé

Il m'arrive souvent d'utilise la référence retournée par createEmptyMovieClip pour agir sur le clip nouvellement créé (changer ses propriété, etc...). Or, l'instruction 'createTextField', elle, ne retourne rien, ce qui ne permet pas la même facilité pour accéder au nouveau TextField.

J'ai donc créé un prototype tout simple qui "écrase" la méthode createTextField, et qui retourne le champ texte créé.
[asfile]proto.movieclip.createtextfield.as[/asfile]
Voici un exemple d'utilisation
#include "movieclip.createtextfield.as"
// Crée le champ texte
var champTexte = _root.createTextField("saisie",10,10,10,100,20);
// Modifie les propriétés du champ texte créé.
champTexte.type = "input";
champtexte.border = true;

::Télécharger le prototype::

8 août 2003

Longueur d'un tableau associatif

Dans Flash, on peut également accéder à une propriété d'un objet en mettant son nom à l'intérieur de l'opérateur 'crochets' [ ]. Les objets Flash peuvent donc être considérés comme des tableaux associatifs. Il peut être bien pratique de récupérer le nombre d'éléments de ce tableau associatif. C'est à ca que sert la propriété 'length' créée ici.

! ATTENTION ! N'oubliez pas que la longueur correspondra à toutes les prorpriétés et méthodes accessibles dans l'objet. Donc, si vous ne voulez pas être embêtés par certaines propriétés/méthodes, pensez à utiliser ASSetPropFlags !!! Le mieux est encore, en POO, d'attribuer toutes les méthodes au prototype de la classe (qui ne seront donc pas décomptées), et de masquer les propriétés qui ne doivent pas être prises en compte avec ASSetPropFlags.

[asfile]proto.object.length.as[/asfile]

Voici un exemple d'utilisation

var monTableauAssociatif = new Objet();
monTableauAssociatif["LAlex"] = {prenom: "Alexandre", age:24};
monTableauAssociatif["Tacha"] = {prenom: "Natacha", age:22};
monTableauAssociatif["Thom"] = {prenom: "Thomas", age:28};
trace("Nombre de membres : " + monTableauAssociatif.length);

::Télécharger le prototype::

1 août 2003

Fonctions : polymorphisme paramétrique

Voila un proto qui ne va pas servir souvent : il permet de faire du polymorphisme paramétriquesur une fonction. En gros, sur un même appel de fonction, c'est une autre fonction qui va être appellée en fonction de la signature (nombre de paramètre et types des paramètres).

Si aucune signature "enregistrée" ne correspond à l'appel, la fonction par défaut est appelée.

[asfile]proto.function.polymorphism.as[/asfile]

Voici un exemple d'utilisation :

// Mes protos
#include "function.polymorphism.as"
// Fonction originale (affiche les parametres et leurs types)
myFunction = function(a,b,c) {
   trace("Typeof 'a' (" + a + ") : " + (typeof a));
   trace("Typeof 'b' (" + b + ") : " + (typeof b));
   trace("Typeof 'c' (" + c + ") : " + (typeof c));
   trace("");
}
// Fonction "polymorphique" (?)
// Fait le même chose que la première,
// avec un texte en plus pour la différencier
var myPolyFunction = function(a,b,c) {
   trace("Poly >> Typeof 'a' (" + a + ") : " + (typeof a));
   trace("Poly >> Typeof 'b' (" + b + ") : " + (typeof b));
   trace("Poly >> Typeof 'c' (" + c + ") : " + (typeof c));
   trace("");
}
// Si on appelle ma fonction avec 3 booléens
// je veux que ce soit myPolyFonction qui soit exécutée
myFunction.addPoly(myPolyFunction,Boolean,Boolean,Boolean);
// Appel de 'myFunction' avec 3 booléens
myFunction.call(this,true,true,true);
// Appel de 'myFunction' avec 3 types quelconques
myFunction.call(this,"LAlex",15,true);
// Je supprime le polymorphisme pour les 3 booléens
myFunction.removePoly(Boolean,Boolean,Boolean);
// J'essaie de rappeler 'myFunction' avec 3 booléens
myFunction.call(this,true,true,true);

La sortie affiche

Poly >> Typeof 'a' (true) : boolean
Poly >> Typeof 'b' (true) : boolean
Poly >> Typeof 'c' (true) : boolean


Typeof 'a' (LAlex) : string
Typeof 'b' (15) : number
Typeof 'c' (true) : boolean


Typeof 'a' (true) : boolean
Typeof 'b' (true) : boolean
Typeof 'c' (true) : boolean

::Télécharger le prototype::

31 juillet 2003

Tableaux d'objets : min et max sur une propriété

Dans beaucoup de classes, on manipule des tableaux d'objets de même type. Trier ces tableaux est possible facilement avec sortOn, mais récupérer l'objet ou la position de l'objet ayant la plus grande propriété l'est deja moins.

Ce prototype permet de la faire sans trop de code.

[asfile]proto.array.minmaxproperty.as[/asfile]

Voici un exemple d'utilisation. Utile quand on gère un tableau de clips et que l'on veut savoir lequel est le plus haut.

// Propriété '_depth' : contient la profondeur du clip
MovieClip.addProperty("_depth",MovieClip.prototype.getDepth,null);
// Tableau contenant plusieurs clips.
monTableau = new Array(monClip1, monClip2,monClip3);
// Dans 'clipAuDessus', on va récupérer le clip ayant le '_depth'
// le plus grand donc celui qui est au dessus des autres.
var clipAuDessus = monTableau.getMaxPropertyObject("_depth");
// On affiche le nom du clip
trace(clipAuDessus._name);

::Télécharger le prototype::

18 juillet 2003

keepEvent / releaseEvent

Ce prototype m'est venu du besoin d'effectuer une action lors d'un événement, mais étant pour un composant que j'allais distribuer l'utilisateur du composant devait lui aussi pouvoir modifier l'action qu'il voulait sans craser mes instructions.

L'avantage de ce prototype est qu'il permet de figer une action à un moment précis, et cette actionsera effectuée même si l'on change la fonction. Par exemple, appliquée à l'événement onPress d'un bouton, il permet d'effectuer le onPress que j'ai créé, puis celui fait par un autre programmeur qui aura crée son évenement comme pour n'importe quel autre bouton.

[asfile]proto.keepevent.as[/asfile]

Voici un exemple d'utilisation. Quand on va passer au dessus du bouton, les deux actions vont être effectuées, alors que dans un cas normal, la deuxième aurait écrasé la première.

monBouton.onRollOver = function() {
   displayInfoBulle("Mon message");
}
monBouton.keepEvent("onRollOver",true);
monBouton.onRollOver = function() {
   trace("Ceci est une nouvelle action");
}

::Télecharger le prototype::

page 4 de 4 -