In apertura del nostro settimo appuntamento dedicato alla programmazione Java è senza dubbio necessaria una premessa. Questo articolo è organizzato in maniera leggermente differente rispetto agli altri, dato che ho preferito inserire a questo punto del corso un argomento molto vasto (ci occuperemo di GUI) da affrontare quasi esclusivamente tranne la costruzione passo-passo di un’applicazione Java completa (benché per ovvi motivi semplici) di cui ovviamente è disponibile anche il codice sorgente. Da notare che in questa puntata ci occuperemo quasi esclusivamente dell’interfaccia grafica e di poche altre funzionalità, rimandando al lettore più intraprendente o alle prossime puntate la finalizzazione dell’applicazione. Questa impostazione ci permetterà di utilizzare in maniera fluida tutti i concetti fin qui appresi che sono, come già detto, la base della programmazione a oggetti.
Passo 1 – definizione di intenti
Come prima cosa quando ci si trova di fronte alla programmazione di un elemento software è necessario che sia chiaro cosa si vuole ottenere. Quello che ci proponiamo di ottenere è un semplice editor testuale (simile a note-pad di Microsoft tanto per intenderci, ma ancora più basilare). Per i nostri scopi non ci concentreremo granchè sull’implementazione di chissà quali funzionalità e programmeremo di fatto qualcosa di ancora più essenziale del note-pad stesso. La nostra attenzione è invece rivolta a capire come si utilizzano alcuni dei numerosissimi elementi grafici che Java ci mette a disposizione, lasciando al lettore più intraprendente, come già detto, il compito di sviluppare qualora lo desideri ulteriormente l’applicazione che verrà in ogni caso ripresa quando ci occuperemo di strema di I/O (input/output). Preme fare un paio di considerazioni:
1. Fino a qui non ci siamo occupati di come si gestisce il salvataggio/caricamento dati da file. Ce ne occuperemo in uno dei prossimi appuntamenti in maniera più approfondita, per ora è sufficiente cominciare a prendere contatto con questa realtà così come sarà presentata nel codice sorgente di questa esercitazione.
2. L’applicazione sarà prodotta e liberamente scaricabile come elemento stand-alone, la cui esecuzione sarà possibile sia tramite l’ambiente di sviluppo che direttamente dal sistema tramite file batch (per soli sistemi windows). Data la moltitudine di situazione differenti che potrebbero verificarsi, se dovessero esserci problemi rimandiamo al forum di discussione del sito per non appesantire ulteriormente questo spazio.
Per la stesura di questo semplice programmino utilizzeremo una minima parte dei metodi e delle classi che Java mette a disposizione. Per una conoscenza più approfondita rimandiamo come al solito alla documentazione ufficiale (e annesso tutorial) con riguardo particolare al package javax.swing.
Passo 2 – definizione della struttura
La nostra applicazione per essere eseguita ha ovviamente bisogno di essere implementata tramite classi. Il nostro progetto, benché non troppo complicato né elaborato, è sufficientemente esteso per dar luogo ad alcune riflessioni e per essere strutturato in maniera modulare. Prima di tutto, decidiamo di alleggerire il più possibile la classe che contiene il metodo main, e il metodo stesso. Questa è una operazione che si fa di frequente in Java, e permette di delegare la costruzione dell’interfaccia grafica ad altre classi. Cominciamo cioè col definire
– Una classe Jeditor
contenente il metodo main
– Una classe JeditorGui
che sarà in effetti la finestra principale del programma
La classe Jeditor
si deve occupare di creare una nuova istanza di JeditorGui
e visualizzarla sullo schermo.
Successivamente decidiamo che implementeremo anche
– Una classe JeditorAbout
per la gestione (a titolo d’esempio ovviamente) della finestra About del programma.
Passo 3 – iniziamo a scrivere codice
E’ evidente che il cuore dell’applicazione è la classe JeditorGui
e abbiamo detto che questa deve essere la finestra principale della nostra applicazione. La classe Java che meglio implementa per i nostri scopi il concetto di finestra è la classe JFrame
del package javax.swing. Questa non è l’unica classe che potremmo usare, dato che ad esempio esiste l’omologo Frame del package java.AWT (notate che questa è una caratteristica abbastanza comune, che si riscontra anche in altri componenti: l’aggiunta della J prima del nome serve a sottolineare che si tratta di classi del nuovo package swing, che è appunto scritto interamente in Java) oppure la classe JDialog
(e la precedente Dialog
, che vedremo più avanti in questa lezione) ma la JFrame
è esattamente la classe che fa al caso nostro poiché implementa la finestra di programma che sia anche riducibile a icona e la cui dimensione sia modificabile. In parole povere, se state leggendo queste pagine tramite un qualunque browser visuale JFrame implementa lo stesso tipo di finestra del browser che state usando!
Stabilito che JFrame è la classe che fa al caso nostro, decidiamo per sfruttare al meglio le proprietà del linguaggio e per ottenere un programma più pulito di fare in modo che JeditorGui erediti da JFrame: questo ci permetterà di gestire gli elementi da inserire nella finestra in maniera molto semplice. Come primo step otteniamo:
import javax.swing.*;
public class JeditorGui extends JFrame{
public JeditorGui(){
}
}
dove abbiamo predisposto il costruttore della classe perché in seguito ci tornerà molto comodo usarlo. Notate come abbiamo importato tutte le classi del package javax.swing e non solo JFrame. La scelta è dovuta al fatto che dato che stiamo scrivendo una classe che implementa una interfaccia grafica è scontato che di qui in avanti utilizzeremo altri elementi grafici! Nel corso di queste pagine non mi soffermerò sugli elementi AWT che sono per molti aspetti obsoleti (ma non per tutti, ad esempio non lo sono se volete trasferire un elemento Java su un cellulare compatibile) quindi utilizzeremo in larga misura le possibilità di swing. Continuiamo lo sviluppo di questa classe definendo alcune semplici proprietà, ad esempio la posizione, il titolo e le misure della nostra finestra.
public JeditorGui(){
super("Editor Java per IlSoftware.it");
setSize(600,400);
setLocation(200,200);
}
La prima riga sfrutta una interessante proprietà dell’ereditarietà in Java, cioè la possibilità all’interno della classe figlio di usare il costruttore del padre ma solo ed esclusivamente nella prima riga del costruttore. Uno dei (tanti) costruttori di JFrame accetta in ingresso una stringa che è proprio il parametro che definisce il titolo della finestra, quindi sfruttiamo volentieri questa proprietà e andiamo oltre. Anche setSize
e setLocation
della classe JFrame (che a sua volta li eredità) e possiamo quindi utilizzarli su un oggetto di tipo JeditorGui senza nessuna difficoltà. Per quanto riguarda setLocation()
è necessaria una piccola precisazione sul modello di coordinate usato da Java: l’angolo in alto a sinistra nello schermo corrisponde a O (0,0), il semiasse positivo delle x si trova alla destra di O e quello positivo delle y in basso rispetto ad O. Una figura in questo è probabilmente molto più esplicativa di molte parole:
L’area gialla nella figura rappresenta la nostra finestra e la parte grigia ovviamente lo schermo. Notate come con setLocation()
abbiamo indicato la posizione dello spigolo in alto a sinistra (le misure sono espresse in pixel).
Nel frattempo, cosa deve succedere nel main? Nel metodo main della classe Jeditor banalmente dovremmo avere qualcosa di questo tipo
JeditorGui editor = new JeditorGui();
editor.setVisible(true);
cioè la creazione di una nuova istanza di un oggetto di tipo Jeditor e la chiamata a setVisible (che prende in ingresso un booleano) per rendere la nostra finestra visibile sullo schermo (NB per chi ha già programmato Java: l’utilizzo di show() è divenuto deprecated, pertanto è più corretto usare setVisible come nell’esempio). Se avviate il programma la finestra dovrebbe venire correttamente visualizzata.
Creiamo la veste grafica della nostra applicazione
Passo 4 – primi elementi grafici sulla finestra principale
Facciamo il punto della situazione: finora abbiamo creato una finestra, che sarà la finestra principale del programma, al momento totalmente vuota e priva di alcuna utilità. E’ evidente che in un qualunque software anche elementare devono essere presenti degli elementi grafici che permettono l’interazione con l’utente e la visualizzazione di dati provenienti da una qualche sorgente di dati. In Java i componenti che si possono utilizzare si distinguono in due categorie
1. containers: si tratta di componenti che possono contenere altri componenti. La JFrame che abbiamo usato fino a qui è un buon esempio di container, ma potremmo citare anche JPanel e tutti i suoi derivati, solo per citare quelli più utilizzati
2. componenti: si tratta dei componenti veri e propri, ad esempi pulsanti (JButton), menu (JMenu e JMenuItem), caselle di scelta (JComboBox), tabelle (JTable) e molto altro ancora
Molti testi riportano in queste suddivisioni anche altre categorie, quali Layout Manager o Listener, ma noi scegliamo di occuparcene solo quando effettivamente ci saranno utili.
Il primo elemento che decidiamo di inserire nella nostra applicazione è il menu tipico delle applicazioni a finestra, per il momento con pochissime voci. Il risultato che vogliamo ottenere è infatti questo:
Esso si compone essenzialmente di elementi di tre tipi:
– una barra di menu, che contiene i menu File e Help, ed in Java si implementa tramite JMenuBar
– due menu, che hanno File e Help come etichetta, implementati tramite JMenu e che contengono le voci di menu
– le voci di menu vere e proprie, che in questo caso sono la voce Esci e la voce About, che appartengono a due menu diversi. Queste sono implementate tramite JMenuItem.
Vediamo il codice: come prima cosa, dichiariamo nell’area di dichiarazione delle variabili della classe JEditorGui le variabili di cui abbiamo bisogno:
private JMenuBar barra = new JMenuBar();
private JMenu file = new JMenu("File");
private JMenu help = new JMenu("Help");
private JMenuItem esci = new JMenuItem("Esci");
private JMenuItem about = new JMenuItem("About");
Notate come abbiamo dichiarato tutte le variabili private e che abbiamo usato costruttori che accettano come parametro la stringe che si va a visualizzare sui vari elementi.
Dopo aver dichiarato le variabili, siamo pronti ad usarle, e lo facciamo direttamente nel costruttore della finestra (in effetti potremmo farlo in un metodo appositamente definito).
public JeditorGui(){
super("Editor Java per Ilsoftware.it");
setSize(600,400);
setLocation(200,200);
file.add(esci);
help.add(about);
barra.add(file);
barra.add(help);
setJMenuBar(barra);
}
Come si vede dal codice, l’utilizzo di questi elementi è estremamente semplice: il metodo add
delle classi JMenu
e JMenuBar
ci permette semplicemente di aggiungere gli elementi nei rispettivi menu e i menu sulla barra, rispettando l’ordine con il quale li inseriamo. Il metodo setJMenuBar
fa invece parte ancora una volta della classe JFrame, e imposta per la nostra finestra la barra di menu: sarà poi cura direttamente dell’interprete posizionarla correttamente nella finestra.
Passo 5 – rendere l’applicazione windows-like o linux-like
I più attenti avranno certamente notato nelle immagini precedenti che il layout della finestra così come l’abbiamo ottenuto finora non è certamente uguale né al tipico layout presente nelle applicazioni a finestra di Microsoft Windows né al tipico layout delle applicazioni a finestra di GNU/Linux. Perché? Per rispondere dobbiamo ricordarci una delle caratteristiche peculiari di Java di cui ci siamo occupati direttamente nella prima puntata del nostro viaggio: Java è indipendente dalla piattaforma su cui gira, o in altre parole la stessa applicazione Java può funzionare su qualunque sistema su cui sia installata l’opportuna Virtual Machine. Questo fatto ha fatto in modo che gli ingegneri di Sun abbiano dovuto occuparsi di gestire layout diversi per sistemi diversi, ma che abbiano anche scelto di creare un layout detto “inter-piattaforma”, cioè un layout standard visualizzabile su tutte le piattaforme senza alcuna distinzione. Di default le applicazioni Java utilizzano proprio questo layout inter-piattaforma e non si adattano al sistema sul quale girano!
Una semplice soluzione consiste nel modificare leggermente il nostro main, come segue:
public static void main(String[] args) {
try{
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
JeditorGui editor = new JeditorGui();
editor.setVisible(true);
}
catch(Exception e){
}
}
La riga che ci interessa in maniera particolare è
In cui diciamo a Java di usare il Look And Feel (quello che finora nelle nostre descrizioni abbiamo chiamato con una certa approssimazione layout) predefinito nel sistema, tramite gli opportuni metodi della classe UIManager
(che fa parte di javax.swing, quindi ricordatevi di importarla se volete che il vostro codice sia correttamente compilato). Dato che il metodo getSystemLookAndFeelClassName()
può sollevare delle eccezioni, siamo costretti ad inserire il nostro codice all’interno di un blocco try-catch (vedi lezione precedente), nel quale non ho inserito alcuna gestione dell’eccezione per brevità, anche se sarebbe corretto farlo. Il risultato della nuova esecuzione del programma adesso è una finestra che si adatta allo stile del sistema sul quale gira. Su Microsoft Windows, ad esempio, abbiamo:
La nostra applicazione prende forma…
Passo 6 – ulteriori elementi grafici e primi listener
Definita la struttura del programma, la visualizzazione della finestra principale e il Look And Feel, cominciamo ad inserire un elemento tipico di un editor: l’area in cui inserire il testo. Il risultato che decidiamo di ottenere è un’area di testo con barra di scorrimento verticale. A questo punto è necessaria una precisazione: esistono in Java parecchi modi per gestire un’area di testo, quello utilizzato qui è semplicemente il più comodo per i nostri scopi attuali, ma non è detto che per altri scopi non sia meglio usare delle altre classi, tra le quali cito a titolo d’esempio JTextArea che è molto usata se non si hanno esigenze particolari e JPasswordField che fornisce le funzionalità per la gestione di un campo in cui inserire una password. Nel nostro caso, tuttavia, useremo la classe JEditorPane che fornisce le funzionalità per visualizzare file di testo di varia natura, compresi file html che possono essere aperti da una qualunque sorgente (anche internet). Abbiamo quindi:
con text/html
che rappresenta la modalità MIME dei testi che desideriamo visualizzare (il lettore che non ha nessuna conoscenza di cosa siano queste modalità non si preoccupi minimante, noi non ce ne occuperemo nuovamente, basti sapere che sono gli standard che definiscono i testi, per chi fosse interessato consiglio invece un’attenta lettura della documentazione della classe JEditorPane).
Per garantirci che l’area di testo sia visualizzata come ci siamo prefissi, sfruttiamo una notevolissima classe del package javax.swing, la classe JScrollPane che ha la possibilità di gestire praticamente qualunque gruppo di componenti Java e di garantirne la visibilità e l’uso delle barra di scorrimento secondo una politica gestita in maniera assolutamente semplice ma molto potente dal programmatore. Nel nostro caso abbiamo:
private JScrollPane panelTest = new JScrollPane(testo,
JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
Il costruttore che abbiamo utilizzato prende tre parametri:
1. il componente che desideriamo gestire tramite il JScrollPane
2. la costante (notare che è una costante static, e infatti accediamo tramite nomeClasse.nomeCostante) che definisce la politica sulla barra di scorrimento verticale
3. la stessa costante, ma riferita alla barra di scorrimento orizzontale.
L’elenco completo delle costanti disponibili per questa classe è molto semplice ed estremamente intuitivo, al punto che non si rende necessario alcun commento:
VERTICAL_SCROLLBAR_AS_NEEDED
HORIZONTAL_SCROLLBAR_AS_NEEDED
VERTICAL_SCROLLBAR_ALWAYS
HORIZONTAL_SCROLLBAR_ALWAYS
VERTICAL_SCROLLBAR_NEVER
HORIZONTAL_SCROLLBAR_NEVER
Infine, ci rimane da aggiungere il panelTest alla finestra, e questo lo facciamo nella maniera standard:
cioè aggiungiamo al pannello di sfondo della finestra, e non alla finestra stessa (questa è una direttiva di Sun, se provate ad usare direttamente add noterete che è deprecated).
La nostra applicazione comincia a prendere forma:
Ma non è ancora in grado di reagire a nessun comando impartito dall’utente. Questo perché non abbiamo inserito alcuna funzionalità in grado di reagire agli eventi che si verificano quando l’applicazione è in esecuzione, come ad esempio un nostro click su esci o su about. In Java gli elementi in grado di intercettare gli eventi sono i cosiddetti Listener, e sono definiti da interface (quindi non si tratta di classi, almeno non nei casi che ci riguarderanno). Questo significa che dobbiamo definire una classe che erediti dall’interface che ci interessa e poi implementare obbligatoriamente i suoi metodi all’interno della stessa classe!
Occupiamoci a titolo d’esempio della gestione degli eventi dell’elemento esci, un JMenuItem che in Java si può trattare in maniera molto semplice alla stregua di un semplice pulsante. Nel nostro caso, la scelta migliore ricade nell’utilizzo dell’interface ActionListener che fa parte del package java.awt.event che dobbiamo quindi importare nel nostro codice. Dato che la nostra applicazione è abbastanza semplice decidiamo che sia la stessa classe che gestisce la finestra a gestire gli eventi dei menu, ma questa scelta sarebbe da rivedere per progetti più complicati e flessibili. La dichiarazione della classe è quindi:
A questo punto dobbiamo specificare che l’elemento esci utilizza come Listener per intercettare i propri eventi la classe JEditorGui, e questo lo possiamo fare semplicemente con
sempre all’interno del costruttore della classe JEditorGui. Notate l’utilizzo di this che si riferisce alla corrente istanza della classe JEditorGui.
Infine, inseriamo il codice del metodo che dobbiamo eseguire quando si clicca su esci:
public void actionPerformed (ActionEvent e){
System.exit(0);
}
in cui System.exit(0)
termina semplicemente l’applicazione. Un modo più elegante potrebbe essere il seguente:
//Pannello per chiedere conferma
int a = pane.showConfirmDialog(null,"Uscire dal programma?", "Vuoi veramente uscire?",
JOptionPane.YES_NO_OPTION,JOptionPane.QUESTION_MESSAGE);
if(a==JOptionPane.OK_OPTION)
{
System.exit(0);
}
in cui pane è definito
Lasciamo al lettore il compito di verificare l’effetto ottenuto e qualora lo desideri di sbizzarrirsi con le costanti elencate nella documentazione ufficiale relativa a JOptionPane che permette di ottenere una serie di finestre veramente molto numerosa.
Passo 7 – Cambio del colore di sfondo
Come già abbondantemente detto nella parte introduttiva, non ci interessa in questa sede occuparci della scrittura di tutti i metodi che regolano l’attività di un editor, ma è comunque utile dare uno sguardo a come è possibile gestire almeno una delle funzionalità seppur in maniera molto basilare. Si tratta del cambio del colore di sfondo, e nel nostro esempio sarà possibile esclusivamente cambiare il colore di sfondo in rosso: si lascia al lettore il compito di estendere questa funzionalità a tutti i colori (esiste un comodo pannello predefinito di Java) e si rimanda al forum di discussione per eventuali richieste.
Per prima cosa, aggiungiamo una toolbar alla nostra finestra, cioè una di quelle barre tipicamente presenti nelle applicazioni che usiamo quotidianamente che contiene uno o più pulsanti fruibili quindi molto rapidamente. La classe JToolBar
ci viene in questo caso d’aiuto:
private JToolBar toolbar = new JToolBar("toolbar1");
private JButton red = new JButton("Red");
aggiungiamo il pulsante alla toolbar e settiamo le sue proprietà per renderla graficamente gradevole
//setting della toolbar e aggiunta alla gui
toolbar.setBorderPainted(true);
toolbar.add(red);
red.addActionListener(this);
toolbar.setFloatable(false);
toolbar.setRollover(true);
Come avete notato, ho deciso di usare sempre la stessa classe per gestire il listener del pulsante, ma è ovviamente necessario stabilire all’interno del metodo ActionListener
quale azione compiere in base a quale elemento ha prodotto l’evento click. Ci viene incontro il metodo getSource()
, in questo modo:
public void actionPerformed (ActionEvent e){
if (e.getSource()==esci){
//Pannello per chiedere conferma
int a = pane.showConfirmDialog(null,"Uscire dal programma?", "Vuoi veramente uscire?",
JOptionPane.YES_NO_OPTION,JOptionPane.QUESTION_MESSAGE);
if(a==JOptionPane.OK_OPTION)
{
System.exit(0);
}
}
if (e.getSource()==red){
testo.setBackground(Color.RED);
}
}
Passo 8 – Considerazioni finali
Come avete avuto modo di osservazione, la costruzione di un’interfaccia grafica in Java è un procedimento che non presenta alcuna difficoltà particolare, sebbene per come l’abbiamo impostato richieda un po’ di tempo. E’ ovviamente possibile usare degli ambienti grafici che sveltiscono molto il lavoro, ma per scopi didattici è più utile dare uno sguardo al codice, come abbiamo fatto in queste pagine. In questo modo sarà possibile usare coscientemente anche gli ambienti grafici, in virtù del fatto che diversamente si rischia di rimanere fortemente disorientati dalla mole di codice che un software come JBuilder (a titolo d’esempio) costruire.
Come abbiamo più volte ripetuto, riprenderemo in seguito lo sviluppo di questa semplice applicazione, della quale potete scaricare i sorgenti da qui, un file zip che contiene anche i files di progetto per jcreator. Rispetto a quanto detto in queste pagine, i sorgenti contengono anche un’ulteriore classe, cioè quella che definisce la finestra di about, nell’intento di mostrare al lettore come gestire una finestra di tipo un po’ diverso, una JDialog.
Arrivederci alla prossima puntata.