MokaByte
Numero 05 - Febbraio 1997
|
|||
|
CON IL JDK 1.1 |
||
Massimo Carli |
|||
Come
detto nell'articolo introduttivo delle nuove caratteristiche del JDK1.1,
la gestione degli eventi ha subito una grossa modifica rispetto alle versioni
precedenti. Le modifiche sono state introdotte per ottenere una migliore
gestione degli eventi in applicazioni Java che iniziano ad essere di una
certa consistenza, e per adattarsi a quella che è la filosofia di
modularità sottolineata nelle specifiche JavaBean a cui la JDK1.1
si riferisce. Differenze JDK 1.02 e JDK 1.1 Ricordiamo brevemente come
avveniva la gestione degli eventi nelle versioni del JDK precedenti la
1.1. Avevamo che ogni oggetto che derivava dalla classe Component, ereditava
da essa i metodi action e handleEvent più una serie di metodi per
la gestione di particolari azioni quali quelle relative al mouse. La gestione
degli eventi avveniva in modo gerarchico. Se un oggetto, per esempio un
Button, voleva gestire gli eventi in locale, cioè non lasciarlo
fare al Component da cui deriva, doveva definire i metodi opportuni.
Per esempio
si definiva la action o, in generale, la handleEvent distinguendo i comportamenti
successivi all'evento in dipendenza dell'evento stesso. Sappiamo che i
metodi relativi alla gestione degli eventi ritornano un valore boolean.
Se il valore ritornato era true, significava che l'evento era stato completamente
gestito e non si voleva che anche oggetti genitori lo gestissero. Se, invece,
si voleva che l'evento del Button, per esempio, fosse gestito anche dal
Component bastava ritornare il valore false.
Come notiamo
la gestione degli eventi avveniva in modo gerarchico. Si può subito
vedere che una gestione degli eventi in quel modo è un po' limitata.
Vi potrebbero essere, infatti, altri oggetti che vogliono "ascoltare" gli
eventi di un Button, per esempio, senza essere Component, Container o altro.
Vogliono solo sapere se il Button è stato premuto. Da qui la necessità
di una gestione migliore. Inoltre si esigeva un modello che permettesse
una più completa modularità degli oggetti, ovvero fare in
modo che ciascun Bean offrisse una interfaccia il più possibile
standard con l'esterno.
Diciamo, inoltre,
che la gestione attraverso i metodi action ed handleEvent necessita, talvolta,
di numerosi test per l'identificazione dell'oggetto che ha generato l'evento
con, quindi, maggiori possibilità di errore. La versione 1.1 permette,
inoltre, anche il filtraggio degli eventi, cosa problematica nella gestione
precedente.
Sottolineo che
la gestione degli eventi utilizzata nella versione 1.1 non offre nulla
di nuovo rispetto alle precedenti nel senso del funzionamento. Non esiste,
cioè, cosa che si può realizzare adesso che non si poteva
realizzare prima. I vantaggi, notevoli e fondamentali, stanno nel modo
in cui questo si può ottenere. Si ha una maggiore facilità
di gestione degli eventi e si rendono i programmi aperti a modifiche grosse
nella funzionalità con poche modifiche nel codice. La cosa vi assicuro
non è da poco. Le nuove specifiche sono solo una gestione degli
eventi conseguenza della esperienza maturata dai programmatori Sun. Quando
ho letto queste specifiche mi sono chiesto perchè non le avevo utilizzate
prima. In verità lo avevo fatto.
Ricordate che
nelle versioni precedenti, la classe Dialog aveva un problema legato alla
sua modalità. Il fatto che una finestra di dialogo sia modale significa
che fino a che essa è aperta, o in funzione, non si poteva accedere
alle altre. Questo non funzionava completamente in quanto non si poteva
sì accedere agli altri Frame, ma il programma non si fermava in
attesa che la finestra si chiudesse, ma proseguiva eseguendo le istruzioni
seguenti la show(). Se la finestra di dialogo doveva ritornare dei valori
inseriti attraverso di essa, tali valori erano elaborati subito dopo la
visualizzazione della finestra di dialogo e quindi senza dare il tempo
per l'inserimento. Bisognava, allora, utilizzare un altro stratagemma.
Ho allora creato
una interfaccia che ho chiamato Risultati con un unico metodo raccogli.
Tale interfaccia era implemetata da ogni oggetto che utilizzava la finestra
di dialogo per la raccolta di dati. Si procedeva così. Quando si
volevano inserire i valori attraverso la finestra di dialogo, la si visualizzava
semplicemente. Quando i dato erano inseriti, si premeva un pulsante OK.
La Dialog, allora, non faceva altro che chiamare il metodo raccogli del
padre che implementava l'interfaccia Risultati passando il nuovo oggetto
i cui valori erano stati inseriti attraverso la Dialog. Era, allora, nella
raccogli del frame padre che gestivo i dati. Come vedremo questo procedimento
si avvicina molto alla gestione degli eventi che descriveremo. Il tutto
sarà legato all'uso delle interfacce ed alla definizione di opportuni
metodi che le interfacce prevedono. Quello che si passerà attraverso
questi metodi, sarà un oggetto di un nuovo tipo che conterrà
le informazioni legate all'evento, alla sua sorgente ed altre. Andiamo,
comunque, per ordine.
La gestione
degli eventi con il JDK 1.1 Iniziamo, quindi, l'esposizione della gestione
degli eventi prevista nel JDK 1.1 e nelle specifiche JavaBean. Questo modello
ha le seguenti caratteristiche:
E' molto semplice
e facile da imparare Permette una separazione tra quella che è la
progettazione del programma e la stesura del codice utilizzando la potenza
delle interfacce. Permette una gestione degli eventi più robusta,
meno portata ad errori. Permette una gestione degli eventi più flessibile.
Permette una definizione più accurata delle interfacce che il Bean
offre all'esterno E' compatibile con le versioni precedenti. La gestione
degli eventi secondo le specifiche JavaBean si rifanno ad un modello frequente
in Java: quello di produttore-consumatore o sorgente-ascoltatore. In questo
caso si utilizza la seconda versione in quanto l'evento è generato
da una sorgente di eventi, ed è sentito ed elaborato da uno o più
ascoltatori. I termini di sorgente e di ascoltatore, come vedremo, non
sono a caso. Descriviamo il modello attraverso una serie di domande risposte:
Come fa una sorgente di un evento a comunicare ad un ascoltatore che l'evento
si è verificato?
Semplice, chiamerà
un particolare metodo dell'oggetto ascoltatore che lo avvisa dell'accaduto
e che farà le opportune azioni relative all'ascoltatore stesso.
Come si fa a distinguere i vari eventi? Come si fa a dire ad un ascoltatore
che tipo di evento si è verificato? Ciascun tipo di evento prevede
la chiamata di un particolare metodo, implementato dall'ascoltatore, associato
all'evento stesso.
Ma come si fa
a sapere che l'ascoltatore ha tale metodo? Ogni oggetto ascoltatore di
un evento o di un gruppo di eventi, implementa una particolare interfaccia
che deriva dalla interfaccia java.util.EventListener e che prevede la gestione
di un certo gruppo di eventi. Notiamo che ognuno di questi oggetti ascoltatori
è un oggetto di tipo EventListener perchè implementa tale
interfaccia. Quindi, per dire che il mio oggetto è interessato ad
un particolare insieme di eventi, devo implementare l'interfaccia che prevede
la gestione degli eventi associati? Si. Saranno create interfaccie, che
derivano dalla EventListener, per ogni gruppo di eventi che si vogliono
ascoltare.
Il criterio
del legame di tali eventi è, per esempio, il fatto che tutti si
riferiscano a movimenti del mouse. L'oggetto che implementa tale interfaccia
realizzerà i metodi previsti dall'interfaccia stessa, con le operazioni
da eseguire quando tali eventi si verificheranno.
Ma dove racchiudo
le informazioni relative all'evento che si è verificato? Tutti i
metodi previsti dalle interfacce che derivano dalla EventListener, devono
avere un solo argomento. Questo argomento racchiude tutte le informazioni
associate all'evento che si è verificato.
Ognuno di questi
è un oggetto che deriva dalla classe java.util.EventObject. Ma se
metto più di un argomento nei metodi relativi ad un evento, ho un
errore di compilazione? NO. Come vedremo anche per le sorgenti di eventi,
le JavaBean danno delle regole cui gli sviluppatori devono adattarsi per
mantenere una certa uniformità. Queste regole sono importanti nel
caso della introspection dei Bean. Per instrospection si intende quel procedimento
che permette di conoscere le interfacce che il Bean mette a disposizione,
osservando il Bean stesso.
Esistono due
modi per fare questo. Uno è detto esplicito e prevede l'implementazione
di una interfaccia che prevede un metodo per la descrizione delle proprietà
del Bean stesso. La seconda, invece, avviene osservando le regole di cui
si parlava prima. Per esempio, supponiamo di avere un Bean con una proprietà
che chiamiamo numero. Se volessimo dire ad un tool che edita il Bean, che
numero è una proprietà editabile, esistono due metodi. Il
primo, esplicito, consiste nel descrivere la proprietà numero come
editabile nel metodo descrittivo del Bean (semplice no?).
La seconda è
quella legata alle convenzioni. Se la proprietà numero è
editabile avrà due metodi i cui nomi saranno getNumero() e setNumero().
La presenza della versione get ci dice che numero può essere letto.
La presenza della versione set ci dice che numero può essere scritto.
Se vi fosse solo la get significherebbe che numero non potrà essere
modificato. La stessa cosa avviene per la gestione degli eventi. Se non
si seguono determinate regole nella programmazione si rinuncia a questo
secondo metodo di introspection. Conviene, quindi, stare alle regole.
Notiamo, anche,
che tutte le informazioni che si metterebbero come secondo o terzo parametro,
possono essere inglobate in un unico oggetto che deriva da EventObject,
quindi il problema di un argomento in più è un falso problema.
Finora si è parlato solo degli ascoltatori di un evento.
Ma come si definiscono
le sorgenti? Anche qui ritorniamo al concetto precedente. Qui non esiste
una interfaccia che permetta di dire che un oggetto è sorgente per
un certo gruppo di eventi. Supponiamo di voler creare una sorgente per
un gruppo di eventi che chiamiamo succede. Creeremo l'interfaccia SuccedeListener,
dalla EventListener, con la gestione degli eventi relativi a succede. Il
parametro che utilizziamo lo deriviamo dalla EventObject e lo chiamiamo
SuccedeEvent. Eccoci alla sorgente. Per dire che essa è sorgente
per il gruppo di eventi SuccedeListener, dobbiamo prevedere i seguenti
due metodi di registrazione:
addSuccedeListener( SuccedeListener sl);
removeSuccedeListener(SuccedeListener sl);
Questi due metodi
permettono di informare la sorgente di quelli che sono gli oggetti che
ascoltano particolari tipi di eventi. Con add si aggiunge un oggetto ascoltatore
a tale lista, e con remove si toglie. Notiamo che l'argomento è
un oggetto di tipo SuccedeListener cioè che implementa la omonima
interfaccia, e quindi che ha i metodi per la gestione degli eventi corrispondenti.
La cosa fondamentale è la gestione dei nomi utilizzati per la creazione
di tutte le classi ed interfacce.
A questo punto
le cose dovrebbero essere abbastanza chiare.
Per approfondire
i concetti esposti diamo un occhiata alla documentazione delle classi del
JDK1.1.
Nel package
java.util notiamo la interfaccia EventListener. Notiamo come essa non abbia
nessun metodo da definire, serve solo come raccoglitore delle classi che
implementano interfacce per ascoltare eventi. L'uso di interfacce senza
metodi, con lo scopo di marcare classi, non è rara in Java. Se guardiamo
la serializzazione, notiamo che la classe Serializable ha una funzione
analoga, stessa cosa per l'interfaccia Remote per la gestione del RMI (Remote
Method Invocation). Andiamo nel package java.awt.event. Notiamo la presenza
di interfacce il cui nome finisce per Listener.
Queste sono
tutte interfacce che derivano dalla interfaccia java.util.EventListener.
Infatti, la gestione dei gruppi di eventi più comuni, sono state
realizzate e messe nel JDK. Se andiamo a vedere ciascuna di queste classi,
notiamo la presenza di metodi per la gestione degli eventi. La cosa ancora
più importante è il parametro che ciascuno di essi ha. Notiamo
che esso è uno solo ed ha nome che soddisfa alle regole precedentemente
dette. Esaminiamo l'interfaccia più comune e forse più utile
in quanto gestisce i movimenti del mouse : MouseListener. I metodi dell'interfaccia
sono i seguenti:
mouseClicked(MouseEvent me) ;
mouseEntered(MouseEvent me);
mouseExited(MouseEvent me) ;
mousePressed(MouseEvent me) ;
mouseReleased(MouseEvent me) ;
Notiamo
come tutti gli eventi legati al mouse siano raggruppati in questa interfaccia.
Una cosa da andare a vedere è la classe MouseEvent. Essa appartiene
al package java.awt.event e deriva, ovviamente, dalla classe java.util.EventObject.
Se guardiamo la gerarchia delle classi notiamo che quella relativa agli
eventi del mouse deriva dalla EventObject attraverso una lunga serie di
classi. E' spesso utile, infatti, raggruppare classi con funzioni simili,
e questo è quello che si fa. Ma esaminiamo bene la classe EventObject
che è comune a tutti gli oggetti relativi ad un evento. Quando succede
qualcosa è bene saperne la causa. Ogni oggetto EventObject dispone
di un campo source che conterrà la sorgente dell'evento. Notiamo
come essa sia di tipo Object, cioè del tipo più generale
possibile. Saranno poi, in relazione alla applicazione specifica, realizzate
le opportune conversione con operazioni di cast. Una cosa che, ai più
attenti, appare strana è la presenza di un nuovo modificatore applicato
a source. Esso è il modificatore transient.
Vedremo meglio
questo modificatore quando parleremo della serializzazione degli oggetti
oppure dell'uso del RMI. Diamone, comunque, una descrizione. Supponiamo
di voler rendere un oggetto persistente (altro nuovo termine in Java).
Rendere un oggetto persistente significa fare in modo che esso esista anche
dopo il termine dell'esecuzione del programma che lo ha creato o che lo
utilizza.
Questo significa che una volta spento il programma, l'oggetto esiste ancora e può essere ricreato, uguale, in una esecuzione successiva dell'applicazione. Per fare questo vedremo come utilizzare la serializzazione. Diciamo solo che essa mi permetterà di salvare su uno stream, in generale, e quindi su un file, un oggetto per poi ripristinarlo successivamente. Un oggetto con queste caratteristiche si dice serializzabile e implementerà, come vedremo, la classe java.io.Serializable.
Però
non sempre si vogliono salvare tutte le proprietà dell'oggetto stesso.
Alcune dovranno, all'atto del ripristino, essere ricreate esattamente come
erano, ma altre possono essere create anche assumendo dei valori di default.
Queste ultime si dicono transient. Se guardiamo la classe EventObject,
notiamo che essa implementa Serializable.
Il campo source
sarà transient per il semplice fatto che non ha molto senso memorizzare
la sorgente di un evento per gestirlo in un'altra sessione. Sarebbe come
premere il bottone del mouse e gestire l'evento in una esecuzione futura
dell'applicazione. La cosa è, peraltro, difficile dato che un evento
viene subito gestito o quasi. Esistono poi i due metodi getSource()
e toString() il cui significato è ovvio. Come visto le informazioni
generali a tutti gli eventi sono legate alla sorgente dell'evento verificato.
Ora proviamo a creare un semplice oggetto che vuole gestire gli eventi
del mouse. Supponiamo di estendere un oggetto Frame ma esplicitiamo il
tutto relativamente agli eventi. Supponiamo di creare un oggetto che ascolta
il mouse e che stampa un messaggio in corrispondenza dell'evento verificato
e che lo smista agli oggetti che lo vogliono ascoltare da lui. La cosa
è molto semplice ma permette di vedere bene il funzionamento degli
eventi nel JDK1.1.
Chiamiamo la nostra classe Sensor. Si ha:
import
java.awt.event.*; // Gestione
degli eventi public class
Sensor extends Frame implements MouseListener {
// Costruttore
public Sensor(){
super();
resize(200,200);
show();
}// fine Costruttore
public void mouseClicked(MouseEvente){
System.out.println("MOUSE CLICKED");
}// fine mouseClicked
public void mousePressed(MouseEvent e){
System.out.println("MOUSEPRESSED");
}// fine mouseClicked
public void mouseReleased(MouseEvente){
System.out.println("MOUSE RELEASED");
}// fine mouseClicked
public void mouseEntered(MouseEvent e){
System.out.println("MOUSEENTERED");
}// fine mouseClicked
public void mouseExited(MouseEvente){
System.out.println("MOUSE EXITED");
}// fine mouseClicked
public static void main(String argv[]){
Sensor s=new Sensor();
}// fine main
}// fine classe Sensor
super.addMouseListener(this);
Il programma
diventa allora il seguente:
import
java.awt.event.*; // Gestione degli
eventi
public
class Sensor extends Frame implements MouseListener {
// Costruttore
public Sensor(){
super();
super.addMouseListener(this);
resize(200,200);
show();
}// fine Costruttore
public void mouseClicked(MouseEvent e){
System.out.println("MOUSE CLICKED");
}// fine
mouseClicked
public void mousePressed(MouseEvent e){
System.out.println("MOUSE PRESSED");
}// fine
mouseClicked
public void mouseReleased(MouseEvent e){
System.out.println("MOUSE RELEASED");
}// fine
mouseClicked
public void mouseEntered(MouseEvent e){
System.out.println("MOUSE ENTERED");
}// fine
mouseClicked
public void mouseExited(MouseEvent e){
System.out.println("MOUSE EXITED");
}// fine
mouseClicked
public static void main(String argv[]){
Sensor s=new Sensor();
}// fine
main
}// fine classe Sensor
protected void processEvent(AWTEvent e);
La
funzione di questo metodo è molto semplice ma importante. Esso ha
quasi la stessa funzione del vecchio handleEvent(). Il suo compito è
quello di raccogliere gli eventi che si sono verificati e di smistarli
agli oggetti ascoltatori che si sono registrati. Questo metodo è
ridefinibile. Nel caso lo facessimo, dobbiamo comunque ricordarci di chiamare
sempre quello del parent perchè il lavoro di smistamento sia effettivamente
fatto oltre al lavoro introdotto nella ridefinizione. Consideriamo allora
il seguente listato
import
java.awt.event.*; // Gestione degli
eventi
public
class Sensor extends Frame implements MouseListener {
// Costruttore
public Sensor(){
super();
resize(200,200);
show();
}// fine Costruttore
public void mouseClicked(MouseEvent e){
System.out.println("MOUSE CLICKED");
}// fine
mouseClicked
public void mousePressed(MouseEvent e){
System.out.println("MOUSE PRESSED");
}// fine
mouseClicked
public void mouseReleased(MouseEvent e){
System.out.println("MOUSE RELEASED");
}// fine
mouseClicked
public void mouseEntered(MouseEvent e){
System.out.println("MOUSE ENTERED");
}// fine
mouseClicked
public void mouseExited(MouseEvent e){
System.out.println("MOUSE EXITED");
}// fine
mouseClicked
protected void processEvent(AWTEvent e){
System.out.println("SIAMO NELLA PROCESSEVENT");
}// fine
processEvent
public static void main(String argv[]){
Sensor s=new Sensor();
}// fine
main
}// fine classe Sensor
protected final void enableEvents(long eventsToEnable);
protected final void disableEvents(long eventsToDisable);
che permettono
di abilitare o meno un gruppo di eventi. Notiamo cone essi siano final
nel senso che non si possono ridefinire. Infatti non ve ne è la
necessità. L'argomento dei due metodi è un long che si ottiene
esaminando la classe java.awt.AWTEvent. Essa dispone di tutte le maschere
per la abilitazione o meno dei gruppi di eventi. Nel nostro caso basterà
aggiungere, nel costruttore, la seguente riga
enableEvents(AWTEvent.MOUSE_EVENT_MASK);
in modo da abilitare
gli eventi del mouse. La nostra classe diventa allora:
import
java.awt.event.*; // Gestione degli
eventi
public
class Sensor extends Frame implements MouseListener {
// Costruttore
public Sensor(){
super();
enableEvents(AWTEvent.MOUSE_EVENT_MASK);
resize(200,200);
show();
}// fine Costruttore
public void mouseClicked(MouseEvent e){
System.out.println("MOUSE CLICKED");
}// fine
mouseClicked
public void mousePressed(MouseEvent e){
System.out.println("MOUSE PRESSED");
}// fine
mouseClicked
public void mouseReleased(MouseEvent e){
System.out.println("MOUSE RELEASED");
}// fine
mouseClicked
public void mouseEntered(MouseEvent e){
System.out.println("MOUSE ENTERED");
}// fine
mouseClicked
public void mouseExited(MouseEvent e){
System.out.println("MOUSE EXITED");
}// fine
mouseClicked
protected void processEvent(AWTEvent e){
System.out.println("SIAMO NELLA PROCESSEVENT");
}// fine
processEvent
public static void main(String argv[]){
Sensor s=new Sensor();
}// fine
main
}// fine classe Sensor
CONCLUSIONI
Penso che gli
esempi introdotti siano una buona base per apprendere i nuovi concetti
del JDK 1.1. E' bene,fin da subito, adottare questa gestione durante la
programmazione.
Uno svantaggio
si potrebbe verificare negli applet. Essi, infatti, possono gestire gli
eventi in questo modo solo se interpretati con appletviewer. Infatti i
browser non sono ancora adattati alla JDK1.1 e molte delle classi per tale
gestione appartengono solo al JDK1.1.
Comunque questo
è un problema che si risolverà presto, o almeno lo spero.
|
||
|
||
MokaByte ricerca
nuovi collaboratori
|
||
|