MokaByte
Numero 24 - Novembre 98
|
|||
|
|||
Massimo Carli |
(II parte) |
||
Lo scorso mese abbiamo introdotto le Swing attraverso una breve storia di Java e delle classi relative alla gestione dell'interfaccia grafica: le AWT (Abstract Window Toolkit). Abbiamo visto che i problemi relativi alla visualizzazione di widget nativi (peer) è stata risolta creando il LightWeight Component del JDK1.1, e costruendo su di esso la totale serie di componenti (Button , TextField, ecc.) cui siamo abituati, attraverso del codice Java puro. Questo ha permesso di risolvere il problema del diverso comportamento dei vari peer nelle diverse implementazioni della JVM (Java Virtual Machine). A partire da una stessa tavolozza, tutti i componenti vengono disegnati allo stesso modo per cui dovranno apparire uguali in tutte le diverse implementazioni di JVM. Questo mese esamineremo nel dettaglio l'architettura alla base delle Swing ovvero il modello MVC(Model View Controller) esaminandolo in pratica nell'oggetto com.sun.java.swing.JButton.
Look And Feel
Lo slegarsi dai
peer ci permette anche di svincolare le apparenze (il Look) dei vari componenti
dalla piattaforma. Prima di questo nuovo approccio non potevamo avere il
bottone di Windows95 in ambiente Solaris in quanto il peer apparteneva
a Windows95. Adesso è possibile visualizzare il bottone di Windows95,
in ogni altro ambiente. Il bottone non è più un peer ma è
realizzato attraverso del codice Java puro, portabile per definizione (a
meno di errori nelle implementazioni della JVM). In un qualunque ambiente
possiamo modificare il modo con cui un bottone è visualizzato semplicemente
modificando il suo codice.
Il diverso modo
con cui un componente appare si indica con il temine di Look. Le azioni
che si possono eseguire su un componente sono spesso legate al Look. Un
bottone rotondo potrà essere cliccato in punti diversi rispetto
ad un bottone rettangolare e viceversa. Ecco che si parla di LookAndFeel
(L&F) ovvero della caratteristica di un componente di essere visualizzato
e di captare le azioni che vengono fatte su di esso. Visualizzare una interfaccia
grafica in modi diversi significa utilizzare per essa diversi L&F.
Possiamo quindi affermare che il L&F è una parte di codice responsabile
della visualizzazione di una particolare interfaccia grafica (GUI) e della
gestione degli eventi su di essa. Se al L&F è delegata la visualizzazione
e la gestione degli eventi di ciascun componente, e se esso è codice
Java puro, dovrà essere possibile passare da un modo di gestione
ad un altro in modo quasi automatico in RunTime. Ecco che si parla di Pluggable
Look And Feel (PL&F). Per vedere come questo è ottenuto nelle
Swing studiamo l'architettura MVC.
Model View Controller
Per comprendere come le Swing gestiscono il PL&F è bene introdurre l'architettura MVC (Model View Controller). Essa è stata introdotta come parte della versione Smalltalk-80 del linguaggio di programmazione Smalltalk (un linguaggio object oriented abbastanza popolare inventato da Alan Kay) per facilitare la programmazione in sistemi che hanno rappresentazioni multiple e sincronizzate di uno stesso insieme di dati. La caratteristica principale di questa architettura è che le sue varie parti, che sono il modello, i controller e le view, sono trattate come entità distinte e che una qualunque variazione fatta al modello da parte di un qualunque controller, si deve riflettere immediatamente in tutte le view. In pratica le view devono esprimere in ogni istante lo stato del modello. Il MVC ha i seguenti vantaggi:
Esistono delle API (Application Program Interface) ben definite che, se opportunamente utilizzate, fanno in modo che la modifica del codice relativo ad un componente non abbia come conseguenza la modifica di un qualunque altro componente.
Il collegamento tra il modello e il view (l'interfaccia grafica per intenderci) avviene a runtime e non in fase di compilazione.
* Il modello è quell'oggetto che rappresenta i dati nel programma. Esso gestisce i dati e ne regola l'elaborazione. Il modello non è a conoscenza dei suoi controller e tanto meno delle sue view; non contiene riferimenti ad essi. Piuttosto, è il sistema che si prende la responsabilità di mantenere i link tra il modello e le sue view e di notificare a queste ultime le variazioni nei dati del modello.
* Le view sono quegli oggetti che gestiscono la rappresentazione visuale dei dati del modello. Esse producono la rappresentazione visuale dell'oggetto modello e visualizza i dati allo user. Esso interagisce con il modello attraverso un riferimento ad esso.
* Il controller permette all'utente di interagire con l'ambiente modificando i dati contenuti nell'oggetto modello. Esso è responsabile, in sintesi, delle operazioni di interazione dell'utente con il modello e della conseguente modifica della rappresentazione attraverso le view. Esso interagisce con il modello attraverso un riferimento ad esso.
Appare quindi
evidente il legame tra una architettura MVC ed il PL&F che si vuole
ottenere con le Swing. MVC e le Swing.
Abbiamo visto
che una caratteristica degli oggetti swing è quella che si riassume
nei termini "pluggable look-and-feel" che permette di ottenere un maggiore
affidabilità e consistenza delle applicazioni ed applet nelle varie
piattaforme. Esso fornisce un potente meccanismo per individuare la rappresentazione
visuale più adatta di un componente senza dover necessariamente
ereditare l'intero componente utilizzato fino a quel momento nella stessa
applicazione. Con il PL&F è possibile fare in modo che l'utente
modifichi l'aspetto visual dell'applicazione o applet a runtime, senza
il bisogno di riavviare l'applicazione stessa ed utilizzando sempre un
aspetto famigliare. Questa caratteristica è particolarmente utile
a coloro che andranno ad utilizzare Network Computer che potranno scegliere
l'aspetto dell'applicazione a loro più semplice.
Cosa fondamentale
diventa, inoltre, la possibilità di integrare, senza notevoli sforzi,
nuovi stili di L&F adattabili ai componenti già creati e disponibili.
La capacità di passare velocemente da un L&F ad un altro è
il risultato della implementazione delle MVC. Esse, infatti, dividono ciascun
componente nelle suddette tre parti:
NOTA: In una prossima puntata vedremo quello che si chiama Delegation Model, ovvero il modello di gestione degli eventi introdotto nel JDK1.1 a seguito delle specifiche JavaBeans e di fondamentale importanza in quasi tutte le nuove API . Per ora si pensi ad un modello che prevede la definizione di una sorgente di un evento e di una serie di oggetti interessati allo stesso. Questi ultimi vengono chiamati ascoltatori (Listener) dell'evento. Il Delegation Model ci mette a disposizione un formalismo per definire un determinato tipo di evento, per identificare una sorgente per quell'evento, per identificare i possibili oggetti interessati all'evento ed un meccanismo per informare la sorgente dell'interessamento degli ascoltatori all'evento. Per ora diciamo che se l'oggetto A vuole ascoltare un evento generato da un oggetto B, A si deve registrare come ascoltatore di B il quale provvederà alla notifica del particolare evento attraverso un metodo che l'oggetto A deve implementare.Abbiamo detto che il delegate è espressione del modello in ogni istante. Dai concetti esposti nella Nota si capisce che il modello deve essere informato di azioni sulla View che possano modificarne lo stato. Abbiamo quindi la necessità di rendere il modello ascoltatore della parte di Controller. La parte di View deve essere sempre espressione del modello per cui dovrà essere ascoltatrice del modello. Ecco che l'utilizzo del Delegation Model diventa addirittura cosa scontata nella direzione Model-Delegate. Per comprendere a fondo questi concetti alla base di ogni componente Swing, esaminiamo un esempio ormai classico: il bottone.
Un esempio: La classe con.sun.java.swing.JButton
Per capire bene il funzionamento del modello MVC modificato utilizzato nelle Swing e per capire a fondo il funzionamento del PL&F, esaminiamo l'insieme delle classi ed interfacce relative alla realizzazione di un pulsante che andrà a sostituire l'ormai datato oggetto java.awt.Button comunque ancora presente. Nella seguente spiegazione facciamo riferimento alla versione JDK1.2beta4 che utilizzeremo anche durante il corso per non incorrere in problemi di incompatibilità di codice nelle diverse puntate del corso.
Definizione
del Modello
Il primo passo
consiste nella creazione di un modello. Anche nelle Swing, come per i JavaBean,
esistono delle regole per i nomi delle varie classi ed interfacce. Se l'oggetto
in questione è il Button, il modello sarà una interfaccia
di nome com.sun.java.swing.ButtonModel. Ogni particolare modello dell'oggetto
che andremo a creare, sarà istanza di una classe che implementa
questa interfaccia. L'utilizzo delle interfacce è quasi d'obbligo
in situazioni di questo tipo in cui si da importanza primaria ai servizi
che una classe deve fornire piuttosto che al come questi servizi vengano
implementati. Nel caso specifico ogni modello del Button dovrà fornire
informazioni relativamente a: stato:
Ogni modello
deve fornire gli strumenti affinché sia possibile accedere allo
stato del bottone. Esistono quindi i seguenti metodi dell'interfaccia
public boolean isArmed(); |
Devono essere
disponibili anche i metodi per modificare lo stato del pulsante. Questi
saranno i metodi utilizzati dal delegate (o meglio la sua parte di controller)
per modificare lo stato del modello a seguito di un evento dell'utente.
public void setArmed(boolean b); |
Già qui si può notare come siano state utilizzati i metodi set e get caratteristici dei JavaBean. Infatti, ogni oggetto Swing è un JavaBean (ci mancherebbe altro...).
Interazione
con il delegate
Ogni modello
deve poter essere ascoltato. Un suo ascoltatore sarà ovviamente
il delegate che in base allo stato del modello, provvederà alla
corrispondente visualizzazione. Ogni modello dovrà quindi fornire
gli strumenti affinché un suo candidato ascoltatore (nel senso che
implementa l'interfaccia com.sun.java.swing.event.ChangeListener)possa
registrarsi ad esso e quindi ascoltarlo. Avremo quindi i seguenti metodi:
public void addChangeListener(ChangeListener listener); |
Altri metodi
di varia utilità
L'interfaccia
che stiamo analizzando contiene anche altre informazioni relative al pulsante.
Non le esaminiamo nel dettaglio in quanto non riguardano in senso stretto
quello che è il PL&F.
Creazione di un modello di default.
Quando si crea
una interfaccia relativa ad un modello, se ne fornisce solitamente una
implementazione di default che, secondo le regole dei nomi utilizzate da
Java, si chiamerà com.sun.java.swing.DefaultButtonModel. L'utilità
di questa classe è maggiore di quello che si possa pensare. Senza
di essa, la creazione di un nuovo modello implicherebbe la scrittura del
corpo di ogni metodo descritto dalla interfaccia. Con questa classe, invece,
che fornisce già una implementazione della interfaccia, basterà
estenderla e ridefinire i metodi che servono. Ogni modello dovrà,
infatti, avere le proprietà relative ai set e get previsti dal modello,
delle costanti per identificare ogni singolo stato, e dovrà gestire
i suoi ascoltatori nel modo classico dei JavaBeans. Nel prossimo articolo,
quando parleremo del Delegation Model applicato alle Swing e delle Inner
Classes, esamineremo nel dettaglio questo aspetto.
La cosa importante
da sottolineare è il fatto che ogni modello del nostro Button, sarà
difficilmente una ulteriore implementazione della interfaccia com.sun.java.swing.ButtonModel,
ma sarà semplicemente una estensione della classe com.sun.java.swing.SimpleButtonModel.
Creazione
del Delegate
Arriviamo quindi
alla parte più interessante e che ci riguarda maggiormente ovvero
la parte di visualizzazione e di gestione degli eventi. Seguendo le regole
dei nomi, se l'oggetto si chiama Button, il delegate si chiama ButtonUI.
Se cerchiamo la classe ButtonUI tra quelle disponibili nella documentazione
non la troviamo, perché? Questa classe appartiene al package com.sun.java.swing.plaf
anch'esso non presente nella documentazione. Ma cosa significa plaf? Ebbene
si, significa Pluggable Look and Feel.
Possiamo fare
subito una osservazione: il delegate è una classe e non un'interfaccia.
Questo è conseguenza del fatto che indipendentemente da come un
componente venga visualizzato, sicuramente questo deve comunicare con un
modello, deve registrarsi ad esso, ascoltarlo e modificare il suo look
di conseguenza. Indipendentemente da come viene visualizzato il componente
a seconda del valore del modello, il delegate deve, ascoltando gli eventi
generati dall'utente, agire sul modello stesso. Notiamo l'esistenza di
un meccanismo comunque comune a tutti i modi di visualizzazione di un componente.
Come conseguenza di questo, si ottiene una classe astratta. E' importante
capire che i delegate (le classi UI) sono classi astratte perché
implementano il meccanismo di notifica al modello degli eventi degli utenti
ed il meccanismo di ascolto del modello ma lasciano indefinito il come
sono gestiti gli eventi dell'utente e del come deve avvenire la visualizzazione
a seguito della modifica del modello. Esse descrivono che a seguito di
un evento di un utente deve essere avvisato il modello e che a seguito
della modifica di un modello bisogna visualizzare l'oggetto in modo diverso.
La decisione relativa a quando notificare un evento dell'utente al modello
(esempio del bottone rotondo), e di come visualizzare il componente, è
lasciata alla classe che estende la relativa classe UI. Nel caso del Button
diciamo che la decisione di quando considerare effettivo un evento dell'utente
(es: il click del mouse) e quindi notificarlo al modello, e di come visualizzare
il Button a seguito di questo evento, e lasciato alla classe che estende
la classe astratta com.sun.java.swing.plaf.ButtonUI. Per creare un nuovo
modo di visualizzare e gestire il componente, e quindi un nuovo L&F
basterà estendere in modo diverso la classe astratta com.sun.java.swing.plaf.ButtonUI.
Il Componente:
la classe
com.sun.java.swing.JButton attraverso un riferimento al modello ed un riferimento
al delegate gestirà la visualizzazione e la gestione del componente.
Notiamo subito che modificando il modello è possibile avere pulsanti
con funzionamenti diversi utilizzando sempre la stessa rappresentazione
grafica (delegate). Basterà utilizzare i metodi:
public void setModel(ButtonModel model); |
Analogamente
è possibile cambiare il delegate attraverso i seguenti metodi:
public void setUI(con.sun.java.swing.plaf.ButtonUI ui); |
NOTA: Il JButton è stato preso come esempio per spiegare il funzionamento dei vari componenti Swing. Nel caso del Button, è stata creata una classe di nome com.sun.java.swing.AbstractButton che riunisce le caratteristiche comuni a più classi tra cui il JMenuItem, JToggleItem che vedremo durante il corso. Questo perché se ci pensiamo bene un pulsante classico ed un CheckBox o RadioButton di differente hanno solamente il delegate e non il modello.
Gli altri componenti?
Nel precedente
paragrafo abbiamo visto come è possibile modificare il modello (
e quindi il funzionamento) ed il delegate (e quindi la visualizzazione)
di un Button. Questo procedimento è valido per ogni componente Swing.
Cambiare il L&F di un componente significa modificare il suo delegate
e questo è possibile in runtime attraverso il metodo setUI() visto
prima. Se vogliamo, però, modificare il L&F di tutti i componenti
Swing serve un meccanismo più semplice in modo da poter, con una
sola istruzione, cambiare il L&F di ogni componente di una applicazione.
A tale scopo sono state introdotte le classi LookAndFeel e UIManager del
package com.sun.java.swing.
La classe com.sun.java.swing.LookAndFeel
è una classe che permette di fare una semplice associazione tra
quello che è un componente e il corrispondente delegate attualmente
in uso. Questo significa che in corrispondenza del JButton di prima, la
classe com.sun.java.swing.LookAndFeel associa la estensione della classe
ButtonUI da considerare come delegate attuale. Il cambiamento del L&F
corrisponde nel creare un nuovo insieme di associazioni e quindi una nuova
classe LookAndFeel. La chiave deve comunque essere la stessa in modo che
se si vuole il delegate relativo al Button si chiederà sempre il
nome della classe relativa ad una stessa chiave, che per default è
proprio la stringa "ButtonUI". La chiave relativa ad un componente Swing
(se parliamo di chiavi ci accorgiamo subito che la classe LookAndFeel non
è altro che una Hashtable) è il nome della classe astratta
UI relativo.
Ora, chi memorizza
quello che è il LookAndFeel attuale a cui chiedere il delegate da
utilizzare da parte di un componente Swing all'atto della sua creazione?
Ecco che è stata creata la classe UIManager. Attraverso uno dei
metodi:
public void setLookAndFeel(LookAndFeel laf); |
è possibile
modificare il L&F in runtime. Esistono due versioni del metodo a seconda
che si voglia specificare la particolare classe LookAndFeel (estensione,
diretta o indiretta, della predefinita) oppure il nome della classe. Ecco
ottenuto il PL&F.
Conclusioni
Questo mese abbiamo
visto nel dettaglio che cosa è il PL&F attraverso lo studio
della architettura MVC e di come questa si può applicare al mondo
Swing. Dal prossimo mese inizieremo finalmente a vedere del codice il cui
utilizzo risulterà sicuramente più chiaro dopo la fatica
di questi due mesi. Il prossimo mese, studieremo per bene il modello per
delega ed introdurremo le Inner Classes utilizzate molto spesso nelle Swing.
MokaByte Web 1998 - www.mokabyte.it MokaByte ricerca nuovi collaboratori. Chi volesse mettersi in contatto con noi può farlo scrivendo a mokainfo@mokabyte.it |