Paola Magillo, Univestita' di Genova,
Corso di Programmazione II per SMID, a.a. 2006-2007.
Lezione 09 a:
UN'INTERFACCIA GRAFICA PER ISTOGRAMMI - PARTE 1
Introduzione generale
Obiettivo
Facciamo un programma per visualizzare istogrammi e compiere operazioni
su di essi.
Un istogramma e' un insieme finito e ordinato di rettangoli
aventi tutti uguale larghezza (width) e ciascuno la sua propria lunghezza
(length).
Le operazioni sono di due tipi: operazioni che avvengono
sull'istogramma e operazioni che modificano il modo in cui l'istogramma
viene disegnato.
Operazioni sull'istogramma:
- Aggiungere un nuovo rettangolo in fondo stabilendo la
sua lunghezza
- Cancellare l'ultimo rettangolo
- Cambiare la lunghezza di un rettangolo
- Calcolare grandezze statistiche relative all'istogramma
(media, varianza, ecc.)
Operazioni che modificano il modo in cui l'istogramma e' disegnato:
- Stabilire il colore di default che viene dato a tutti i
rettangoli se non altrimenti specificato
- Stabilire il colore proprio di uno specifico rettangolo
- stabilire se i numeri corrispondenti alle lunghezze dei
rettangoli vengono mostrati o no; in caso affermativo,
stabilire se vengono mostrati sopra o sotto ciascun rettangolo
- Stabilire il fattore di scala usato per disegnare sulle x e sulle y
- Stabilire la posizione dell'asse x nella finestra (se
deve essere disegnato piu' in alto o piu' in basso)
Da che cosa partiamo
Sono date le seguenti classi e interfacce Java:
-
IstoPainter = classe di componente Java a cui posso
assegnare un istogramma e questo sara' disegnato.
-
AnyIstogram = interfaccia che stabilisce quali funzioni
una classe deve implementare per essere accettata come istogramma
da IstoPainter.
-
AnyRectangle = interfaccia che stabilisce quali funzioni
una classe deve implementare per essere accettata come rettangolo
da AnyIstogram. Nota: nell'istogramma sono accettati anche
rettangoli con lunghezze negative!
-
SelectionListener = interfaccia event listener per
associare comportamento alla classe IstoPainter quando l'utente
selezione un rettangolo dell'istogramma disegnato (ved. dopo).
-
IstoDialog = finestra di dialogo per chiedere
all'utente i parametri delle operazioni
(la vedremo nella lezione 9b).
-
ShowIsto = base di partenza per realizzare l'interfaccia grafica,
realizza una finestra che contiene:
- un oggetto di classe IstoPainter
- barra di menu' con un menu' File contenente
le voci per caricare un istogramma (che viene disegnato),
salvare l'istogramma disegnato, e uscire dal programma
- una linea di messaggi in fondo per informare l'utente delle
operazioni compiute
Che cosa dobbiamo fare
-
Scrivere una nostra classe che implementi l'interfaccia AnyRectangle.
-
Scrivere una nostra classe che implementi l'interfaccia AnyIstogram.
-
Partire da ShowIsto per
realizzare un'interfaccia grafica che permetta le operazioni elencate sopra.
Descrizione dettagliata del materiale di partenza
Interfaccia AnyRectangle
File AnyRectangle.java.
Stabilisce che la nostra classe per il rettangolo
deve avere i seguenti metodi:
- int getLength() e getWidth()
= restituiscono lunghezza e larghezza
- void setLength(int l) e setWidth(int w)
= assegnano nuova lunghezza e larghezza
Interfaccia AnyIstogram
File AnyIstogram.java.
Stabilisce che la nostra classe per l'istogramma
deve avere i seguenti metodi:
- int getCapacity() = ritorna la capacita'
(numero massimo di rettangoli) che questo istogramma puo' contenere.
- int getRectangleNum() = ritorna il numero effettivo di rettangoli
presenti in questo momento.
- void setWidthForAll(int w) e int getWidthForAll()
= assegna e ritorna la width comune di tutti i rettangoli.
- boolean addRectangle(AnyRectangle r) = aggiunge rettangolo in
fondo all'istogramma e modifica il rettangolo
rendendo la sua width uguale a quella prestabilita per tutti;
lo aggiunge solo se non eccede la capacita'; ritorna true o
false a seconda che abbia aggiunto o no.
- AnyRectangle getRectangle(int i) = ritorna l'i-esimo rettangolo,
se i = 0...getRectangleNum()-1
estremi inclusi, altrimenti ritorna null.
- int getPosition(AnyRectangle r)
= cerca e ritorna la posizione del rettangolo
dato; ritorna -1 se non e' presente.
- boolean removeRectangle()
= elimina l'ultimo rettangolo, se almeno un rettangolo e' presente;
ritorna true o false a seconda che abbia eliminato o no.
Classe IstoDialog
File IstoDialog.java.
Useremo questa classe cosi' come e', senza modificarla.
Finestra di dialogo (sotto-clases di Dialog)
con un numero di campi di testo ciascuno con sua etichetta esplicativa,
due bottoni Ok e Cancel per chiudere la finestra.
+----------+----------+----------+
| label1 | label2 | label3 | pannello p1 con grid layout
+----------+----------+----------+ 2xK dove K = numero di
| campo1 | campo2 | campo3 | etichette/campi di testo
+----------+----+-----+----------+
| Ok | Cancel | pannello p2 con grid layout 1x2
+---------------+----------------+
L'utente, guidato da quanto scritto nelle etichette esplicative,
immettera' valori nei campi di testo.
Poi chiudera' la finestra premendo Ok oppure Cancel.
Il numero di campi di testo e le stringhe da scrivere
nelle loro etichette esplicative si decidono all'atto della costruzione
di un oggetto di classe IstoDialog. Cosi' come il titolo che dovra'
apparire sulla finestra.
Il bottone Cancel semplicemente fa sparire la finestra (comportamento
gia' impostato nella classe IstoDialog).
Al bottone Ok bisognera' associare un action listenere per fargli
fare quello che ci interessa.
Attributi
L'attributo che interessa a noi e' OkButton = il bottone
Ok a cui dovremo associare un action listener.
Costruttori
-
IstoDialog(Dialog win, String tit, String[] msg) e
IstoDialog(Frame win, String tit, String[] msg) =
costruisce finestra con la stringa tit come titolo,
l'array di stringhe msg contiene le stringhe che devono
apparire nelle etichette esplicative dei campi di testo;
la lunghezza di tale array determina quante saranno le
etichette ovvero quanti saranno i campi di testo.
Il parametro win e' la finestra da cui vogliamo che questa
finestra di dialogo dipenda; servono due
costruttori diversi a seconda che tale finestra sia
un frame un altro dialogo.
Tutto il lavoro di costruzione e' in realta' delegato al metodo
ausiliario (privato) makeIstoDialog.
Metodi
-
makeIstoDialog(String tit, String[] msg) =
metodo privato che esegue la costruzione della finestra;
il significato dei parametri e' come nei due costruttori (ved. sopra).
Stabilisce il titolo (chiamando setTitle funzione ereditata dalla
super-classe Dialog).
Crea due pannelli:
- p1 organizzato a grid layout con 2 righe e numero di colonne
uguale alla lunghezza dell'array msg; la riga superiore contiene
le etichette e quella inferiore i campi di testo.
- p2 organizzato a grid layout con 1 riga e 2 colonne
che contiene i bottoni Ok e Cancel.
Aggiunge i due pannelli alla finestra (organizzata con border layout)
in posizioni p1 a NORTH e p2 a SOUTH, poi impacchetta la finestra.
Associa al bottone Cancel un action listener che chiude questa
finestra di dialogo (memorizzata nell'attributo thisDialog).
-
void setText(int i, String s) = assegna un contenuto
iniziale all'i-esimo campo di testo (se non viene usata,
il campo di testo rimane inizialmente vuoto).
-
String getText(int i) = ritorna la stringa che si trova
scritta (scritta dall'utente) nel campo di testo
i-esimo, nulla se il campo e' vuoto.
Classe IstoPainter
File IstoPainter.java.
Useremo questa classe cosi' come e', senza modificarla.
Non guardiamo come e' fatta dentro, ma solo come si comporta e
i metodi interessanti per noi.
E' sottoclasse di Panel, quindi ne eredita le caratteristiche.
A differenza pero' di un Panel, che e' pensato per contenere altre
cose, un IstoPainter e' pensato per restare vuoto e
disegnare un istogramma sul proprio sfondo.
Mentre le dimensioni di un Panel di norma vengono calcolate
in base alle dimensioni del contenuto,
un IstoPainter stabilisce all'atto della costruzione quali
sono le sue dimensioni preferite.
Nota: le dimensioni preferite
(ved. lezione 8)
verranno poi usate dai
layout manager della gerarchia di contenimento in cui si trova
per stabilire le sue dimensioni effettive all'interno dell'interfaccia
grafica.
I rettangoli dell'istogramma sono disegnati pieni inizialmente
usando un colore di default, con il contorno tracciato in grigio
chiaro. E' poi possibile assegnare a ciascun rettangolo un suo
specifico colore di riempimento.
IstoPainter e' sensibile all'evento di click con il mouse.
Come comportamento predefinito,
cliccare col mouse su un rettangolo seleziona il rettangolo.
L'avvenuta selezione e' segnalata disegnando un contorno
piu' spesso al rettangolo selezionato.
In ogni istante puo' essere selezionato al piu' un solo rettangolo;
selezionare un rettangolo disseleziona l'eventuale altro rettangolo
selezionato in precedenza;
cliccare fuori dai rettangoli invece non seleziona nulla,
ma disseleziona il rettangolo eventualmente selezionato.
La classe IstoPainter prevede un listener apposito
(ved. interfaccia SelectionListener)
per aggiungere altri comportamenti all'evento di selezione di un rettangolo.
Vediamo ora i metodi interessanti della classe IstoPainter:
-
IstoPainter(int prefx, int prefy) = costruttore,
assegna le dimensioni predefinite, inizialmente non ha istogramma
da disegnare.
-
int setIstogram(AnyIstogram) = assegna l'istogramma da disegnare:
da questo momento verranno disegnati i rettangoli usando il colore
di default.
-
AnyIstogram getIstogram() = ritorna l'istogramma disegnato.
-
boolean setValuePos(int p) = se il paramtro e' una delle costanti
IstoPainter.ABOVE, IstoPainter.BELOW e IstoPainter.HIDDEN,
stabilisce che i valori delle lunghezze dei rettangoli siano mostrati
sopra a ciascun rettangolo, sotto, oppure nascosti, e ritorna true;
ritorna false e non fa nulla se il parametro ha un valore diverso dai
tre ammessi.
-
int getValuePos()
= ritorna la posizione corrente dei valori delle lunghezze
dei rettangoli (una delle tre costanti sopra citate).
-
void setScaleX(int sc) e setScaleY(int sc)
= assegnano il fattore di
scala sulla x e sulla y con cui vengono disegnati i rettangoli.
-
int getScaleX() e getScaleY() = ritornano i fattori di scala
correntemente impostati.
-
void setDefaultColor(Color c) e Color getDefaultColor()
= assegna e ritorna il colore dato per difetto a tutti i rettangoli.
-
void setRectangleColor(AnyRectangle r, Color c) = assegna
il colore con cui viene disegnato il rettangolo r, se presente;
altrimenti non fa nulla.
-
Color getRectangleColor(AnyRectangle r) = ritorna il colore del
rettangolo r, se presente; altrimenti ritorna null.
-
AnyRectangle getSelectedRectangle()
= restituisce il rettangolo selezionato se esiste;
altrimenti restituisce null.
-
void selectedRectangle(int x, int y) = seleziona il rettangolo
che disegnato contiene il punto
di coordinate in pixel (x,y); seleziona null
se in quella posizione non e' disegnato nessun rettangolo.
-
void setXAxisPos(double p) e double getXAxisPos()
= assegna e ritorna
la distanza dell'asse x dal bordo inferiore del pannello, espressa
in percentuale sulla altezza del pannello.
Per esempio il valore 0.0 disegna l'asse x nel bordo inferiore
della finestra, 50.0 lo disegna a meta' altezza, ecc.
-
void addSelectionListener(SelectionListenerlistener) =
aggiunge un comportamento per l'evento di selezione di un rettangolo
Interfaccia SelectionListener
File
SelectionListener.java.
L'interfaccia SelectionListener prevede come unico metodo il seguente:
- public void rectangleSelected(MouseEvent e) = stabilisce il
comportamento, in aggiunta a quello predefinito,
alla selezione di un rettangolo
Classe ShowIsto
File ShowIsto.java.
La classe eredita da Frame e realizza la finestra principale
dell'applicazione che contiene
al centro un pannello per disegno di
un istogramma (oggetto di classe IstoPainter),
in basso un campo di testo per mostrare
messaggi (oggetto di classe TextField), ed e' provvista di barra di menu'.
La barra di menu' per ora contiene solo il menu' File con le voci
Load, Save, Quit.
+-------------------------------------------------------+
| File | Barra menu'
+-------------------------------------------------------+
| |
| |
| |
| IstoPainter |
| |
| |
| |
+-------------------------------------------------------+
| TextField per messaggi |
+-------------------------------------------------------+
Altre classi interne
gestiscono gli eventi di caricamento e salvataggio dell'istogramma.
Della classe ShowIsto vediamo in dettaglio il contenuto perche' dovrete
modificarla.
Attributi
- Frame thisFrame = questa stessa finestra
- AnyIstogram is = istogramma da disegnare
- IstoPainter ip = dispositivo che disegna istogramma
- TextField msg = linea per mostrare messaggi
- Voci del menu' File:
- MenuItem quitItem = termina programma
- MenuItem loadItem = carica istogramma chiedendo nome file
- MenuItem saveItem = salva istogramma chiedendo nome file
- IstoDialog fileDialog = finestra di dialogo che chiede nome file
Costruttore
Memorizza se stesso nell'attributo thisFrame.
Crea la barra di menu' e la attacca alla finestra.
Crea il menu File e sue voci e lo aggiunge alla barra di menu'.
Stabilisce che l'interno della finestra e' gestito con un
layout manager di classe BorderLayout (saranno occupate le
posizioni CENTER e SOUTH).
Per la parte centrale, crea un oggetto di classe IstoPainter,
che memorizza nell'attributo ip, a questo
assegna da disegnare l'istogramma contenuto nell'attributo
is (per ora nullo, voi lo dovete cambiare).
Assegna poi alcune caratteristiche di ip:
fattori di scala su assi x e y, posizione dell'asse x,
dove disegnare i valori delle lunghezze dei rettangoli.
Infine aggiunge ip alla finestra principale in posizione CENTER.
Per la parte inferiore crea la linea di messaggi come oggetto di
classe TextField, non editabile, lo memorizza nell'attributo
msg e lo aggiunge alla principale in posizione SOUTH.
Impacchetta la finestra.
Finora abbiamo solo l'estetica della finestra.
Ora bisogna gestire gli eventi.
Stabilisce il window listener per chiudere la finestra e
terminare il programma quando l'utente aziona la x sul bordo
(ved. lezione 7).
Stabilisce lo stesso comportamento per il bottone quit,
in questo caso pero' occorre usare un action listener.
Ai bottoni load e save associa gli
action listener per gestire il caricamento e il salvataggio;
questi non appartengono a classi definite "al volo" (come i precedenti),
ma sono oggetti di due classi definite a parte
(classi interne a ShowIsto): loadListener e SaveListener.
Classi interne
In generale, i listener un po' piu' complicati meritano di essere
realizzati con classi a parte, anche per ottenere un codice piu' leggibile.
Le classi LoadListener e SaveListener (entrambe implementanti
l'interfaccia ActionListener) definiscono il metodo actionPerformed
in modo analogo:
-
Crea finestra di dialogo di classe IstoDialog,
dipendente da questo frame, con titolo "Load" o "Save"
(rispettivamente), la finestra conterra' un solo campo
con la dicitura "Name of file to load:" o
"Name of file to save:" (rispettivamente).
-
Al bottone Ok di questa finestra di dialogo associa un action
listener che e' quello che compie effettivamente l'operazione
di caricamento o salvataggio:
nasconde la finestra di dialogo,
recupera il nome del file dal campo di testo presente nella
finestra, apre il file e legge/scrive a seconda del caso.
In caso di caricamento collega il nuovo istogramma
al pannello di disegno (ip.setIstogram).
Le operazioni di lettura/scrittura possono sollevare eccezioni,
per questo sono inserite in un blocco try-catch.
Se ci sono eccezioni viene mostrato un messaggio di errore nella
linea messaggi (msg.setText);
se va tutto bene viene mostrato un messaggio di operazione
compiuta.
-
Pulisce la linea messaggi e visualizza la finestra di dialogo.
-
A questo punto il compito del bottone load/save e' finito: il resto
del lavoro lo fara' il bottone Ok della finestra di dialogo
(tramite l'action listener che associato al punto 2).
Vedremo meglio nella lezione 9b le funzioni Java per lettura/scrittura.
Main
Crea oggetto di classe ShowIsto e lo visualizza.
Materiale da realizzare
Nostra classe per i rettangoli
Facile.
Implementare l'interfaccia AnyRectangle ispirandosi
alle numerose varianti di rettangolo viste
(lezione 2 e
lezione 3).
Questa parte e' argomento del
laboratorio 4a.
Nota: i metodi sono pubblici nell'interfaccia AnyRectangle,
percio' devono essere pubblici anche nella nostra classe rettangolo.
La nostra classe puo' avere anche altri metodi (es: area, print...)
oltre a quelli previsti da AnyRectangle.
Nostra classe per l'istogramma
Facile.
Implementare l'interfaccia AnyIstogram ispirandosi
a quanto fatto nell'esercitazione.
Questa parte e' argomento del
laboratorio 4a.
Nota: nell'esercitazione si aggiungeva un rettangolo passando
all'istogramma solo la length (il rettangolo veniva costruito
dall'istogramma). Qui invece passiamo all'istogramma gia' il rettangolo
e l'istogramma deve modificare la sua width.
L'interfaccia grafica -- passo 1
Integrare le nostre classi istogramma e rettangolo nella classe ShowIsto
fornita.
La classe ShowIsto fornisce gia' una rudimentale visualizzazione
di istogrammi.
Al momento pero' l'istogramma impostato per essere visualizzato e' nullo,
percio' non appare nulla nella finestra.
Creare un istogramma della nostra classe istogramma,
aggiungere a tale istogramma alcuni rettangoli della nostra classe
rettangolo, e impostare tale istogramma per essere visualizato,
invece di quello nullo.
Questa parte e' argomento del
laboratorio 4a.
L'interfaccia grafica -- passo 2
Aggiungere alla classe ShowIsto dispositivi per tutte
le altre operazioni.
Per fare un buon software, l'ultima cosa e' scrivere il codice!
Prima bisogna progettare...
Qui si tratta di esaminare le operazioni richieste e cercare
il modo migliore per renderle possibili all'utente.
Decidiamo che il modo migliore e' usare il piu'
possibile dei menu'.
Prevediamo allora 4 menu': File Edit View Function da
sistemare sulla barra di menu' della finestra principale.
Dove necessario useremo sotto-menu' o finestre di dialogo
per chiedere all'utente i parametri delle operazioni.
Menu' File
Ha voci per caricare un istogramma, salvare
l'istogramma presente, uscire dal programma.
Esiste gia' nella classe ShowIsto fornita.
Menu' Edit
Ha voci per modificare l'istogramma:
- Aggiungere un nuovo rettangolo in fondo
Bisogna chiedere la sua lunghezza con una finestra di dialogo.
- Cancellare l'ultimo rettangolo
- Cambiare la lunghezza del rettangolo selezionato
Se nessun rettangolo e' selezionato questa voce deve
essere disabilitata.
La lunghezza verra' chiesta all'utente con una finestra di dialogo.
Menu' View
Ha voci per modificare il modo in cui l'istogramma e' disegnato:
- Colore di default
Bisogna chiedere le tre componenti red, green, blue
con una finestra di dialogo.
- Colore del rettangolo selezionato
Se nessun rettangolo e' selezionato questa voce deve
essere disabilitata.
Bisogna chiedere le tre componenti red, green, blue
con una finestra di dialogo.
- Se e dove mostrare i valori delle lunghezze dei rettangoli
Questa voce e' un sotto-menu' che a sua volta ha tre
voci corrispondenti alle tre opzioni: sopra, sotto, nascosti.
- Fattori di scala usati sulle x e sulle y
Una finestra di dialogo chiedera' i due valori.
- Posizione dell'asse x nella finestra
Una finestra di dialogo chiedera' il valore tra 0.0 e 100.0
che rappresenta la distanza dell'asse x dal bordo inferiore
del pannello, in parcentuale sull'altezza del pannello stesso.
Menu' Function
Ha voci che corrispondono al calcolo di caratteristiche dell'istogramma
quali media, varianza ecc.
Per adesso siamo in grado di costruire questi menu'.
Nella prossima lezione (lezione 9b)
vedremo come realizzare i comportamenti da associare alle varie voci.