Quinta esercitazione
Nota: a causa di un bug di Internet Explorer, questa pagina non ? visualizzata correttamente dal browser Microsoft (compare la barra di scorrimento orizzontale, che rende la consultazione difficoltosa). Si consiglia di visualizzare la pagina con Mozilla Firefox.
L'obiettivo dell'esercitazione è creare un'applicazione Java che apra una finestra con un disegno 2D, per esempio una griglia 8x8 per il gioco di Otello.
L'esercitazione consta di tre parti:
- Aprire una finestra e disegnarvi qualcosa, per esempio un'ellisse.
- Invece dell'ellisse, disegnare una griglia 8x8 che occupi l'intera area "utile" della finestra. Ridimensionando la finestra, anche la griglia deve essere ridimensionata.
- Dare all'utente la possibilità di inserire pedine nella griglia. Il programma chiede ripetutamente le coordinate (riga e colonna) di una pedina da inserire, e disegna la pedina.
Soluzione guidata
Prima parte
Creiamo una finestra con un pannello un po' speciale. Il pannello sarà in grado di disegnarsi (cioè di disegnare il suo contenuto, un'ellisse) ogni volta che è necessario.
La soluzione proposta consta di due classi (da definire in altrettanti file). La prima classe contiene il metodo main(), che ha il compito di creare una finestra e di aggiungervi il pannello "speciale".
Il pannello "speciale" è un pannello di tipo (cioè di classe) PannellOtello; la classe PannellOtello è una classe che definiremo in seguito. Dunque il metodo main() NON deve inserire un JPanel nella finestra, ma un oggetto di tipo PannellOtello. Se escludiamo questa piccola differenza, il codice del metodo main() è del tutto analogo a quello delle classi scritte nelle esercitazioni precedenti.
//File Eserc5.java import java.awt.*; import javax.swing.*; public class Eserc5 { public static void main(String[] args) { JFrame finestra=new JFrame("Gioco dell'Otello"); PannellOtello pannello=new PannellOtello(); finestra.setContentPane(pannello); finestra.setSize(200, 200); finestra.setVisible(true); } } |
Definiamo ora la classe PannellOtello, che è semplicemente un JPanel "modificato". La definiamo a partire dalla classe JPanel: estendendo la classe JPanel, la classe PannellOtello eredita il suo comportamento, cioè tutti i suoi metodi e le sue variabili; e può essere usata "al posto" di un JPanel, quale pannello principale di una finestra:
//File PannellOtello.java // //Deve essere contenuto nella stessa cartella //del file Eserc5.java. // //Si consiglia di creare un nuovo progetto JCreator //con i due file import java.awt.*; import javax.swing.*; import java.awt.geom.*; public class PannellOtello extends JPanel { // ... // codice della classe // ... } |
Ora vogliamo inserire il codice della classe. In che cosa si discosta il nostro PannellOtello da un comunissimo JPanel? Nell'aspetto: un JPanel ordinario è un semplice rettangolo grigio e vuoto, mentre il nostro PannellOtello contiene un bel disegno.
Allora ridefinamo il metodo paintComponent() della classe PannellOtello. Ricordiamo che il metodo paintComponent() è invocato automaticamente dal gestore delle finestre ogniqualvolta la finestra deve essere ridisegnata per qualche motivo. Il metodo paintComponent() è già presente in PannellOtello, perché viene ereditato dalla classe JPanel; ma il metodo paintComponent() ereditato non fa niente di interessante!
Il nostro paintComponent() invece deve produrre un disegno; ecco perché lo dobbiamo ridefinire, nel seguente modo: (ci troviamo nella classe PannellOtello)
//Nella classe PannellOtello public void paintComponent(Graphics g) { // ... // codice del paintComponent // ... } |
Che cosa fa paintComponent()? Prima di tutto invoca il "vecchio" paintComponent() della classe JPanel. In questo modo siamo sicuri che, se il vecchio paintComponent() faceva qualche cosa di utile prima di essere ridefinito, continuerà a farla anche dopo:
super.paintComponent(g); |
Ricordiamo che nelle versioni recenti di Java il parametro g, ricevuto in input da paintComponent() quando viene invocato dal gestore delle finestre, è un contesto grafico di tipo Graphics2D (estensione di Graphics). Per poter usare tutti i metodi della classe Graphics2D occorre fare un cast, che ci fornisce un riferimento di tipo Graphics2D al nostro contesto grafico:
Graphics2D g2d=(Graphics2D) g; |
Ora siamo pronti per disegnare la nostra ellisse:
Ellipse2D.Double e=new Ellipse2D.Double(0,0,50,50); g2d.draw(e); |
Compilare ed eseguire il programma, e verificare che funzioni.
Seconda parte
Eliminiamo le due istruzioni che disegnano l'ellisse, e al loro posto disegnamo la griglia.
Per prima cosa dobbiamo scoprire le dimensioni del pannello: lo possiamo fare invocando i metodi getWidth() e getHeight() sull'oggetto di invocazione (ricordiamoci che ci troviamo dentro il metodo paintComponent(), che viene invocato dal gestore delle finestre su un particolare oggetto di tipo PannellOtello - l'oggetto che abbiamo creato dentro il metodo main()). Per accedere all'oggetto di invocazione possiamo usare il riferimento this:
//Nel metodo paintComponent() int w=this.getWidth(); int h=this.getHeight(); |
this può anche essere usato implicitamente (e quindi omesso):
//Al posto del precedente si può mettere int w=getWidth(); int h=getHeight(); |
Ora che abbiamo w e h, dividiamoli per 8: otterremo le dimensioni di una singola casella della griglia:
int w8=w/8.0; int h8=h/8.0; |
Osserviamo che abbiamo fatto una divisione tra numeri in virgola mobile, perché il divisore è 8.0. Se avessimo scritto semplicemente w/8, avremmo effettuato una divisione fra interi, con quoziente intero, commettendo un errore di troncamento (potete provare a vedere che cosa succede alla griglia in questo caso).
Perfetto, ora possiamo disegnare le linee della griglia. Iniziamo dalle linee verticali. Sono 9 linee in tutto. La riga i-esima partirà dal punto di coordinate (0, i*w8) (in alto) e terminerà nel punto di coordinate (h, i*w8) (in basso). Il codice per disegnarle tutte è:
for(i=0; i<=8; i++) { Line2D.Double l=new Line2D.Double(i*w8, 0, i*w8, h); g2d.draw(l); } |
Facciamo lo stesso con le 9 linee orizzontali:
for(i=0; i<=8; i++) { Line2D.Double l=new Line2D.Double(0, i*h8, w, i*h8); g2d.draw(l); } |
Compiliamo ed eseguiamo... e una fumante finestra alla griglia è servita!
Terza parte
Ed ora le pedine. Per inserirle bisogna far "parlare" le due classi: il metodo main(), a cui affidiamo il compito di chiedere (in continuazione) l'input all'utente (le coordinate di una nuova pedina da inserire); ed il metodo paintComponent() della classe PannellOtello, che dovrà disegnare tutte le pedine inserite.
A tal fine definiamo alcune variabili d'istanza, pubbliche, nella classe PannellOtello. Le seguenti variabili d'istanza contengono tutte le informazioni necessarie per disegnare le pedine. La variabile intera nPedine contiene il numero di pedine disegnate finora (inizialmente 0). L'array di interi pedineRiga[] contiene, nell'i-esimo elemento, la riga dell'i-esima pedina. L'array di interi pedineColonna[] contiene, nell'i-esimo elemento, la colonna dell'i-esima pedina.
//Nella classe PannellOtello public int nPedine; public int[] pedineRiga; public int[] pedineColonna; |
Ora costruiamo gli array. Lo facciamo nel metodo main(). Poiché la nostra griglia ha 64 caselle, array di 64 elementi sono sufficienti.
//Nel metodo main() pannello.nPedine=0; pannello.pedineColonna=new int[64]; pannello.pedineRiga=new int[64]; |
Nel metodo main() inseriamo un ciclo che, fino a quando l'utente non digita la parola fine, effettua queste operazioni:
- chiede all'utente la riga della prossima pedina da inserire - oppure fine
- chiede all'utente la colonna della prossima pedina da inserire
- inserisce riga e colonna - convertite da stringhe a interi - nei prossimi elementi degli array
- incrementa il numero nPedine di pedine inserite finora
- invoca il metodo repaint() sulla finestra.
Quest'ultima operazione è necessaria per far comparire immediatamente la pedina inserita. Infatti il metodo paintComponent(), di norma, è invocato dal gestore delle finestre solo quando è necessario (per esempio perché la finestra viene ridimensionata). Tramite l'invocazione del metodo repaint() chiediamo al gestore delle finestre di ridisegnare la finestra il più presto possibile, sicché anche l'ultima pedina inserita venga disegnata.
//Nel metodo main, alla fine String s; do { s=JOptionPane.showInputDialog(finestra, "Inserisci la coordinata x (o scrivi fine)"); if(!s.equals("fine")) { //Se l'utente ha immesso un numero, e non la parola "fine" pannello.pedineColonna[pannello.nPedine]=Integer.parseInt(s)-1; s=JOptionPane.showInputDialog(finestra, "Inserisci la coordinata y"); pannello.pedineRiga[pannello.nPedine]=Integer.parseInt(s)-1; pannello.nPedine++; //Aggiorna il numero di pedine: è aumentato di 1 finestra.repaint(); //Ridisegna la finestra, con la nuova pedina } } while(!s.equals("fine")); |
Ora aggiungiamo al metodo paintComponent() il codice necessario a disegnare le pedine. Dobbiamo scandire i primi nPedine elementi degli array pedineRiga[] e pedineColonna[], e disegnare le pedine corrispondenti (ellissi piene):
//Nel metodo paintComponent() for(i=0; i<nPedine; i++) { Ellipse2D.Double e=new Ellipse2D.Double(pedineColonna[i]*w8, pedineRiga[i]*h8, w8, h8); g2d.fill(e); //Disegna una pedina PIENA } |
Per finire, un tocco di classe. Coloriamo le pedine alternativamente di colore rosso e blu. All'interno del ciclo for ci basiamo sul valore dell'indice i: se i è pari, impostiamo il colore del contesto grafico a rosso (prima di disegnare la pedina); se i è dispari, lo impostiamo a blu.
Ricordando che i%2 restituisce il resto della divisione di i per 2 (0 se i è pari, 1 se i è dispari), il codice da inserire dentro il for è il seguente:
if(i%2==0) g2d.setColor(Color.blue); else g2d.setColor(Color.red); |
Soluzione completa
Per comodità riportiamo il codice completo, spiegato finora, dei due file java.
Commenti dei visitatori
Accedi per lasciare commenti.