MokaByte Numero 24  -  Novembre 98
Corso Swing
di 
Massimo Carli
Prosegue il  corso sui nuovi componenti grafici di Java.
(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:

L'architettura Model/View/Controller permette la progettazione e la programmazione dei vari componenti in modo indipendente tra loro collegandoli solamente in runtime. Se un componente dovesse diventare inutilizzabile, può essere sostituito senza influire sugli altri componenti.  Ma cosa sono in pratica il Model, le View ed i Controller?

* 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:

    Modello : contiene le informazioni relative allo stato del componente
    View : rappresentazione visuale del modello (look)
    Controller : gestione degli eventi
Sebbene una completa modularità tra le tre parti sia vantaggiosa non è molto semplice da raggiungere. Non è, infatti, semplice realizzare un Controller in modo indipendente dal modulo View su cui l'utente agisce. Risulta più semplice unire le parti di View e di Controller. Questa scelta è stata fatta anche dai progettisti delle Swing creando un nuovo componente che si chiama Delegate. L'architettura MVC in ambito Swing diventa una architettura composta di due parti:
    Modello : contiene la logica dell'oggetto
    Delegate : ad esso è delegata la visualizzazione e la gestione degli eventi
Anche in questo MVC-Modificato il Delegate deve essere in ogni istante, espressione del modello. Per affrontare il passo successivo vediamo la seguente Nota:
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();

public boolean isEnabled();

public boolean isPressed();

public boolean isRollover();

public boolean isSelected();

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);

public void setEnabled(boolean b);

public void setPressed(boolean b);

public void setRollover(boolean b);

public void setSelected(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); 
public void removeChangeListener(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); 
public ButtonModel getModel();

Analogamente è possibile cambiare il delegate attraverso i seguenti metodi:
 
public void setUI(con.sun.java.swing.plaf.ButtonUI ui); 
public con.sun.java.swing.plaf.ButtonUI getUI();

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); 
public void setLookAndFeel(String classname);

è 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