|
Java Tutorial - Parte 2 0.1
Un tutorial per esempi
|
In questa versione della app cominceremo ad affrontare l'argomento che più interessa il lettore: la programmazione GUI e scriveremo le classi che gestiscono le sequenze (la solution e tutte le possibili guess) con i tre tipi di simboli gestiti dalla app: NUMBER, LETTERS e COLORS. Scriveremo anche una app di test per verificare che la grafica del pannello sia appropriata e che le classi che gestiscono le sequenze ed il pannello delle sequenze si comportino come atteso.
Per la versione 0.2 creiamo due nuove sottocartelle che rappresentano i due nuovi packages della applicazione:
mastermind.gui che raggruppa le classi che gestiscono la interfaccia grafica mastermind.app che raggruppa le classi che gestiscono la applicazione principaleI files che analizzeremo in questo capitolo sono i seguenti:
| nome file | package | descrizione |
|---|---|---|
| SequenceGUI.java | mastermind.gui | classe base delle sequenze nella GUI |
| SequenceNumbers.java | mastermind.gui | classe specializzata per il tipo NUMBERS |
| SequenceLetters.java | mastermind.gui | classe specializzata per il tipo LETTERS |
| SequenceColors.java | mastermind.gui | classe specializzata per il tipo COLORS |
| SequencePanel.java | mastermind.gui | classe che visualizza il pannello sequenze |
| Main.java | mastermind.app | la classe principale (entry-point) |
| Main02.java | mastermind.app | la classe principale versione 0.2.0 |
Un pannello delle sequenze viene rappresentatoto dalla classe SequencePanel ed esso contiene la solution nella prima riga e le guess nelle righe successive. Il numero di righe successive a quella della solution è determinato dalla proprietà della applicazione maxTries (vedi La classe MMProperties). Graficamente, il pannello delle sequenze appare così:
Ogni riga del pannello sequenze è composta da una sequenza di simboli e da un bottone di comando sulla destra che nella riga della solution viene chiamato Done (=fatto, ho scelto la sequenza segreta) mentre nelle righe delle guess viene chiamato Check (=controlla, verifica i risultati della mia ipotesi).
Lo so, esteticamente quei bottoni sono uno schifo ma ... pazienza, rimedieremo subito, già nel prossimo capitolo.
La classe SequencePanel possiede diversi membri dati; alcuni sono costanti che definiscono i colori dei vari componenti:
panelBackground: il colore di fondo del pannello delle sequenze blacksColor e whitesColor: sono i colori dei punti rossi e bianchi ottenuti nel confronto tra la solution e la guess sequenceColors: è una array di due elementi che contiene un colore chiaro ed uno scuro usatgi come colore di sfondo e di primo piano per i simboli delle sequenzeInfine, vengono definiti i membri dati che contengono le sequenze, le guess ed i bottoni di comando:
solution: la sequenza della solution guesses: le 9 sequenze delle guess results: le 9 labels dei risultati del confronto checks: i 9 bottoni "Check" che attivano la verifica dei risultatiCosa succede se viene raggiunto il numero massimo di tentativi per indovinare la soluzione dell'avversario? Su questo punto le regole del mastermind non sono uniformi in tutto il mondo: in questa versione del gioco, se entrambi i giocatori non riescono a pervenire alla solution entro il numero massimo di tentativi, la partita si considera "patta" (vedi Le regole del gioco).
Il pannello delle sequenze viene costruito con i seguenti argomenti:
k: un intero che rappresenta la lunghezza di una sequenza l: il listener degli eventi per i bottoni Done e Check numGuess il numero di righe delle guess che corrisponde al numero massimo di tentativi per giocatore oltre il quale la partita si considera patta (per maggiori info vedi I parametri di gioco)Nel costruttore il pannello delle sequenze inzializza tutti i suoi membri dati interni, li stessi passati come argomenti ma non crea il pannello GUI vero e proprio: per ottenere il componente GUI da inserire nel frame si deve richiamare il metodo createPanel.
Da notare che è possibile richiamare createPanel più di una volta ma il pannello restituito è sempre lo stesso, quello creato la prima volta.
Nella immagine che mostra il pannello delle sequenze (vedi Il pannello delle sequenze) possiamo facilmente notare che i simboli e i bottoni di comando sono disposti in una griglia di 10 righe e 4 colonne: il numero di righe è uguale al numero massimo di guess più uno mentre il numero delle colonne è uguale a k più uno.
Per inserire le sequenze nella griglia viene usato un metodo specifico della classe SequenceGUI: questo perchè il disegno dei simboli nella sequenza dipende dal tipo di simbolo e, poichè ogni classe derivata da SequenceGUI ha i propri metodi specializzati per disegnare i simboli, è opportuno usare il metodo specifico delle sequenze piuttosto che gestire i diversi tipi di simboli nel pannello delle sequenze:
Gli argomento passati al metodo SequenceGUI.addToPane sono, rispettivamente, il pannello dove la sequenza deve essere aggiunta e la riga nella griglia di layout che occuperà la sequenza: la solution occupa la riga ZERO mentre le guess occuperanno le righe da 1 a 9. Il numero di riga nella griglia viene usato per determinare i colori di sfondo e primo piano delle sequenze. Vengono definiti due colori, uno chiaro ed uno scuro:
Gli argomenti alla classe Color sono numeri reali nel range che và da 0.0 a 1.0 e rappresentano la intensità dei tre colori fondamentali: rosso, verde e blu.
la strategia usata nel pannello delle sequenze è quella di disegnare la sequenza della solution con un colore di primo piano scuro su sfondo chiaro e le sequenze delle guess con un colore di primo piano chiaro su sfondo scuro: in pratica i due colori (sfondo e primo piano) vengono scambiati tra la solution e le guess.
La classe SequenceGUI è quella destinata a gestire una sequenza grafica. Benchè una sequenza in questo Mastermind viene rappresentata da un oggetto di classe String, non è raccomandato usare questa classe per gestire le sequenze nella GUI.
Il problema è che la classe String è immutabile: questa affermazione vi sembrerà strana dal momento che la classe String possiede metodi per modificare la stringa contenuta in una sua istanza come per esempio il metodo strip() che rimuove tutti i caratteri whitespace all'inizio ed alla fine della stringa.
Ebbene, in realtà il metodo strip() non modifica affatto l'oggetto sul quale viene richiamato ma, invece, crea una nuova istanza della classe String e copia la stringa originale modificata:
Poichè nella GUI del Mastermind le sequenze vengono create aggiungendo un simbolo alla volta, possono essere cancellate ed ancora è possibile togliere l'ultimo simbolo digitato, finiremo col creare molte inutili istanze della classe String e questo potrebbe determinare un rallentamento delle operazioni.
Il linguaggio Java non è un linguaggio lento, anzi; pur non essendo compilato in codice nativo, Java si è dimostrato piuttosto veloce (vedi Java Tutorial Parte prima - Test di velocità: Java vs C). Cionostante, non è una buona idea usare la classe String per gestire le sequenze nella GUI: abbiamo strumenti ben più efficaci.
In fondo, una stringa non è altro che una array di caratteri: il vantaggio della classe String rispetto ad una array di caratteri è che la classe String mi permette di disinteressarmi della effettiva lunghezza della stringa mentre la array di caratteri deve essere allocata in anticipo.
Ma questo non è un problema per le sequenze MasterMind dal momento che la lunghezza della sequenza è conosciuta a priori ed è pari al valore del parametro di gioco k.
La classe SequenceGUI è una classe specializzata nel gestire le sequenze che l'user inserisce nel proprio pannello giocatore: mantiene la sequenza in una array di caratteri di nome chars la cui lunghezza effettiva viene determinata in ogni momento dal membro dati size:
La array chars contiene i codici delle sequenze e non i simboli. Vi è una netta separazione tra il codice ed il simbolo in una sequenza: il codice è sempre un carattere numerico da "0" a "9" e tutte le sequenze ritornano una stringa formata da codici. Questo è necessario per poter confrontare le sequenze.
Il simbolo, invece, è quello che viene visualizzato sullo schermo che può essere anche un numero ma che può anche essere una lettera o un colore: il simbolo visualizzato corrisponde all'elemento di una tabella il cui indice è rappresentato dal codice.
La classe SequenceGUI è la base astratta delle tre classi che gestiscono i simboli della sequenza: vi sono tre tipi di simboli:
SequenceNumbers SequenceLetters SequenceColors Una sequenza valida in MasterMind è, per esempio, la seguente:
1 2 3 4
Le sequenze possono però essere visualizzate graficamente con diversi simboli i quali, ovviamente, hanno una corrispondenza biunivoca con i codici: I simboli possono essere qualsiasi cosa, a piacere dell'utente. Al momento sono definiti tre tipi di simboli:
Nella tabella seguente potete vedere la sequenza 123 visualizzata nei tre tipi di simboli diversi:
| NUMBERS | LETTERS | COLORS |
|---|---|---|
|
|
|
Il tipo di simbolo viene definito in un enumerator:
Il simbolo della sequenza viene visualizzato in un componente di tipo JLabel. Ogni JLabel che contiene un simbolo ha le seguenti caratteristiche:
Vi è un solo costruttore della classe SequenceGUI. Esso accetta un solo argomento: il pannello delle sequenze di cui questa sequenza fa parte. Il costruttore inizializza i dati membri dati interni, dichiarati protected dal momento che sono previste classi derivate:
SequencePanel.getK. row a -1: al momento della costruzione questa sequenza non fò parte di alcun pannello; sarà il metodo addToPane che imposterà il membro dati al numero di riga del pannelloAlcuni di essi sono già stati commentati ma faccio un breve riepilogo:
addToPane labels: il componente GUI che disegna tutti i simboli della sequenza; viene descritto in dettaglio in Il disegno dei simboli chars: i codici della sequenza: contiene solo caratteri numerici da "0" a "9" size: lunghezza effettiva della sequenza che può andare a ZERO a k row: la riga del pannello delle sequenze dove questa sequenza è posizionata; questo dato viene specificato dal metodo addToPane ed è il discriminante per stabilire se questa sequenza è una solution (row = 0) oppure una guess (row > 0)La classe base SequenceGUI possiede molti metodi concreti condivisi da tutte le derivate. I metodi concreti non si occupano del disegno della sequenza ma della sua modifica:
addToPane(): aggiunge questa sequenza ad un pannello; questo è il metodo che visualizza effettivamente una sequenza quando viene creato il pannello delle sequenze (vedi Inserire le sequenze nel pannello). drawSequence(): questo metodo itera su tutti i codici nella sequenza e richiama il metodo astratto drawSymbol oppure clearSymbol backspace(): cancella l'ultimo codice della sequenza, è lo stesso di popChar popChar(): cancella l'ultimo codice della sequenza pushChar(char): aggiunge un codice alla sequenza; se non vi è più spazio disponibile, cancella l'intera sequenza e riparte dal primo codice isEmpty(): ritorna TRUE se la sequenza è vuota, cioè se size è uguale a ZERO length(); ritorna la lunghezza della sequenza in numero di codici setSequence(String): imposta una nuova sequenza di codici e la disegna richiamando il metodo drawSequence clearSequence(): : cancella l'intera sequenza toString(): ritorna la sequenza come stringa; questo metodo ritorna sempre i codici della sequenza e cioè una sequenza di caratteri da "0" a "9"Il disegno vero e proprio della sequenza è affidato a tre metodi astratti; con questa strategia implementare nuovi simboli (casette, astronavi, carri armati, etc) è davvero facile: basta implementare questi tre metodi astratti in una classe derivata da SequeneGUI:
drawBackground: disegna il colore di fondo: questo metodo viene richiamato subito dopo che la sequenza viene istanziata ed aggiunta al pannello delle seqeunze clearSymbol: cancella il simbolo dalla sequenza lasciando la sua posizione vuota (viene visualizzato solo il background) drawSymbol: disegna il simbolo corrispondete al codice della sequenza in base al tipo di simbolo da usare (NUMBERS, LETTERS, COLORS )Vi è da osservare che i metodi drawBackground e clearSymbol possono sembrare apparentemente uguali: in fondo, cancellare il simbolo equivale a lasciare visibile il suo sfondo: ebbene, vi è una sottile differenza tra le due operazioni e questa differenza è data dalla modalità in cui vengono disegnati i componenti JLabel. Nella prossima sotto-sezione analizzeremo il disegno vero e proprio.
Il disegno di ogni simbolo della sequenza avviene in un componente di classe javax.swing.JLabel (=etichetta). Con la classe JLabel, è possibile visualizzare testo e immagini non selezionabili. Se è necessario creare un componente che visualizzi una stringa, un'immagine o entrambe, è possibile farlo utilizzando o derivando JLabel.
Specificando codice HTML nel testo di un'etichetta, è possibile attribuirle diverse caratteristiche, come più righe, più font o più colori. La JLabel è un quadrato di dimensioni che si adattano al contenuto ed ha tre caratteristiche fondamentali:
JLabel ha uno sfondo trasparente a meno che non venga espressamente impostato opaco con il metodo JComponent.setOpaque(true). Dal momento che i simboli di Mastermind sono rotondi, il fatto che lo sfondo di JLabel sia trasparente è un vantaggio: l'user vedrà lo sfondo del pannello delle sequenze JLabel può contenere una icona cioè una immagine di qualsiasi formato convertita in una classe che implementa la interfaccia Icon; useremo la icona per disegnare lo sfondo del simbolo: un cerchio di colore chiaro per la solution, di colore scuro per le guess Graficamente, un simbolo è disegnato nel modo seguente:
La icona dello sfondo viene creata con il metodo statico SequencePanel.createIcon che accetta tre parametri: il colore del cerchio, il margine (ZERO se non c'è margine) e la dimensione della icona in pixels:
Il metodo ritorna un oggetto di classe ImageIcon il quale crea la icona passando al suo costruttore una immagine di tipo Image. Questa immagine viene creata da una classe BufferedImage nella quale è possibile disegnare usando le primitive grafiche già viste nella prima parte di questo mio tutorial.
Chi desidera rinfrescarsi la memoria, segua questo link: Java Tutorial parte prima - Il disegno dei poligoni.
Il simbolo vero e proprio viene disegnato impostando il testo nella JLabel usando il metodo setText. Per i simboli numerici, il testo da visualizzare è uguale al codice nella seqeunza: i numeri da "0" a "9". Per i simboli alfabetici, il testo da visualizzare è uguale al codice nella sequenza aumentato di 17 unità. le lettere da "A" a "J".
Per i simboli basati sui colori, questa organizzazione non funziona poichè i simboli colorati non hanno testo. Quindi la classe specializzata SequenceColors usa una strategia diversa: il simbolo da visualizzare è contenuto nella icona circolare che viene disegnata di colore diverso a seconda del codice da visuaqlizzare.
La classe SequenceColor crea due array di 10 elementi contenenti le icone dei 10 simboli possibili da visualizzare: una array per la solution ed una per le guess.
Le icone dei simboli colorati hanno un piccolo margine di 2 pixels che viene colorato col colore di fondo delle sequenze: chiaro per la solution e scuro per le guess. Nella immagine seguente potete vedere le icone dei simboli delle guess per i 10 codici delle sequenze Mastermind, disegnati dalla classe specializzata SequenceColors.
Per testare la funzionalità del pannello delle sequenze dobbiamo scrivere la classe principale della applicazione cioè il suo entry-point, in parole povere, il metodo pubblico statico main.
Per mantenere la possibilità di eseguire la app in una qualsiasi versione, è stato escogitato un trucchetto che consente non solo l'esecuzione della app in una qualsiasi versione ma anche di documentare praticamente automaticamente tutte le implementazioni e le modifiche al codice eseguite nelle diverse versioni della app. Il trucco può essere descritto in questo modo:
main viene inserito in una classe base astratta che interpreta la command-line alla ricerca della proprietà "version" che può contenere il numero di versione come intero Main, la quale implementa solo le funzionalità specifiche di quella versione: in questo modo i metodi ed i membri dati che costituiscono il sorgente della derivata si riferiscono solo a quella specifica versione main non deve essere sovrascritto: esso istanzia la classe specializzata specifica per la versione della app e poi ne richiama il metodo run che è generico: se proprio si deve basta sovrascrivere il metodo run e non il metodo statico main Questa classe definisce i membri dati comuni a tutte le versioni della app ed in particolare:
logger statico, usato in tutta la applicazione frame principale properties, istanziato nel main La classe definisce anche l'entry-point della app, il metodo pubblico statico main che, in ultima analisi, non fà altro che istanziare la classe derivata e richiamarne il metodo run:
Per non dover sovrascrivere il metodo run, esso richiama altri metodi che possono (alcuni devono) essere sovrasctitti. Il metodo run esegue, in sequenza, le seguenti operazioni:
overrideDefaultProperties che può essere sovrascritto nelle derivate per modificare i valori di default delle proprietà createContentPane che deve obbligatoriamente essere implementato nelle derivate e che restituisce il pannello da inserire nel frame principale Le varie versione della app sono rappresentate da classi derivate da Main alcune direttamente, altre indirettamente. Nelle varie versioni saranno aggiunte le funzionalità della applicazione e quindi è logico che ogni versione successiva derivi da quella precedente in modo da ereditarne le funzionalità già implementate.
Non tutte le versioni, però, sono la naturale conseguenza di una versione precedente: le due versioni 0.2 e 0.3 sono versioni di valutazione della interfaccia utente ma non implementano alcuna funzionalità di gioco. Esse sono come si suol dire, "sganciate" dal normale versionamento della app e possono essere considerate versioni a se stanti: esse derivano da Main direttamente.
La gerarchia delle varie versioni di MasterMind risulta essere:
Main | -> Main02 versione 0.2.0 (di valutazione U.I) | -> Main03 versione 0.3.0 (di valutazione U.I) | -> Main04 versione 0.4.0 (prima versione di gioco) | | | -> Main05 versione 0.5.0 (seconda versione di gioco) | | | -> Main06 versione 0.6.0 (terza versione di gioco) | | ... versione x.y.z
La classe specializzata della versione 0.2 della applicazione viene chiamata Main02 e, come descritto nella sezione precedente, deriva da Main. La GUI visualizza tre pannelli delle sequenze nella parte superiore del frame ed un pannello comandi nella parte inferiore. Attraverso i bottoni di comando dobbiamo poter controllare ed editare le singole sequenze:
Nella immagine successiva potete vedere il risultato che ci aspettiamo:
Le classi derivate da Main NON definiscono il metodo pubblico statico main poichè esso viene implementato e gestito dalla classe base astratta. Non c'è nemmeno bisogno di sovrascrivere il metodo run dal momento che esso esiste già nella classe base ed il suo comportamento è più che consono. La classe derivata implementa solo i metodi:
createContentPane che crea il pannello principale specializzato actionPerformed che reagisce ai bottoni di comando specifici di questa versione di valutazione getVersion che restituisce il numero di versione della app.La libreria Swing contiene un nutrito set di classi specializzate nel posizionamento (layout) dei componenti in un pannello. Al seguente link: A Visual Guide to Layout Managers potete vedere i molti modi in cui potete disporre i vari componenti GUI.
Divideremo il content-pane in quattro sottopannelli: tre nella parte superiore in cui ogni pannello contiene un pannello delle sequenze ed un pannello nella parte inferiore che contiene i bottoni di comando.
Il pannello delle sequenze sarà composto da dieci righe: la prima riga contiene la sequenza della solution mentre le successive nove righe conterranno le sequenze delle guess. Ogni riga dovrà contenere i simboli scelti nella sequenza (tre o quattro) più una colonna che conterrà un bottone di comando che chiameremo:
done per la prima riga, quella della solution; la pressione di questo bottone indica che l'user ha scelto la sequenza segreta check per tutte le altre righe, quelle delle guess; la pressione di questo bottone indica che l'user ha scelto la sequenza della ipotesi e che la app deve confrontarla con la solution per visualizzare i risultati del confronto: i punti red ed i punti white Il layout manager che io ho scelto per questa nostra prima versione di test è quello che calza a pennello per noi: il BorderLayout implementato dalla classe omonima contenuta nel package java.awt. Questo layout può disporre fino a cinque componenti nel pannello in questo modo:
| ------------------------------------------ | PAGE_START | | ------------------------------------------ | | | | | LINE_START | CENTER | LINE_END | | | | | | ------------------------------------------ | PAGE_END | | ------------------------------------------
Posizioneremo quindi i tre pannelli delle sequenze in LINE_START, CENTER e LINE_END mentre il pannello dei comandi andrà in PAGE_END.
Vi è da osservare che il layout manager BorderLayout gestisce lo spazio per ciascuna posizione in base alle dimensioni dei singoli componenti che vengono inseriti nella posizione stessa. Poichè i tre pannelli delle sequenze hanno una dimensione piuttosto grande in altezza, essi occuperanno la maggior parte del content-pane. Di contro, il pannello dei comandi sarà di dimensione molto ridotta.
La posizione PAGE_START non sarà usata e quindi il layout manager non assegnerà alcuno spazio per questa posizione. Il codice per creare il content-pane principale è nel metodo createContentPane:
Per il pannello dei comandi useremo un layout di classe FlowLayout del quale potete ottenere informazioni dettagliate leggendo la guida ufficiale in lingua inglese al sequente link: How to Use FlowLayout
Quella che segue è una traduzione libera delle principali caratteristiche del FlowLayout riportata nella guida di cui sopra: La classe FlowLayout dispone i componenti in una riga, dimensionati in base alle dimensioni desiderate. Se lo spazio orizzontale nel contenitore è troppo piccolo per collocare tutti i componenti in una riga, la classe FlowLayout utilizza più righe. Se il contenitore è più largo del necessario per una riga di componenti, la riga viene, per impostazione predefinita, centrata orizzontalmente all'interno del contenitore. Per specificare che la riga debba essere allineata a sinistra o a destra, utilizzare un costruttore FlowLayout che accetta un argomento di allineamento. Un altro costruttore della classe FlowLayout specifica la quantità di padding verticale e/o orizzontale da applicare attorno ai componenti.
Il pannello dei comandi è composto da quattro sottopannelli ognuno dei quali contiene due componenti disposti in un BoxLayout con orientamento verticale:
-FlowLayout ----------------------------------------------------------------
| |
| - BoxLayout ---- -GridLayout - - BoxLayout ------- - BoxLayout - |
| | ò solution | | 0 1 2 3 4 5 | | sequence ________ | | SET | |
| | o guess | | 6 7 8 9 b c | | index _________ | | | |
| ---------------- ------------- ------------------- ------------- |
| ---------------------------------------------------------------------------
Il metodo che crea il pannello comandi è il createCommandPanel ed è richiamato dal metodo createContentPane:
Per informazioni dettagliate sul layout manager BoxLayout consultate la guida ufficiale in lingua inglese al seguente link: <a href"https://docs.oracle.com/javase/tutorial/uiswing/layout/box.html" target=blank> How to Use BoxLayout.
Il sottopannello dei radio-buttons viene usato per stabilire se i comandi del pannello comandi si riferiscono alla solution oppure ad una guess. Il sottopannello ha questo aspetto:
Un radio-button viene rappresentato dalla classe JRadioButton che possiede molti costruttori dei quali il più semplice è quello che accetta come argomento una stringa di testo da visualizzare accanto al radio-button.
I due componenti radio-buttons vengono disposti in un BoxLayout con orientamento verticale (BoxLayout.Y_AXIS) ed aggiunti al sotto-pannello:
Voglio attirare la attenzione del lettore su un componente che non appare sullo schermo: il ButtonGroup, istanziato nella terz'ultima riga del listato di cui sopra. Questo componente NON fà parte della GUI Swing vera e propria in quanto, come avrete notato, non comincia con la lettera "J" (JPanel, JLabel, JRadioButton).
Anche i layout manager non cominciano con la lettera "J" ed in effetti anche questi ultimi non visualizzano qualcosa sullo schermo ma rappresentano componenti di comodo per posizionare i componenti visuali in un pannello.
Anche il ButtonGroup è un componente di comodo: serve a raggruppare i radio-buttons in modo che solo uno di questi componenti del gruppo possa essere selezionato in un dato istante. Quando l'user seleziona un radio-button quello che era eventualmente selezionato in precedenza viene automaticamente deselezionato se questi due radio-buttons fanno parte dello stesso ButtonGroup.
In questo modo non dobbiamo preoccuparci degli eventi di selezione/deselezione dei radio-buttons ma è sufficente determinarne la selezione con il metodo isSelected che, come avrete intuito, ritorna true se quello specifico radio-button è selezionato.
Passiamo ora a creare il sottopannelo dei bottoni che useremo per dare all'utente la possibilità di inserire e cancellare i simboli dalle sequenze. Un bottone di comando è rappresentato dalla classe JButton ls cui funzione principale è quella di essere "clikkato" per eseguire una determinata azione.
Vi sono molti tipi di bottoni di comando in Java Swing tutti derivati dalla classe base astratta AbstractButton. Il sottopannello dei bottoni nella nostra app di test appare come segue:
Vi sono 12 bottoni disposti in una griglia 6x2:
Il layout manager più consono per disporre i componenti in una griglia è, come si può facilmente intuire, il GridLayout il quale deve essere costruito con due parametri: il numero delle righe ed il numero delle colonne che formano la griglia. Vi è anche la possibilità di specificare lo ZERO come valore del numero di righe: in questo caso il manager creerà tante righe quante sono necessarie a contenere tutti i componenti che vengono aggiunti al pannello:
Il codice crea il pannello sub a cui viene associato il layout GridLayout costruito con ZERO righe e 6 colonne.
Il componente che visualizza un bottone di comando che può essere clikkato dall'utente è la classe JButton che può essere costruita, tra gli altri, specificando un solo parametro; la stringa che deve apparire sul bottone.
Il sottopannello delle caselle di testo viene usato per impostare una determinata sequenza (una qualsiasi stringa) nei tre pannelli delle sequenze. Il sotto-pannello delle caselle di input appare come segue:
Il sottopannello di compone di quattro componenti disposti in un GridLayout:
JLabel: sequence e index JTextField in cui l'user può digitare un testo qualsiasi; per maggiori info sul componente casella di testo vedi la guida ufficiale al seguente link: How to Use Text FieldsIl bottone di comando SET viene usato per impostare nei tre pannelli delle sequenze la sequenza digitata nella casella di testo etichettata sequence. Nel caso fosse selezionato il radio-button guess, la casella di testo etichettata index stabilisce l'indice della riga delle guess dove la sequenza deve essere impostata.
Poichè si tratta di un unico componente, non è necessario creare un sotto-pannello per aggiungere il bottone SET al pannello dei comandi: può essere aggiunto direttamente:
Ma cosa succede quando l'user clikka il bottone? Come già accennato in Introduzione alla GUI una applicazione GUI è molto diversa da una applicazione CLI; in una applicazione GUI non vi è un flusso procedurale di codice con un inizio ed una fine ben definiti.
Piuttosto, il programma GUI inizia visualizzando la interfaccia grafica e li il suo compito termina. E' lo user che, con i suoi comandi, fà partire i compiti specifici della applicazione: quando l'user clikka il bottone di comando "0" un simbolo viene aggiunto alla sequenza della solution (o della guess, dipende dal radio-button selezionato).
Una volta portato a termine il compito (aggiunta del simbolo) il flusso del programa termina e la GUI rimane in attesa di un altro evento: si suol dire che la GUI ha introdotto un nuovo modo di programmazione: il cosidetto event driven programming (=la programmazione guidata dagli eventi).
Tutti gli eventi che possono accadere in una GUI scaturiscono dal sistema operativo: è quest'ultimo che rileva l'input dell'utente oppure una richiesta di connessione di rete, oppure la esecuzione di una applicazione, etc.
Ma come vengono gestiti gli eventi? Ogni piattaforma di sviluppo ha la propria strategia: gli ambienti di sviluppo GUI basati sul C++, per esempio, sfruttano la caratteristica del polimorfismo della OOP. In ambienti scritti in altri linguaggi come per esempio il "C", che non è OOP, si usa la tecnica delle funzioni callback,
Ma vediamo cosa accade in Java Swing. Quando accade un evento il runtime Java (detto JRE) crea un oggetto derivato da EventObject e lo inserisce nella coda degli eventi. In ogni applicazione GUI Java vi è un thread detto Event Dispatching Thread (EDT) che si occupa di prelevare gli oggetti EventObject dalla coda e distribuirli (to dispatch = distribuire, spedire) a tutti gli oggetti che si sono registrati come gestori di quello specifico evento.
Ogni componente possiede un suo specifico tipo di evento; gli oggetti di classe JButton inviano sulla coda degli eventi oggetti di tipo ActionEvent che, ovviamente, deriva, anche se indirettamente, da EventObject.
Un oggetto interessato a gestire eventi di tipo ActionEvent deve implementare la interfaccia ActionListener e registrarsi presso il EDT come gestore degli eventi di quel tipo e generati da quello specifico componente. Pertanto, per poter gestire l'evento button-pressed del bottone SET (ma lo stesso vale per tutti gli altri bottoni) dobbiamo:
ActionListener Per esempi:
Il metodo ActionEvent.getActionCommand ritorna la action-command associata allo specifico bottone di comando. Se non diversamente impostata, la action-command corrisponde al testo visualizzato dal buttone stesso; per esempio per il bottone SET il testo è "Set". La action command può essere personalizzata con il metodo AbstractButton.setActionCommand.
Il metodo che gestisce gli eventi button-pressed ottiene la action-command dall'oggetto evento e poi ne analizza il solo primo carattere poichè ogni bottone ha un primo carattere diverso dall'altro:
done check set backspace cancel addSymbol Tutti questi metodi sono piuttosto semplici da leggere e non necessitano di commenti particolari
Avete provato a giocare un pò con questa applicazione di test? Se lo avete fatto avrete notato che il layout manager BorderLayout gestisce lo spazio destinato ai tre pannelli delle sequenze in un modo che non ci aspettavamo: invece di dividere equamente lo spazio tra i tre pannelli delle sequenze il manager ridimensiona solo il pannello centrale, quello che abbiamo aggiunto con la costante BorderLaout.CENTER.
Ma questa è solo una applicazione scritta al volo per testare i vari tipi di simboli. In futuro, questo comportamento poco ortodosso non accadrà.
La documentazione completa del package descritto in questo capitolo può essere visualizzata clikkando il seguente link che riporta alla documentazione Javadoc della versione 0.2 del progetto: The MasterMind Project Version 0.2
Argomento precedente - Argomento successivo - Indice Generale