Nella programmazione delle applicazioni Flash un ruolo fondamentale è rappresentato dalla gestione degli eventi.
La nostra vita è ricca di eventi, momenti che accadono in determinati momenti a cui noi reagiamo in determinati modi. Li chiamiamo eventi perchè scandiscono momenti importanti della nostra esistenza. Pensate a questo esempio per cercare di immaginare cosa si cela dietro alla definizione di Event Model, un sistema con cui il Flash Player ci comunica che qualcosa di importante è accaduto in un movie flash.
Uno dei grandi cambiamenti nell'architettura V2 dei componenti è rappresentato proprio dal nuovo approccio con cui vengono gestiti gli eventi. Nella vecchia sintassi (della prima versione dei componenti) si usava il parametro ClickHandler per associare una funzione che veniva lanciata per esempio la click del mouse.
istanzaBottone.setClickHandler("miaFunzione");
Ecco un esempio di vecchia sintassi:
mioRadio1_rb.setClickHandler("miaFunzione");
mioRadio2_rb.setClickHandler("miaFunzione");
large_rb.setClickHandler("miaFunzione");
miaFunzione=function(evt){
trace("È stato cliccato il bottone "+evt);
};
Il problema consisteva allora nel dover conoscere a priori se un componente rispondesse ad un evento «click» o per esempio al «change» e nel poter gestire solo un evento alla volta.
Con l'avvento dell'architettura V2 e l'introduzione dei Listener Object ora i componenti ereditano maggiore estensibilità con la nuova sintassi:
miaFunzione = function(evt:Object){
mioTesto_txt.text = "Il Bottone" + evt.target + " è stato cliccato";
}
mioBottone_btn.addEventListener("click", miaFunzione);
mioObj = new Object();
mioObj.click = function(evt:Object){
mioTesto_txt.text = "L'evento " + evt.type + " è stato scatenato";
}
mioSecondoBottone_btn.addEventListener("click", mioObj);
In questo semplice script già si intuiscono la grande novità e la vera potenza di questo approccio. Il metodo addEventListener() associa al click del tasto «mioBottone_btn» la funzione «miaFunzione» che esegue una semplice scrittura su un campo testo dinamico.
Degno di nota è l'intercettazione del parametro all'interno della funzione, evt, un oggetto che contiene il target dell'oggetto che ha scatenato l'evento e il type, cioè il tipo di evento (click).
Per saperne di più del metodo addEventListener potete andare direttamente sulle LiveDocs di Macromedia.
Abbiamo detto che è possibile gestire più funzioni associate allo stesso oggetto Listener:
mioObj = new Object();
mioObj.click = function(evt:Object){
mioTesto_txt.text = "L'evento " + evt.type + " è stato scatenato";
}
mioObj.load = function(evt:Object){
mioTesto_txt.text = "L'evento " + evt.type + " è stato scatenato";
}
mioBottone_btn.addEventListener("click", mioObj);
mioSecondoBottone_btn.addEventListener("load", mioObj);
In questo articolo ci occuperemo dei vari metodi per gestire gli eventi e di capire sopratutto i benefici nella scelta di un metodo rispetto ad un altro.
La gestione degli eventi nei Flash Components V2
Abbiamo detto che un evento è un occorrenza che richiede una risposta da un'applicazione Flash. I classici esempi sono il click del mouse, la pressione di un tasto sulla tastiera.
Affinché un'applicazione reagisca agli eventi lo sviluppatore dovrà preoccuparsi di gestire gli eventi e scrivere del codice Actionscript associato ad un particolare oggetto ed evento specifico.
Gestire un evento significa individuare l'oggetto a cui l'evento deve essere applicato, il nome dell'evento dell'oggetto e la funzione da associare al verificarsi di quel evento. Un semplicissimo esempio che conosciamo tutti :
next_btn.onPress = function () {nextFrame(); }
Non ci dilungheremo però troppo sulla gestione classica degli eventi, ma in questo articolo ci occuperemo essenzialmente di definire come è possibile gestire gli eventi dei componenti di flash mx 2004.
Tutti i componenti possiedono eventi che sono scatenati quando l'utente interagisce con il componente stesso o quando c'è un cambiamento rilevante subito dal componente. È possibile usare differenti tecniche per gestire gli eventi sui componenti :
-
Usando il gestore di eventi on()
-
Usando la funzione di gestione degli eventi
-
Usando gli event listener objects
-
Usando i "typed" event listener objects
-
Usando una classe EventProxy
-
Usando la classe Delegate
Andiamo ad analizzare i vari casi, scoprendo che in realtà la maggior parte di essi li abbiamo usati senza nemmeno saperlo ;).
Gestione degli eventi attraverso il gestore di eventi on()
Se abbiamo mai programmato un pochino in Flash non possiamo non aver usato questo gestore di eventi che permette di assegnare ad una istanza di un componente una funzione da eseguire al verificarsi di un evento:
on(click){
trace(this);
}
Utilizzando questo gestore un event object (eventObj) viene automaticamente generato quando l'evento scatta e viene passato alla funzione. Questo oggetto conserva proprietà riguardanti l'evento stesso:
on(click){
trace("L'evento scattato è : " + eventObj.type);
}
In particolare l'eventObj espone le due seguenti proprietà:
|
Questo metodo per quanto risulti il più semplice è attualmente in fase "deprecated" da parte di Macromedia, che quindi ne scoraggia l'utilizzo da parte degli sviluppatori. Il più grande svantaggio risulta essere quello di poter assegnare un solo gestore di evento per un component alla volta. Per questo il metodo descritto risulta essere poco flessibile e scalabile.
ATTENZIONE I vecchi componenti di Flash Mx (la versione V1 dei components) è compatibile con questo metodo mentre non permette l'utilizzo di listener objects.
Gestione degli eventi attraverso con la funzione di gestione degli eventi
Un secondo metodo per la gestione degli eventi che si trova a cavallo tra l'uso dell'event handler on e i listener object, utilizza la seguente sintassi:
istanzaComponente.clickHandler = function(evt)
{ // codice
}
dove l'evento che si gestisce in questo caso è l'evento "click" scatenato dall'istanza del nostro componente.
Infatti questa sintassi viene costruita aggiungendo il suffisso "Handler" al nome dell'evento per cui si vuole scrivere una funzione. Sarà poi l'event model dei component (Versione1 e Versione2) che si preoccuperà di andare a cercare la funzione relativa all'esecuzione dell'evento.
Questo metodo per quanto semplice dovrebbe essere utilizzato solo quando si conosce a priori che un particolare oggetto è l'unico listener per l'evento.
Gestione degli eventi con gli event listener objects
Con la versione 2 dei component è possibile utilizzare i listeners per gestire gli eventi. Questa rappresenta la soluzione che Macromedia stessa consiglia di applicare.
In questo metodo l'oggetto listener viene assegnato ad una funzione che gestisce l'evento ed è possibile creare oggetti listener multipli da assegnare sia ad una solo istanza di un componente che a più istanze.
È questo che rende questa soluzione la più flessibile e scalabile rispetto alla gestione degli eventi con le funzioni on o clickhandler.
Per usare il modello di event listener basterà creare un oggetto listener definendo la proprietà che rappresenta il nome dell'evento da gestire. Questa proprietà è assegnata ad una funzione cosiddetta di callback. Vediamo subito la sintassi generica per poi scendere più in dettaglio:
var listenerObject:Object = new Object();
componentInstance.addEventListener("eventName", listenerObject);
listenerObject.eventName = function(evtObj){
...
};
Viene quindi creato prima di tutto una variabile di tipo o Objecte dopo viene chiamato il metodo UIEventDispatcher.addEventListener() sull'istanza del componente che processa l'evento. Il metodo addEventListener accetta due proprietà:
- event, una stringa che contiene il nome dell'evento
- listener contiene una reference al listener object o alla funzione
Una volta che abbiamo registrato l'evento è possibile cancellarlo con il metodo EventDispatcher.removeEventListener() o usando il metodo delete theObject.theEventHandler.
Un esempio pratico potrebbe essere quello di dover gestire l'evento change di una combo box che scatta quando una voce di menù viene selezionata dall'utente :
var comboListener = new Object();
comboListener.change = function(eventObj:Object):Void {
var country:String = eventObj.target.value;
trace(country);
};
countrySelect.addEventListener("change", comboListener);
Immaginiamo invece lo scenario in cui vogliamo gestire un unica funzione per controllare tutti gli eventi click dei bottoni che fanno parte della nostra applicazione. Possiamo svolgere questo task semplicemente definire un unico listener object che controlla chi è stato a scatenare l'evento andando a fare un check dell'oggetto evtObj passato dalla funzione:e:
var listenerObject:Object = new Object();
mioBottone.addEventListener("click", this);
listenerObject.click = function(evtObj:Object):Void{
if (evtObj.target == Invia_btn) {
//invia il modulo
} else if (evtObj.target == Resetta_btn) {
//Resetta il modulo
}
};
Vedremo più avanti come una funzione di Proxy ci permette di gestire meglio e rendere quindi il nostro codice più leggibile e sopratutto più scalabile.
I "typed" event listener objects
Un modo per rendere la gestione degli eventi nei components V2 più logica e mantenibile è quella di creare una classe che gestisce gli eventi e gli associa le relative funzioni.
In pratica quello che facciamo è di incapsulare il gestore di eventi in una classe separata isolando quindi il codice e aumentandone la leggibilità ma sopratutto la scalabilità della nostra applicazione.
Per esempio per gestire l'evento click di un pulsante che ci permette di inviare un modulo possiamo lanciare la seguente sintassi:
mioBottone.addEventListener("click", new GestionePulsanteInvia(this));
Ecco invece il codice per la classe GestionePulsanteInvia.as:
import mioPackage.ClassePrincipale
class mioPackage.GestionePulsanteInvia {
var mioOggetto:ClassePrincipale;
// costruttore di classe
public function GestionePulsanteInvia(mioOggetto:ClassePrincipale) {
this.mioOggetto = mioOggetto;
}
public function click (evtObj:Object):Void {
istanzaClasse.invia();
}
}
ATTENZIONE Questo metodo, come gli altri precedentemente descritti, non evita il problema di errori di battitura e se quindi per esempio scrivessimo (riprendendo l'esempio di prima):
comboListener.change = function(eventObj:Object):Void {
var country:String = eventObj.target.value;
trace(country);
};
l'errore nella sintassi eventObj.trget.value (invece di eventObj.target.value) non verrebbe segnalato in compile time!
Gestione di una classe attraverso una classe EventProxy
Tra tutti i metodi fin d'ora proposti quello più elegante per una perfetta gestione degli eventi ci è dato dalla creazione di una classe che smisti per noi le funzioni a seconda dell'oggetto che ha scatenato l'evento.
In questo ci viene in aiuto Mike Chambers con la sua EventProxy class (http://www.markme.com/mesh/archives/004286.cfm):
/* Allows:
-scope of events to be changed
-events calls to be proxied to arbitrary functions
*/
class com.macromedia.mesh.events.EventProxy
{
private var receiverObj:Object;
private var funcName:String;
/* Constructor:
-receiverObj : the receiverObj in which the event will be called
-the function name to be called in response to the event
*/
function EventProxy(receiverObj:Object, funcName:String)
{
this.receiverObj = receiverObj;
this.funcName = funcName;
}
//this is called before the event is broadcast by the components
private function handleEvent(eventObj:Object):Void
{
//if not function name has been defined
if(funcName == undefined)
{
//pass the call to the event name method
receiverObj[eventObj.type](eventObj);
} else {
//pass the call to the specified method name
receiverObj[funcName](eventObj);
}
}
}
In pratica con il costruttore new è possibile definire un oggetto da una classe di tipo EventProxy che prende due parametri:
mioObjProxy = new EventProxy( receiverObj , myFunction);
dove :
-
receiverObj : l'oggetto dal quale l'evento viene chiamato
-
myFunction : il nome della funzione che viene eseguita in risposta all'evento
ovvero :
mioBottone.addEventListener("click", new EventProxy(this, "inviaModulo"));
Facendo un esempio concreto (preso direttamente dall'applicazioni di MXNAFeedView di Mike :
public function onLoad(Void):Void
{
categoryProxy = new EventProxy(this, "onCategorySelect");
category_cb.addEventListener("change", categoryProxy);
feedGridProxy = new EventProxy(this, "onFeedGridSelect");
feedDG.addEventListener("change", feedGridProxy);
}
//broadcast by ComboBox when user selects an item in the combo
private function onCategorySelect(eventObj:Object):Void
{
//ComboBox change event fired
}
//broadcast by the DataGrid when the user selects a row
private function onFeedGridSelect(eventObj:Object):Void
{
//datagrid change event fired
}
La classe Delegate, una gestione degli eventi più intelligente
L’update di Macromedia Flash Mx 2004 dell’estate scorsa tra le molte novità esponeva anche una nuova classe, la mx.utils.Delegate che permette agli sviluppatori di autorizzare gli eventi a rispondere a delle funzioni.
La classe prevede una chiamata ad una funzione di tipo statico che ritorna una funzione da utilizzate come autorizzazione per la chiamata dell’evento.
Ma per capire quali sono i problemi che risolve questa classe dobbiamo ovviamente prima capire quale era la vecchia limitazione imposta da Flash.
Immaginiamo questo scenario: i components come il ComboBox e il Datagrid dispongono entrambi di un evento “change” che quindi lo sviluppatore per gestire semplicemente usa lo statement switch ed intercetta l’eventObj per risalire al componente che ha fatto scattare l’evento. Un semplice esempio potrebbe essere il seguente :
private function change(eventObj:Object)
{
switch(eventObj:target)
{
case mioCombo:
{
//codice per gestire la combobox
break;
}
case mioDG :
{
// per gestire il datagrid
break;
}
}
}
Questo però comporta una certa rigidezza nel codice che diventa difficile da mantenere perché richiede tre passaggi : definire un event handler, gestire uno switch e definire le funzioni dei singoli eventi.
Vari Flashers avevano trovato soluzioni alternative e stilisticamente parlando più di classe. Prima fra tutte quella di Mike Chambers che crea una classe che funge da proxy per gli eventi per differenziare metodi e validità. Ed ecco il codice della classe EventProxy:
/* EventProxy class
Allows:
-scope of events to be changed
-events calls to be proxied to arbitrary functions
*/
class com.macromedia.mesh.events.EventProxy
{
private var receiverObj:Object;
private var funcName:String;
/* Constructor:
-receiverObj : the receiverObj in which
the event will be called
-the function name to be called
in response to the event
*/
function EventProxy(receiverObj:Object, funcName:String)
{
this.receiverObj = receiverObj;
this.funcName = funcName;
}
//this is called before the event is broadcast by the components
private function handleEvent(eventObj:Object):Void
{
//if not function name has been defined
if(funcName == undefined)
{
//pass the call to the event name method
receiverObj[eventObj.type](eventObj);
}
else
{
//pass the call to the specified method name
receiverObj[funcName](eventObj);
}
}
}
Questa è invece un utilizzo della classe EventProxy :
public function onLoad(Void):Void
{
categoryProxy = new EventProxy(this, "onCategorySelect");
category_cb.addEventListener("change", categoryProxy);
feedGridProxy = new EventProxy(this, "onFeedGridSelect");
feedDG.addEventListener("change", feedGridProxy);
}
//broadcast by ComboBox when user selects an item in the combo
private function onCategorySelect(eventObj:Object):Void
{
//ComboBox change event fired
}
//broadcast by the DataGrid when the user selects a row
private function onFeedGridSelect(eventObj:Object):Void
{
//datagrid change event fired
}
In pratica con il costruttore new è possibile definire un oggetto da una classe di tipo EventProxy che prende due parametri:
mioObjProxy = new EventProxy(receiverObj, myFunction);
-
receiverObj: l'oggetto dal quale l'evento viene chiamato
-
myFunction: il nome della funzione che viene eseguita in risposta all'evento
La classe mx.utils.Delegate viene importata nel nostro codice utlizzando il comando import con la seguente sintassi:
#import mx.utils.Delegate;
e viene lanciata direttamente dalla funzione addEventListener del component stesso:
mioCmp.addEventListener(evento,Delegate.create(scope,miaFunzione));
dove :
-
evento: è l'evento del component (per esempio "change")
-
scope: rappresenta il fattore scatenante dell'evento (per esempio "this")
-
miaFunzione: la funzione che verrà chiamata allo scattare dell'evento
In pratica questa nuova classe permette di definire una funzione eseguita al verificarsi di un certo evento. Ma c'è dell'altro, infatti la sintassi completa ci fa notare che:
static function create(scope:Object, handler:Function):Function
questa funzione ritorna a sua volta un'altra funzione che sarà usata per ricevere e procurare la diffusione dell'evento. Dopo tutta la teoria per spiegare concettualmente come funziona questa nuova classe facciamo un semplice ma efficace esempio pratico in cui utilizziamo un combobox component e a cui associamo una funziona allo scattare dell'evento change:
import mx.utils.Delegate;
mio_cb.addEventListener("change",Delegate.create(this,miaFunzione));
MioDataGrid.addEventListener("change",Delegate.create(this,miaFunzione2));
function miaFunzione(eventObj:Object):Void
{
trace(" Funziona :) ");
}
function miaFunzione2 (eventObj:Object):Void
{
trace(" Funziona davvero allora:) ");
}
Per rimuovere il Listener potete usare la seguente sintassi:
MioDataGrid.removeEventListener("change", dataGridDelegate);
Come avrete notato l'argomento sulla gestione degli eventi e quindi sull'Event Model è davvero molto vasto. Per questo ecco molti articoli con cui approfondire questo infinito viaggio alla scoperta della gestione degli eventi in Flash Mx 2004:
- Event Delegate Documentation
- Using Events with Components
- Il post di Samuel Neff's sulla listaFlashCoders sull'evento EventProxy class
- Mike Chambers' Event Proxy class
- The Many Faces of EventDispatcher
- The V2 Component Event Model Part 1 - EventDispatcher
- Grant Skinner's discussion of using EventDispatcher