MokaByte Numero 30  - Maggio 1999
Corso di Swing
IV parte 
Il delegation model
di 
Massimo Carli
l Delegation Model Prima di entrare nel vivo degli argomenti fondamentali di Swing , alcuni concetti di base molto importanti. Questo mese il modello di gestione degli eventi


Dopo aver esaminato l'utilizzo delle inner classes, vediamo uno dei concetti fondamentali nella programmazione Java: la gestione degli eventi secondo il modello per delega (Delegation Model). In questo articolo  vedremo quelle che sono le regole principali descritte, a tale riguardo, nelle specifiche JavaBean, e le applicheremo ad un semplice esempio. Vedremo, inoltre, come l'utilizzo delle inner classes sia un ottimo strumento quando si lavora con un modello ad eventi del tipo Delegation Model. 

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. 

 
 

MokaByte rivista web su Java

MokaByte ricerca nuovi collaboratori
Chi volesse mettersi in contatto con noi può farlo scrivendo a mokainfo@mokabyte.it