Facciamo un programma per disegnare rettangoli e compiere operazioni su di essi. Le operazioni sono:
Da che cosa partiamo?
Che cosa dobbiamo fare?
File MyRectangle.java.
Stabilisce che la nostra classe per il rettangolo
deve avere i seguenti metodi:
File RectanglePainter.java.
Non guardiamo come e' fatta dentro, ma solo come si comporta e
quali metodi interessanti ha.
E' sottoclasse di Panel, quindi ne eredita le caratteristiche.
A differenza pero' di un Panel, che e' pensato per contenere altre
cose, un RectanglePainter e' pensato per restare vuoto e
disegnare i rettangoli sul proprio sfondo.
Mentre le dimensioni di un Panel di norma vengono calcolate
in base alle dimensioni del contenuto,
un RectanglePainter 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 presenti sono disegnati pieni e ognuno del suo colore. I rettangoli presenti hanno un ordine da "sotto" a "sopra", tale ordine per default e' quello in cui sono stati aggiunti (il piu' veccchio sotto, il piu' recente sopra), ma ci sono appositi metodi per cambiarlo. I rettangoli vengono disegnati in ordine da sotto a sopra. Se due rettangoli sono parzialmente sovrapposti, quello che viene disegnato dopo (sopra) copre il precedente (sotto).
Il RectanglePainter puo' essere in due modi operativi: selezione (selection) e disegno (drawing). Per default (cioe' appena dopo costruito) e' in modo selezione. Ci sono metodi appositi per cambiarlo. In ciascuno dei due modi operativi RectanglePainter ha dei comportamenti predefiniti per certi eventi.
Come comportamento predefinito in modo selezione, cliccare col mouse su un rettangolo seleziona il rettangolo. L'avvenuta selezione e' segnalata disegnando un sottile contorno bianco 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 RectanglePainter prevede un listener apposito (ved. interfaccia SelectionListener) per aggiungere altri comportamenti all'evento di selezione di un rettangolo.
Come comportamento predefinito in modo disegno, premere il mouse su un punto inizia il processo di disegno di un nuovo rettangolo. Un rettangolo e' definito mediante due vertici opposti: si preme il mouse in un punto, si trascina il mouse a bottone premuto (drag) e infine si rilascia il mouse, i punti di pressione e rilascio definiscono il rettangolo. Durante il trascinamento del mouse, viene disegnato in nero il contorno del rettangolo che si otterrebbe rilasciando alla posizione attuale del mouse.
Nota bene: il nuovo rettangolo non viene automaticamente creato ed aggiunto al RectanglePainter! E' previsto un listener apposito (ved. interfaccia EndDrawingListener) da aggiungere per gestire l'evento di fine disegno del nuovo rettangolo. Tipicamente tale evento verra' gestito costruendo ed aggiungendo il nuovo rettangolo.
Vediamo ora i metodi interessanti della classe RectanglePainter:
File EndDrawingListener.java.
L'interfaccia EndDrawingListener prevede come unico metodo il seguente:
File SelectionListener.java.
L'interfaccia SelectionListener prevede come unico metodo il seguente:
Facile. Implementare l'interfaccia MyRectangle ispirandosi alle numerose varianti di rettangolo viste (lezione 2 e lezione 3).
MyRectangle impone che un rettangolo abbia tre caratteristiche: dimensioni (lunghezza, larghezza), colore e punto di aggancio (x,y del suo angolo di coordinare minime). Solo l'ultima e' nuova rispetto ai rettangoli visti a lezione.
Nota: i metodi sono pubblici nell'interfaccia MyRectangle, 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 MyRectangle.
Per fare un buon software, l'ultima cosa e' scrivere il codice! Prima bisogna progettare...
Esaminiamo le operazioni richieste e cerchiamo il modo migliore per renderele possibili all'utente.
1) Aggiungere un nuovo rettangolo stabilendone la posizione e le
dimensioni
Possiamo mettere il RectanglePainter in modo disegno e usare la
funzionalita' che gia' offre per disegnare rettangoli.
Gestiremo l'evento di fine disegno prelevando le dimensioni e
il punto di aggancio del rettangolo tracciato, costruendo con queste
un nuovo rettangolo ed aggiungendolo al RectanglePainter.
A questo nuovo rettangolo possiamo dare un colore di default (ved. dopo).
2) Cancellare un rettangolo
Possiamo mettere il RectanglePainter in modo selezione, l'utente seleziona
un rettangolo da cancellare e questo viene cancellato.
3) Stabilire il colore di un rettangolo
Possiamo mettere il RectanglePainter in modo selezione, l'utente seleziona
un rettangolo e a questo viene dato il nuovo colore.
Il nuovo colore viene scelto dall'utente in qualche modo (ved. dopo).
4) Portare un rettangolo sopra o sotto gli altri
Possiamo mettere il RectanglePainter in modo selezione, l'utente seleziona
un rettangolo e questo viene portato sopra o sotto.
5) Stampare informazioni su un rettangolo
Possiamo mettere il RectanglePainter in modo selezione, l'utente seleziona
un rettangolo e di questo vengono mostrate le informazioni:
punto di aggancio, lunghezza, larghezza.
Bisogna stabilire dove e come vengono mostrate (ved. dopo).
6) Spostare un rettangolo
Possiamo mettere il RectanglePainter in modo selezione, l'utente seleziona
un rettangolo e questo viene spostato in una direzione stabilita:
verso nord, sud, est, ovest.
7) Copiare un rettangolo
Possiamo mettere il RectanglePainter in modo selezione, l'utente seleziona
un rettangolo e di questo viene fatta una copia, spostata di qualche pixel
ed aggiunta al RectanglePainter.
Con questo NON abbiamo precisato tutto!
Rapporto fra selezione e operazioni
L'utente prima seleziona il rettangolo e poi sceglie che cosa farne
(cancellarlo, cambiare colore, portarlo sopra/sotto, stampare informazioni,
copiare, spostare) oppure prima sceglie che cosa fare e poi
seleziona un rettangolo?
Per comodita' d'uso sembra meglio la prima!
Inoltre come scegliere l'operazione da fare sul rettangolo selezionato?
tramite menu' o tramite una serie di bottoni?
Per comodita' d'uso sembra meglio la seconda!
Quindi: l'interfaccia grafica ha una serie di bottoni per le operazioni da compiere. Se l'utente aziona uno di questi bottoni e c'e' un rettangolo selezionato, l'operazione corrispondente viene applicata su quel rettangolo.
Stampa delle informazioni
Dove stampare le informazioni su un rettangolo?
In una zona della finestra principale oppure
in una finestra di dialogo (che avra' un bottone per chiuderla)?
Per semplicita' usiamo una zona della finestra principale.
Quando stampare le informazioni? Stabiliamo che, non appena viene selezionato un rettangolo, le sue informazioni vengano stampate automaticamente.
Quindi: la stampa delle informazioni e' trattata in modo speciale rispetto alle altre operazioni, non ha bisogno di un bottone per invocarla, viene eseguita automaticamente all'atto della selezione.
Nota: se avessimo usato la finestra di dialogo, la stampa automatica sarebbe stata scomoda (finestre di dialogo che saltano fuori e bisogna andare a richiuderle)!
Impostazione del colore
E' utile avere un "colore corrente", da usare sia quando si cambia colore ad un rettangolo esistente, sia quando si aggiunge un nuovo rettangolo.
Con quali dispositivi scegliere il colore corrente?
Cambio di modo operativo
Come passare da modo selezione a modo disegno e viceversa?
Possiamo usare un gruppo di due bottoni radio.
Quando il RectanglePainter passa in modo disegno, i bottoni delle operazioni (cancella, cambia colore ecc.) vengono disabilitati. Vengono riabilitati al ritorno in modo selezione.
Ecco infine quali componenti ci saranno nell'interfaccia grafica:
Come disponiamo questi componenti? Procediamo in modo modulare: individuiamo prima dei sotto-gruppi di componenti che andranno a costituire pannelli, poi decidiamo come disporre questi pannelli nella finestra principale.
Pannello direzioni
Disponiamo i bottoni per spostamento secondo lo schema:
+-----+ |nord | +-----+-----+-----+ |ovest| | est | +-----+-----+-----+ | sud | +-----+Questo sara' un pannello con GridLayout a 3x3 caselle, di cui 4 occupate dai bottoni e 5 occupate da etichette (Label) vuote come riempitivo.
Pannello bottoni
Disponiamo i restanti 5 bottoni per le operazioni (cancella, colora,
sopra, sotto, copia) per esempio in fila orizzontake.
Pannello colore
I dispositivi per la scelta del colore corrente
li organizziamo per esempio secondo uno dei due schemi:
Colore corrente: [] rosso [] verde [] blu [] giallo | oppure |
Colore corrente: +-------------+ | | | | +-------------+ rosso: [ ] verde: [ ] blu : [ ] |
Pannello modo operativo
Disponiamo i due bottoni per la scelta del modo operativo
per esempio secondo lo schema:
Modo operativo: [] selezione [] disegno
Pannello info
Organizziamo la zona delle informazioni sul rettangolo selezionato
in questo modo (x,y,l,w stanno per i numeri...):
Rettangolo selezionato: punto di aggancio = (x,y) lunghezza = l larghezza = wQuesto puo' essere semplicemente un pannello con GridLayout 4x1 e dentro quattro etichette.
Nota: in generale possiamo scrivere i titoli dei pannelli di un altro colore cosi' si distinguono meglio.
Finestra principale
Alla luce di queste considerazioni per esempio possiamo disporre il
contenuto della finestra principale in questo modo:
+--------------------------------------------------------------+ | Pannello bottoni Pannello direzioni | +-----------+--------------------------------------------------+ | Pannello | | | modo | | | operativo | | | | RectanglePainter | | | | | Pannello | | | colore | | | | | | | | Pannello | | | info | | +-----------+--------------------------------------------------+ | TextField per messaggi | +--------------------------------------------------------------+
PER ESERCIZIO: Quale gerarchia di contenimento e quali layout manager servono per ottenere questa disposizione?
Decidiamo che cosa eseguire nella callback di ciascun dispositivo.
ItemListener del Checkbox "disegno"
-- Passaggio in modo disegno
Chiama setDrawingMode (classe RectanglePainter) e
disabilita i bottoni delle operazioni (cancella, colora,
sopra, sotto, stampa, copia).
ItemListener del Checkbox "selezione"
-- Passaggio in modo selezione
Chiama setSelectionMode (classe RectanglePainter) e
riabilita i bottoni delle operazioni (cancella, colora,
sopra, sotto, stampa, copia).
EndDrawingListener del RectanglePainter
-- Aggiunta di nuovo rettangolo
Deve costruire e aggiungere al RectanglePainter il rettangolo appena finito
di disegnare.
Controlla se esiste con isNewRectangle, se esiste legge
il punto di aggancio e le dimensioni con
getNewMinX, getNewMinY, getNewLength, getNewWidth (classe RectanglePainter).
Crea un rettangolo con il costruttore della nostra classe per i rettangoli,
gli assegna il colore corrente e lo aggiunge al RectanglePainter
con addRectangle.
SelectionListener del RectanglePainter
-- Stampa automatica delle informazioni
quando viene selezionato un rettangolo
Ottiene il rettangolo appena selezionato con getSelectedRectangle
(classe RectanglePainter). Se questo esiste, ottiene
le sue informazioni con getMinX, getMinY, getLength, getWidth
(interfaccia MyRectangle).
Le mette poi nelle tre etichette del pannello info
con setText (classe Label).
Se il rettangolo selezionato non esiste, mette
nelle etichette "--" invece dei valori.
ActionListener del Button "cancella"
-- Cancellazione del rettangolo selezionato
Ottiene il rettangolo selezionato con getSelectedRectangle
(classe RectanglePainter). Se esiste,
lo cancella con removeRectangle (classe RectanglePainter)
ActionListener del Button "colora"
-- Assegnazione del colore al rettangolo selezionato
Ottiene il rettangolo selezionato con getSelectedRectangle
(classe RectanglePainter).
Se esiste, ottiene il colore corrente:
se ho usato un gruppo di bottoni radio controlla quale dei bottoni radio
e' selezionato, altrimenti prende il colore di sfondo dell'etichetta
anteprima (metodo getBackground).
Assegna poi il colore al rettangolo con setColor (interfaccia MyRectangle).
ActionListener dei TextField del colore
-- Cambio del colore corrente
Se abbiamo usato un gruppo di bottoni radio non occorre gestire alcun evento
(semplicemente quando ci servira' il colore lo leggeremo, ved.
bottone "colora").
Se invece abbiamo usato le tre componenti con anteprima, bisogna gestire
la fine interazione nei i tre TextField: leggere il contenuto numerico
di tutti e tre
(prelevando la stringa con getText e convertendola in intero
come visto nella lezione 2),
costruire il colore corrispondente e assegnarlo come
colore di sfondo all'etichetta dell'anteprima (metodo setBackground).
Bisogna gestire il caso in cui l'utente non abbia digitato un numero
oppure abbia messo un numero non valido (non tra 0 e 255).
In quel caso si puo' rimettere il numero che c'era
e mandare un messaggio di errore nel TextField dei messaggi.
Il colore che c'era si ottiene
leggendo il colore di sfondo dell'etichetta anteprima e
prendendone la componente rossa / verde / blu a seconda del caso.
ActionListener dei Button "sopra" e "sotto"
-- Portare il rettangolo selezionato sopra o sotto
Ottiene il rettangolo selezionato con getSelectedRectangle
(classe RectanglePainter). Se esiste,
lo porta sopra / sotto con raise / lower (classe RectanglePainter).
ActionListener dei Button "nord", "sud", "est", "ovest"
-- Spostamento del rettangolo selezionato
Deve modificare le coordinate del punto di aggancio.
Per esempio per "est" deve incrementare la x.
Bisogna scegliere di quanto, per es. 5 pixel.
Ottiene il rettangolo selezionato con getSelectedRectangle
(classe RectanglePainter). Se esiste,
ottiene la vecchia x del punto di aggancio con getMinX,
le somma 5 e la riassegna con setMinX (interfaccia MyRectangle).
Analogamente gli altri punti cardinali.
Attenzione: per Java le x crescono da sinistra a destra mentre le y
crescono dall'alto verso il basso (asse y rovesciato rispetto al solito)!
ActionListener del Button "copia"
-- Copia del rettangolo selezionato
Ottiene il rettangolo selezionato con getSelectedRectangle
(classe RectanglePainter). Se esiste,
costruisce un nuovo rettangolo a cui assegna stesse dimensioni,
stesso colore e punto di aggancio leggermente spostato
(per es. 5 pixel in piu' su x ed y).
Poi aggiunge il nuovo rettangolo al RectanglePainter.
Inoltre bisogna far terminare il programma quando l'utente chiude la finestra principale (mettendo un WindowListener, ved. lezione 7)!