Gestione degli eventi
con il modello a cascata
Riassumiamo brevemente
come avviene la gestione degli eventi con il modello a cascata utilizzato
nel JDK1.02 e precedenti. Questo modello degli eventi è utilizzato
principalmente per la gestione delle azioni su una interfaccia grafica.
In questo modello, i sensori degli eventi sono gli oggetti java.awt.Component
i quali possono distinguere due tipi di eventi:
eventi di azione
Quelli relativi ad una effettiva azione di un utente: pressione di un bottone,
invio in un TextField, scelta in una Choice, ecc.
altri tipi di
eventi Quelli relativi al solo movimento del mouse, scrittura all'interno
di un TextField, ecc.
Nel primo caso questo
porta ad
public
boolean action(Event e, Object who);
Esso contiene
due parametri. Il primo è di tipo java.awt.Event e contiene le informazioni
sul tipo di evento (proprietà id), sulla sorgente dello stesso (proprietà
target), eventuale posizione (proprietà x e y) in cui si è
verificato l'evento, ed altre dipendenti dal particolare tipo di evento.
Il secondo parametro è un oggetto caratteristico del tipo di evento.
Nel caso di
un evento del secondo tipo verrà invocato il seguente metodo:
public
boolean handleEvent(Event e);
che possiede un
parametro di tipo Event indicativo del tipo di evento, dello stesso tipo
del primo parametro del metodo action(). L'implementazione di default di
questo metodo esegue un semplice switch/case sull'id dell'evento e chiama
altri metodi di utilità che permettono la semplice gestione di particolari
eventi quali la gestione del mouse o della tastiera. Nel caso della pressione
del pulsante del mouse, ad esempio, esso richiama il metodo
public
boolean mouseDown(Event e, int x, int y);
Nel caso in cui
di volesse gestire solo questo evento, basterà ridefinire il metodo
appena descritto senza dover effettuarne la gestione dell'id..
Nella gestione
degli eventi secondo il metodo a cascata, assume molta importanza il valore
di ritorno dei due metodi descritti. Essi indicano, infatti, se l'evento
deve essere propagato dal componente sorgente dello stesso, ai componenti
che lo contengono. Nel caso in cui il valore di ritorno sia false, significa
che l'evento deve essere propagato al componente parent. Nel caso di valore
di ritorno true, significa che l'evento è stato completamente gestito
e che non deve essere propagato oltre.
Da questa ultima
considerazione si comprende come la gestione degli eventi con il modello
a cascata vada bene solamente nel caso in cui la propagazione di un evento
avvenga attraverso componenti contenuti l'uno nell'altro. Quando sono state
create le prime specifiche JavaBean ci si è subito accorti della
inadeguatezza del modello a cascata. Le specifiche JavaBean, infatti,
descrivono le regole per la realizzazione di componenti Java che devono
essere utilizzati così come sono senza modificarne il codice. Essi
sono caratterizzati da un insieme di proprietà, da eventi che possono
generare e da azioni che possono eseguire a seguito di un particolare evento.
Era necessario, quindi, un modello di gestione degli eventi che permettesse
la comunicazione tra componenti senza necessariamente rendere uno parte
dell'altro. Bisognava creare un modello che permettesse, la comunicazione
tra componenti senza modificarne il codice.
Il Delegation Model
Il modello degli
eventi per delega è stato introdotto nel JDK1.1 a seguito delle
specifiche JavaBean che descrivono le regole da seguire nella realizzazione
di un componente Java. Come sappiamo un componente si deve descrivere al
tool visuale che l'utilizza. L'operazione attraverso la quale il tool visuale
esamina il componente e ne determina le caratteristiche, in termini di
proprietà, eventi e metodi, si chiama introspection. Essa
può essere di due tipi:
-
esplicita : Nel
file .jar che contiene il Bean vengono fornite delle classi supplementari
(BeanInfo) che contengono informazioni esplicite sul Bean stesso. L'insieme
delle proprietà, degli eventi che il Bean può generare e
dei metodi che esso può eseguire, sono ottenuti esplicitamente da
queste classi di supporto creandone una istanza e richiamando opportuni
metodi. La descrizione è fornita utilizzando le classi relative
alla Reflection (package java.lang.reflect).
-
implicita : Non
vengono fornite classi di supporto ma le informazioni relative al Bean
sono ottenute semplicemente esaminando il bytecode della relativa classe.
Affinché questo sia possibile, la scrittura delle classi deve essere
fatta seguendo rigorosamente le regole descritte dalle specifiche JavaBeans.
Esisteranno, quindi, dei meccanismi per descrivere le proprietà,
eventi e metodi del Bean.
-
Inoltre esisteranno
delle regole per indicare che il Bean può generare un certo tipo
di eventi, che ne può ascoltare altri e che può eseguire,
in conseguenza, un certo tipo di azioni.
Per il nostro scopo
utilizziamo la seconda opzione. Creiamo una classe, che chiamiamo Timer,
che può generare un evento che si chiama Tic. La classe Timer permetterà
di decidere l'intervallo tra un evento ed il successivo (proprietà
delay), memorizzerà un contatore (tic) che incrementerà,
ad ogni passo, di una certa quantità (step). Concluderemo descrivendo
un utilizzo di Timer che evidenzia quelli che sono i reali vantaggi di
una gestione con Delegation Model..
I passi che
seguiremo sono i seguenti:
-
Creazione di un
oggetto che memorizza le informazioni dell'evento di Tic.
-
Creazione di un
meccanismo che permetta di indicare che una classe è una candidata
ascoltatrice del Timer.
-
Creazione
di un meccanismo di utilità per la notifica dell'evento da parte
della sorgente a tutti i suoi ascoltatori. Un oggetto di questo tipo si
identifica con il termine Support.
-
Creazione di un
meccanismo che permetta di indicare che un oggetto è sorgente di
un particolare evento.
-
Creazione della
classe sorgente (Timer).
-
Utilizzo della classe
Timer.
Creazione dell'oggetto
evento
Gli eventi si possono
classificare in due categorie a seconda di quale sia l'informazione che
essi devono trasmettere. Nel caso in cui interessi solamente il fatto che
un evento si sia verificato e se ne voglia conoscere la sorgente si ha
una Lightweight notification. Nel caso in cui l'evento debba essere accompagnato
da dati si parla di Statefull notification. Se pensiamo in termini di classi,
un evento Statefull si può considerare una estensione di un evento
di tipo LightWeight a cui sono state aggiunte altre proprietà o
metodi. Nel caso del Timer si ha un evento StateFull in quanto vogliamo
trasmettere agli ascoltatori anche il valore del conteggio (proprietà
tic) al momento della notifica.
Esaminiamo quindi
il seguente Listato3_9.
1:
package corso.swing.capitolo3;
2:
3:
4: import
java.util.EventObject;
5:
6: /**
7: * Questa
classe rappresenta un evento di Tic generato da
8: * un
Timer. Esso contiene le informazioni relative
9: * all'evento.
Essa estende, come stabilito dalle specifiche
10: *
JavaBeans, la classe <EM>java.util.EventObject</EM>
11: *
12: *@author
Massimo Carli
13: *@version
0.1 (09/02/1999)
14: */
15:
16: public
class TicEvent extends EventObject {
17:
18:
/*
19:
* Informazione associata all'evento ovvero il
20:
* valore di conteggio del Timer.
21:
*/
22:
private int tic;
23:
24:
25:
/**
26:
* Crea un evento di tic specificandone la sorgente. Il valore
27:
* associato al Timer è 0.
28:
*@param source Sorgente dell'evento di tic
29:
*/
30:
public TicEvent(Object source){
31:
this(source,0);
32:
}// fine
33:
34:
35:
/**
36:
* Crea un evento di tic specificandone la sorgente ed il valore
37:
* associato al conteggio del Timer
38:
*@param source Sorgente dell'evento di tic
39:
*@param tic Valore di conteggio del Timer
40:
*/
41:
public TicEvent(Object source, int tic){
42:
super(source);
43:
this.tic=tic;
44:
}// fine
45:
46:
47:
/**
48:
* Il valore del conteggio del Timer
49:
*/
50:
public int getTic(){
51:
return tic;
52:
}// fine
53:
54:
55: }//
fine
56:
Esaminiamo
la riga 16 che contiene l'intestazione della classe. Essa ci permette di
fare due importanti considerazioni. La prima riguarda il nome della classe
stessa: TicEvent. Anche se non obbligatorio, è bene chiamare
la classe che contiene le informazioni dell'evento, con un nome ottenuto
concatenando il nome dell'evento stesso (nel nostro caso Tic) alla parola
Event. Questo porterà, come vedremo meglio in seguito, dei vantaggi
in fase di programmazione. La seconda considerazione riguarda il fatto
che la nostra classe estende la classe java.util.EventObject. Questa classe,
che dal JDK1.1 fa parte delle core API, rappresenta proprio un evento di
tipo LightWeight. Essa contiene, infatti, solamente l'informazione relativa
alla sorgente dell'evento (proprietà source). Ereditare ogni classe
relativa ad un evento, direttamente o indirettamente, da una stessa classe
è un fatto utile anche ai tool di sviluppo visuali che possono assegnare
un oggetto evento ad una variabile di tipo EventObject. Il resto
della classe è abbastanza semplice e permette di memorizzare l'informazione
tic associata all'evento. Un altro motivo per cui si realizza una classe
che contiene tutte le informazioni associate ad un evento, permette anche
di notificare l'evento stesso attraverso la chiamata a metodi che hanno
un unico parametro. Eventuali altri parametri potrebbero, infatti, essere
incapsulati all'interno di una classe.
Creazione di un
meccanismo che permetta di indicare che una classe è una candidata
ascoltatrice del Timer
Arriviamo alla fase
fondamentale di tutto il Delegation Model. Come si fa a fare in modo che
classi diverse diventino candidate ad ascoltare un particolare tipo di
evento? Come si fa a fare in modo che classi diverse possano ascoltare
lo stesso tipo di evento ed eseguire, quando esso si verifica, azioni diverse?
Come possiamo fare in modo che la sorgente di un evento veda tutti i suoi
ascoltatori come oggetti dello stesso tipo? Le risposte a tutte queste
domande stanno nei termini interfacce e polimorfismo.
La notifica
da parte di una sorgente ad un ascoltatore può avvenire solamente
attraverso l'invocazione di un particolare metodo dell'ascoltatore stesso.
Esso avrà come parametro un oggetto di tipo corrispondente all'evento,
che ne conterrà tutte le informazioni. Alla sorgente non interessa
nulla relativamente al tipo della classe ascoltatrice ma è interessata
ai soli metodi che utilizza per la notifica dell'evento. Essa deve vedere
tutti gli ascoltatori come oggetti dello stesso tipo. Per quello che riguarda
la classe ascoltatrice, non è importante il suo tipo ma è
importante che essa disponga del metodo che la sorgente chiama per notificare
l'evento. Come è facile intuire si tratta di una questione di interfacce.
Se creiamo una interfaccia che descrive i metodi che sono chiamati da una
sorgente per la notifica di un evento, essa dovrà essere necessariamente
implementata dagli oggetti che intendono ascoltare l'evento stesso. Ricordiamo,
inoltre, che se una classe implementa una interfaccia ogni sua istanza
si può considerare di tipo corrispondente alla stessa interfaccia.
Ecco che anche la sorgente vede tutti gli ascoltatori come oggetti dello
stesso tipo perché implementano tutti la stessa interfaccia, ovvero
quella che prevede i metodi da chiamare per la notifica dell'evento. Ovviamente
ogni ascoltatore implementa l'interfaccia a modo proprio, per cui a seguito
di un evento, ogni ascoltatore può eseguire una azione diversa.
Quest'ultimo si chiama polimorfismo.
A questo punto
basterà creare l'interfaccia che sarà implementata da tutti
gli oggetti ascoltatori dell'evento. Vediamo quindi il Listato 3_10.
1:
package corso.swing.capitolo3;
2:
3:
4: import java.util.EventListener;
5:
6: /**
7: * Questa interfaccia sarà
implementata da tutti gli oggetti
8: * che vorranno essere ascoltatori
del Timer e notificati
9: * del verificarsi di un evento
di Tic. Essa estende
10: * l'interfaccia <EM>java.util.EventListener</EM>
che non
11: * prevede metodi ma che
identifica una interfaccia di
12: * ascolto di un evento.
13: *
14: *@author Massimo Carli
15: *@version 0.1 (09/02/1999)
16: */
17:
18:
19: public interface TicListener
extends EventListener {
20:
21:
22: /**
23: * Questo metodo viene
chiamato in tutti gli ascoltatori
24: * del Timer cui viene
passato un oggetto di tipo
25: * <EM>TicEvent</EM>
che contiene le informazioni associate
26: * all'evento.
27: *
28: *@param tic_event
Evento di Tic
29: */
30: public void tic(TicEvent
tic_event);
31:
32:
33: }//
fine interfaccia
Anche qui
facciamo alcune considerazioni sull'intestazione (riga 19). Come vediamo
il nome dell'interfaccia che deve essere implementata dai candidati ascoltatori
dell'evento Tic, si chiama TicListener. Come per la classe relativa all'evento,
anche in questo caso esiste una regola dei nomi. Il nome dell'interfaccia
si ottiene concatenando il nome dell'evento (Tic) alla parola Listener.
Questa regola, non è obbligatoria, ma se seguita permette di conoscere,
a partire dal nome dell'evento, il nome delle altre classi ed interfacce
implicate nella gestione dell'evento stesso. La seconda considerazione
riguarda il fatto che l'interfaccia TicListener estende l'interfaccia java.util.EventListener
che non prevede la definizione di alcun metodo. Non è raro in Java
trovare interfacce che non descrivono metodi (es: java.rmi.Remote, java.io.Serializable)
e che permettono di marcare in qualche modo alcune classi.
Alla riga 30
abbiamo finalmente la descrizione del metodo che sarà invocato dalla
sorgente dell'evento Tic per la notifica. Come vediamo esso prevede un
solo parametro di tipo TicEvent.
Creazione
di un meccanismo di utilità per la notifica dell'evento da parte
della sorgente a tutti i suoi ascoltatori.
Nei punti precedenti
abbiamo sempre parlato di oggetti candidati o possibili ascoltatori. Un
punto di forza del modello per delega è relativo al fatto che per
essere notificati di un evento, non basta implementare l'interfaccia relativa
ai suoi ascoltatori, ma bisogna informare la sorgente dell'effettivo interesse.
E' infatti compito della sorgente memorizzare tutti gli ascoltatori registrati
e notificarli quando si verifica l'evento. Come vedremo nel prossimo punto,
i servizi che caratterizzano la sorgente di un evento sono quelli che permettono
la registrazione o deregistrazione di un ascoltatore. Solitamente è
la stessa sorgente che gestisce la memorizzazione degli ascoltatori ed
il supporto alla notifica. Quando, però, uno stesso tipo di evento
può essere gestito da più tipi di sorgenti è bene
fornire una classe di supporto che solitamente ha un nome che si ottiene
concatenando il nome dell'evento a Support.
1:
package corso.swing.capitolo3;
2:
3:
4: import
java.util.*;
5:
6:
7: /**
8: * Questa
classe permette di gestire l'insieme degli ascoltatori
9: * di
un evento di Tic. Esso permette la memorizzazione degli
10: *
ascoltatori e gestisce la notifica di un evento di tic. La
11: *
creazione di questa classe risulta utile nel caso in cui
12: *
si volessero creare altre classi, diverse dalla classe Timer,
13: *
sorgenti di un evento di Tic.
14: *
15: *@author
Massimo Carli
16: *@version
0.1 (09/02/1999)
17: */
18:
19: public
class TicSupport {
20:
21:
/*
22:
* Insieme degli ascoltatori
23:
*/
24:
private Vector ascoltatori;
25:
26:
27:
/*
28:
* Sorgente dell'evento
29:
*/
30:
private Object source;
31:
32:
/**
33:
* Crea un gestore degli eventi di tic associato alla sorgente <EM>source</EM>
34:
*@param source Sorgente dell'evento
35:
*/
36:
public TicSupport(Object source){
37:
// Memorizziamo la sorgente
38:
this.source=source;
39:
// Inizializziamo il contenitore degli ascoltatori
40:
ascoltatori= new Vector();
41:
}// fine
42:
43:
/**
44:
* Permette l'aggiunta di un ascoltatore
45:
*@param listener Ascoltatore da aggiungere
46:
*/
47:
public void addTicListener(TicListener listener){
48:
ascoltatori.addElement(listener);
49:
}// fine
50:
51:
/**
52:
* Permette la rimozione di un ascoltatore
53:
*@param listener Ascoltatore da togliere dalla lista
54:
*/
55:
public void removeTicListener(TicListener listener){
56:
ascoltatori.removeElement(listener);
57:
}// fine
58:
59:
60:
/**
61:
* Questo metodo permette la notifica di un evento
62:
* a tutti gli ascoltatori.
63:
*@param tic Valore da associare all'evento
64:
*/
65:
public void fireTicEvent(int tic){
66:
// Creiamo l'evento da inviare agli ascoltatori
67:
TicEvent tic_event= new TicEvent(source,tic);
68:
/*
69:
* A questo punto congeliamo l'insieme degli ascoltatori
70:
* e ne facciamo una copia da utilizzare per la notifica.
71:
* Questo accorgimento è indispensabile per evitare che
72:
* durante la notifica un ascoltatre si aggiunga o rimuova
73:
* modificando la struttura ed il numero di elemento contenuti
74:
* nel contenitore.
75:
*/
76:
Vector copia_ascoltatori= new Vector();
77:
synchronized(ascoltatori){
78:
copia_ascoltatori=(Vector)(ascoltatori.clone());
79:
}// fine synchronized
80:
// Lavoriamo sulla copia
81:
for (int i=0;i<copia_ascoltatori.size();i++)
82:
((TicListener)(copia_ascoltatori.elementAt(i))).tic(tic_event);
83:
}// fine
84:
85:
86: }//
fine classe
Nel Listato
3_11 abbiamo, quindi, la classe TicSupport che fornisce un supporto alla
memorizzazione ed alla notifica di eventi di tipo TicEvent. Alla riga 24
definiamo il contenitore degli ascoltatori. Alla riga 30 memorizziamo un
riferimento alla sorgente dell'evento che utilizzeremo per creare gli oggetti
TicEvent da inviare agli ascoltatori. Il costruttore (riga 36) è
molto semplice e permette la memorizzazione della sorgente e l'inizializzazione
del contenitore degli ascoltatori. Alle righe 47 e 55 vi sono i metodi
che individuano la sorgente di un evento. Anche qui vale la regola dei
nomi: se l'evento si chiama Tic, l'interfaccia degli ascoltatori si chiamerà
TicListener ed i metodi per la registrazione o deregistrazione si chiameranno
rispettivamente addTicListener e removeTicListener. Sono proprio questi
due metodi che individuano, secondo le specifiche JavaBean, la sorgente
di un evento (in questo caso di nome Tic).
E' importantissimo
notare come il tipo degli ascoltatori da aggiungere o rimuovere sia quello
dell'interfaccia. In questi metodi si può anche osservare il motivo
dell'utilizzo di una istanza della classe java.util.Vector come contenitore.
Esso rappresenta un vettore a dimensione variabile e permette di utilizzare
metodi di nomi molto simili a quelli relativi agli ascoltatori per l'inserimento
e la rimozione. Per la notifica di un evento si utilizza il metodo fireTicEvent()
(riga 65). Esso dispone di un solo parametro che rappresenta l'informazione
da notificare, insieme alla sorgente dell'evento. Questo metodo viene chiamato
per notifica per cui bisognerà creare l'evento da inviare agli ascoltatori
(riga 67). Se il numero degli ascoltatori è elevato potrebbe capitare
che, nel periodo di notifica, uno o più ascoltatori si rimuovano
o aggiungano alla lista. Si procede, allora, facendo una copia dell'insieme
degli ascoltatori in una regione critica (accesso esclusivo all'insieme)
e lavorando successivamente sulla copia per la notifica. Notiamo come alla
riga 82, si faccia una operazione di cast con il tipo TicListener per poter
invocare il metodo tic() sugli ascoltatori. Una sorgente di un evento di
tipo Tic, utilizzerà un oggetto TicSupport per la memorizzazione
degli ascoltatori. Quando vorrà notificare l'evento, chiamerà
semplicemente il metodo fireTicEvent() comunicando il valore della proprietà
tic.
Creazione
di un meccanismo che permetta di indicare che un oggetto è sorgente
di un particolare evento.
Le regole descrivere
la sorgente di un evento sono già state descritte nel punto precedente.
Le specifiche JavaBeans dicono che se una classe descrive una sorgente
di un evento Tic dovrà disporre dei seguenti due metodi:
/**
* Permette l'aggiunta di un ascoltatore
*@param listener Ascoltatore da aggiungere
*/
public void addTicListener(TicListener listener){
ascoltatori.addElement(listener);
}// fine
/**
* Permette la rimozione di un ascoltatore
*@param listener Ascoltatore da togliere dalla lista
*/
public void removeTicListener(TicListener listener){
ascoltatori.removeElement(listener);
}// fine
E' bene notare
ancora che il tipo dei parametri è quello dell'interfaccia implementata
dagli ascoltatori. Tutti gli ascoltatori infatti, anche se istanze di classi
diverse, devono implementare tale interfaccia e disporre dei metodi utilizzati
dalla sorgente.
Creazione della
classe sorgente (Timer).
Per creare un Timer
(Listato 3_12) abbiamo implementato l'interfaccia Runnable ed implementato
il metodo run(). Esso deve semplicemente notificare un evento ad ogni intervallo
di delay millisecondi. Per quello che riguarda la gestione degli ascoltatori
e la notifica degli eventi, notiamo la definizione, alla riga 29, di un
oggetto di tipo TicSupport. I metodi che identificano Timer come sorgente
di un evento di Tic sono alle righe 115 e 123. Notiamo come questi metodi
non facciamo altro che richiamare gli omonimi metodi dell'oggetto TicSupport.
Inoltre notiamo, alla riga 87, che per notificare un evento e sufficiente
richiamare il metodo fireTicEvent() dell'oggetto TicSupport che si preoccuperà
della notifica.
1:
package corso.swing.capitolo3;
2:
3:
4: import
java.util.*;
5:
6:
7: /**
8: * Questa
classe rappresenta una possibile implementazione
9: * di
un Timer sorgente di eventi di <EM>tic</EM>. Esso
10: *
genera un evento ad intervalli regolari la cui durata è
11: *
indicata nel costruttore o modificata a runtime.
12: *
13: *@author
Massimo Carli
14: *@version
0.1 (09/02/1999)
15: */
16:
17: public
class Timer implements Runnable{
18:
19:
20:
/*
21:
* Durata in millisecondi tra un evento ed il successivo
22:
*/
23:
private int delay;
24:
25:
/*
26:
* Oggetto utilizzato per la memorizzazione degli ascoltatori
27:
* e perla notifica degli eventi.
28:
*/
29:
private TicSupport tic_support;
30:
31:
/*
32:
* Valore corrente del conteggio
33:
*/
34:
private int tic;
35:
36:
/*
37:
* Passo tra un tic ed il successivo
38:
*/
39:
private int step;
40:
41:
/*
42:
* Thread corrente. Questo serve per fare in modo che il Timer
43:
* a seguito di uno start dopo uno stop, riprenda i conteggio.
44:
* Sappiamo, infatti, che un Thread muore dopo uno stop() oppure
45:
* al termine del metodo run() che ne contiene il corpo.
46:
*/
47:
private Thread this_thread;
48:
49:
/**
50:
* Crea un Timer che genera un evento ogni secondo e che conta in avanti
di 1
51:
* partendo da 0.
52:
*/
53:
public Timer(){
54:
this(1000,0,1);
55:
}// fine
56:
57:
/**
58:
* Crea un Timer che genera un evento ogni <EM>delay</EM>
59:
* millisecondi e inizia un conteggio a partire dal valore di <EM>tic</EM>
60:
*@param delay Intervallo in millisecondi tra un evento ed il
successivo
61:
*@param tic Valore iniziale del conteggio
62:
*@param step Passo nel conteggio
63:
*/
64:
public Timer(int delay, int tic, int step){
65:
// Inizializziamo le proprietà interne
66:
this.delay=delay;
67:
this.tic=tic;
68:
this.step=step;
69:
// Inizializziamo this_thread a null
70:
this_thread=null;
71:
// Inizializziamo il gestore degli eventi
72:
tic_support= new TicSupport(this);
73:
}// fine
74:
75:
/**
76:
* Questo metodo rappresenta il corpo del Thread. Esso non fa altro
77:
* che generare un evento ogni <EM>delay</EM> millisecondi ed aggiungere
78:
* <EM>step</EM> a <EM>tic</EM>.
79:
*/
80:
public void run(){
81:
while(true){
82:
// Attendiamo delay
83:
try{Thread.sleep(delay);}catch(InterruptedException e){}
84:
// Aggiungiamo step a tic
85:
tic+=step;
86:
// Notifichiamo gli eventi
87:
tic_support.fireTicEvent(tic);
88:
}// fine while
89:
}// fine
90:
91:
/**
92:
* Permette la partenza del Timer
93:
*/
94:
public void start(){
95:
if(this_thread==null){
96:
this_thread= new Thread(this);
97:
this_thread.start();
98:
}// fine
99:
}// fine start
100:
101:
/**
102:
* Permette lo stop del Timer
103:
*/
104:
public void stop(){
105:
if(this_thread!=null){
106:
this_thread.stop();
107:
this_thread=null;
108:
}// fine
109:
}// fine stop
110:
111:
/**
112:
* Permette l'aggiunta di un ascoltatore
113:
*@param listener Ascoltatore da aggiungere
114:
*/
115:
public void addTicListener(TicListener listener){
116:
tic_support.addTicListener(listener);
117:
}// fine
118:
119:
/**
120:
* Permette la rimozione di un ascoltatore
121:
*@param listener Ascoltatore da togliere dalla lista
122:
*/
123:
public void removeTicListener(TicListener listener){
124:
tic_support.addTicListener(listener);
125:
}// fine
126:
127: }//
fine
Per completezza
diciamo che i metodi start() e stop() sono stati ridefiniti in modo tale
da poter riavviare il Timer con uno start() anche dopo uno stop(). Sappiamo
infatti, che uno un Thread muore quando finisce l'esecuzione del metodo
run() o quando viene invocato il metodo stop(). Questo è anche il
metodo del perché è stata implementata l'interfaccia Runnable
e non estesa la classe Thread.
Utilizzo della classe
Timer
Per verificare l'utilizzo
della classe Timer creiamo la classe ProvaTimer (Listato3_13) che dispone
del solo metodo main().
1:
package corso.swing.capitolo3;
2:
3: import
java.awt.Toolkit;
4:
5: /**
6: * Questa
applicazione permette di verificare l'utilizzo
7: * della
classe Timer. Essa fa uso delle classi interne.
8: *
9: *@author
Massimo Carli
10: *@version
v0.01 (17/02/1999)
11: */
12:
13:
14: public
class ProvaTimer {
15:
16:
/*
17:
* Quest metodo crea un Timer e registra due ascoltatori
18:
* anonimi che fanno due cose distinte alla notifica
19:
* dell'evento.
20:
*/
21:
public static void main(String[] args){
22:
// Creiamo l'oggetto Timer
23:
Timer timer= new Timer();
24:
/*
25:
* Registriamo un ascoltatore che stampa il valore di tic
26:
* ad ogni intervallo
27:
*/
28:
timer.addTicListener(new TicListener (){
29:
public void tic(TicEvent e){
30:
System.out.println("tic = "+e.getTic());
31:
}// fine tic
32:
});
33:
/*
34:
* Registriamo un ascoltatore anonimo che genera un bip
35:
* ad ogni intervallo
36:
*/
37:
timer.addTicListener(new TicListener (){
38:
public void tic(TicEvent e){
39:
Toolkit.getDefaultToolkit().beep();
40:
}// fine tic
41:
});
42:
/*
43:
* Facciamo partire il Timer
44:
*/
45:
timer.start();
46:
}// fine main
47:
48:
49: }//
fine classe
Alla riga
28 registriamo un primo ascoltatore del Timer attraverso una classe anonima.
Il compito di questo primo ascoltatore è quello di visualizzare,
sullo standard output, il valore di tic associato all'evento. Alla riga
37 registriamo, invece, un secondo ascoltatore che emette un beep ad ogni
evento. In questo esempio possiamo anche apprezzare la semplicità
e l'utilità delle classi anonime.
Eventi relativi
all'AWT ed eventi gestiti localmente
Fino ad ora abbiamo
parlato di sorgente ed ascoltatori come oggetti distinti. Nel caso in cui
un oggetto dovesse gestire un certo numero di eventi localmente,
un meccanismo come quello a cascata sarebbe molto comodo. Con la gestione
degli eventi per delega una soluzione potrebbe essere quella di registrare
un oggetto come ascoltatore di se stesso. Ovviamente anche la sorgente
di tale evento dovrebbe implementare l'interfaccia degli ascoltatori. Dal
JDK1.1, le classi relative all'interfaccia grafica, che quindi estendono
la classe java.awt.Component, dispongono di un meccanismo che permette
di indicare il loro interesse alla gestione locale di un certo numero di
eventi. Vediamo come.
E' stata creata
la classe java.awt.AWTEvent che raccoglie l'insieme degli eventi che possono
essere generati dai widget dell'AWT. Essa dispone, tra le altre cose, di
un insieme di costati statiche che identificano alcune classi di eventi
dell'AWT. Ad esempio, l'insieme degli eventi relativi ad eventi Action
vengono rappresentati dalla costante statica AWTEvent.ACTION_EVENT_MASK
di tipo long. Per l'abilitazione o disabilitazione di un certo tipo
di eventi basterà utilizzare i seguenti metodi :
protected
final void enableEvents(long eventsToEnable);
protected
final void disableEvents(long eventsToDisable);
dove eventToEnable
è uno dei valori statici della classe AWTEvent. Nel caso in cui
un componente volesse ascoltare un evento di tipo Action generato all'interno
dello stesso, basterà utilizzare l'istruzione :
enableEvents(AWTEvent.
ACTION_EVENT_MASK);
Dopo aver invocato
questa istruzione, il componente è in ascolto degli eventi di tipo
Action generati al suo interno. Serve, a questo punto, un metodo invocato
nel caso in cui si verifichi l'evento. A tal proposito esiste il seguente
metodo:
protected
void processEvent(AWTEvent e);
che ha funzione
analoga al vecchio handleEvent(). Esso viene invocato nel caso in cui si
verifichi uno degli eventi per cui il componente è stato abilitato
con il metodo enableEvents(). Il comportamento di default di questo metodo,
analogamente a quello che faceva il suo predecessore handleEvent(), è
quello di identificare il tipo di evento e di richiamare un altro metodo
caratteristico dello stesso. Per esempio, nel caso in cui sia stato abilitato,
in modo analogo a quanto visto prima, l'ascolto di eventi MouseMotion (relativi
al movimento del mouse ovvero alla costante statica AWTEvent.MOUSE_MOTION_MASK),
il metodo delegato da processEvent() sarà:
protected
void processMouseMotionEvent(MouseEvent e);
Dalle regole dei
nomi descritte in precedenza è abbastanza semplice vedere quali
sono i tipi di eventi supportati dai vari widget, quali sono le costanti
da utilizzare per la loro abilitazione locale, quali le interfacce da implementare
per esserne ascoltatori e quali metodi invocare per la registrazione. |