Rilevazione eventi

Rilevazione del click del mouse

Gli applet visti fino ad ora si limitano a stampare qualcosa sullo schermo, e basta. In questa pagina vediamo in che modo un applet può reagire al click del mouse.

Quello che vogliamo fare, in generale, è dire all'interprete che ogni volta che viene premuto il tasto del mouse deve eseguire una sequenza di istruzioni. Il programma di esempio che vediamo è quello che disegna un quadratino nella posizione in cui si trova il cursore.

Un applet sensibile al click del mouse ha questa struttura:

import java.awt.*;

public class nomefile extends java.applet.Applet {
  public boolean mouseDown(Event e, int x, int y) {
    Graphics g=new Graphics();

    istruzioni

    return True;
  }
}
Le istruzioni sono quelle che vanno eseguite in risposta al click del mouse. Queste istruzioni possono utilizzare il contenuto delle due variabili x e y, in cui è memorizzata la posizione del cursore nel momento in cui è stato premuto il pulsante.

Nel caso in cui si vuole disegnare un quadratino ogni volta che viene premuto il pulsante, l'operazione da eseguire è una fillRect. Il programma che disegna i quadratini è PuntiMouse.java:

/*
  Disegna dei punti, sulla base del click del mouse
*/

import java.awt.*;

public class PuntiMouse extends java.applet.Applet {
  public boolean mouseDown(Event e, int x, int y) {
    Graphics g=getGraphics();

    g.fillRect(x-2,y-2,4,4);

    return true;
  }
}

La seguente immagine mostra come appare la finestra dopo che il pulsante è stato premuto su cinque punti diversi: in ogni punto è stato disegnato un quadratino.

Croci e cerchi

Vediamo ora un altro esempio: vogliamo sempre disegnare delle figure geometriche nel punto dove viene premuto il pulsante del mouse, soltanto che questa volta la figura deve essere una croce oppure un cerchio. In particolare, se il pulsante del mouse viene premuto in una posizione in cui x è minore o uguale a 200, si deve disegnare una croce, mentre invece si disegna un cerchio se x è maggiore di 200.

Concettualmente, questo esercizio non presenta nessuna difficoltà: si tratta sempre di fare dei disegni ogni volta ogni volta che il pulsante del mouse viene premuto. Quindi, occorre semplicemente mettere delle istruzioni nel blocco mouseDown in modo che venga fatto il disegno appropriato. Questa volta la cosa da disegnare dipende dal valore della coordinata x, per cui la procedura mouseDown contiene una istruzione condizionale, in cui la condizione è x<=200. Se questa condizione è verificata si disegna la croce, altrimenti si disegna il cerchio.

Il programma completo CrociCerchi.java è riportato qui sotto.

/*
  Disegna croci e cerchi
*/

import java.awt.*;

public class CrociCerchi extends java.applet.Applet {
  public boolean mouseDown(Event e, int x, int y) {
    Graphics g=getGraphics();

    if( x<=200 ) {
      g.drawLine(x,y-5,x,y+5);
      g.drawLine(x-5,y,x+5,y);
    }
    else {
      g.drawOval(x-5,y-5,10,10);
    }

    return true;
  }
}

In questo programma si può osservare come le variabili x e y sono variabili come tutte le altre. L'unica cosa che le differenzia dalle variabili definite localmente è che il loro valore iniziale rappresenta le coordinate del punto in cui si è premuto il pulsante. Per il resto, si possono usare come qualsiasi altra variabile intera, per cui per esempio ci si può fare un test come x<=200.

Coordinate dei punti

Si risolva il seguente problema: ogni volta che si preme il pulsante del mouse in una posizione, si stampi in alto a sinistra nella finestra le coordinate in cui il pulsante è stato premuto.

Dal momento che vogliamo fare qualcosa in risposta al click, occorre mettere le istruzioni di stampa dentro la procedura mouseDown. Quello che occorre fare è semplicemente stampare le coordinate, che sono date da x e y. Una prima versione del programma potrebbe essere la seguente:

import java.awt.*;

public class Coordinate extends java.applet.Applet {
  public boolean mouseDown(Event e, int x, int y) {
    Graphics g=getGraphics();

    g.drawString("Coordinate: "+x+" "+y,0,20);

    return true;
  }
}
Il problema è che le coordinate del punto successivo vengono scritte sopra a quelle precedenti, senza prima cancellarle. È quindi necessario cancellare la scritta fatta in precedenza prima di scriverne una nuova. Il programma definitivo Coordinate.java è quindi fatto cosí:

/*
  Stampa le coordinate dei punti dove viene fatto click.
*/

import java.awt.*;

public class Coordinate extends java.applet.Applet {
  public boolean mouseDown(Event e, int x, int y) {
    Graphics g=getGraphics();

    g.clearRect(0,0,200,20);
    g.drawString("Coordinate: "+x+" "+y,0,20);

    return true;
  }
}

Paint e mouseDown insieme

I programmi visti fino ad ora sono di due tipi: quelli con paint, che disegnano qualcosa ma non reagiscono ai click del mouse, e quelli con mouseDown, che invece reagiscono al click del mouse, ma disegnano una finestra che inizialmente è vuota. Ci sono casi in cui si vogliono fare tutte e due queste cose, ossia si deve disegnare qualcosa all'inizio, e poi reagire alla pressione del tasto del mouse con azioni opportune.

Come abbiamo visto, l'applet che disegna quando viene premuto il tasto del mouse ha questa struttura:

import java.awt.*;

public class nomefile extends java.applet.Applet {
  public boolean mouseDown(Event e, int x, int y) {
    ...
  }
}
L'applet che fa un disegno all'inizio e poi basta ha invece una struttura leggermente diversa:

import java.awt.*;

public class nomefile extends java.applet.Applet {
  public void paint(Graphics g) {
    ...
  }
}
La differenza sta nel fatto che il primo tipo di applet contiene un blocco di istruzioni mouseDown(..) { ... }, mentre il secondo tipo contiene un blocco paint(..) { ... }. Come si è già detto, le istruzioni che si trovano all'interno delle parentesi graffe di mouseDown(..) { ... } sono quelle che vengono eseguite quando il pulsante del mouse viene premuto.

Nel caso degli applet che disegnano, il blocco di istruzioni all'interno delle parentesi graffe di paint(..) { ... } vengono invece eseguite comunque all'inizio della esecuzione dell'applet.

È possibile mettere nello stesso applet sia il blocco mouseDown(..) { ... } che quello paint(..) { ... }. In questo modo, possiamo specificare cosa deve succedere sia quando si preme il pulsante del mouse che quando si deve disegnare la finestra. Lo schema generale, a questo punto, risulta il seguente:

import java.awt.*;

public class nomefile extends java.applet.Applet {
  public boolean mouseDown(Event e, int x, int y) {
    bloccoA
  }

  public void paint(Graphics g) {
    bloccoB
  }
}
La sequenza di istruzioni bloccoA viene eseguito quando si preme il pulsante del mouse, mentre bloccoB si esegue all'inizio della esecuzione. Questo è molto interessante, e ci consente di realizzare un applet in cui possiamo contemporanemente disegnare qualcosa e rispondere al click.

Supponiamo per esempio di voler modificare l'applet CrociCerchi.java che disegna una croce se si fa click in un punto con x<=200 e un cerchio altrimenti. Vogliamo tracciare una linea sul confine fra i punti dove si disegnano le croci e i punti dove si disegnano i cerchi.

La linea che separa le due regioni è una linea verticale in cui x vale 200. Per esempio, si può tracciare con drawLine(200,0,200,500). Dal momento che questa linea va disegnata ogni volta che l'applet parte, la mettiamo all'interno di paint. Il programma complessivo CrociCerchiLinea.java è riportato qui sotto.

/*
  Disegna croci e cerchi, piu' la linea
  che li separa.
*/

import java.awt.*;

public class CrociCerchiLinea extends java.applet.Applet {

	/* risponde al click del mouse */
  public boolean mouseDown(Event e, int x, int y) {
    Graphics g=getGraphics();

    if( x<=200 ) {
      g.drawLine(x,y-5,x,y+5);
      g.drawLine(x-5,y,x+5,y);
    }
    else {
      g.drawOval(x-5,y-5,10,10);
    }

    return true;
  }

	/* disegna la linea di separazione */
  public void paint(Graphics g) {
    g.drawLine(200,0,200,500);
  }
}

Griglia e punti

Realizziamo un applet che disegna prima una griglia di linee a distanza 30 l'una dall'altra, e poi disegna un quadratino in ogni punto in cui viene premuto il pulsante del mouse.

La parte in cui si risponde al click è la stessa del programma PuntiMouse.java: l'effetto della pressione deve essere il disegno del rettangolo centrato intorno alla posizione corrente del mouse:

  public boolean mouseDown(Event e, int x, int y) {
    Graphics g=getGraphics();

    g.fillRect(x-2,y-2,4,4);

    return true;
  }
La parte di disegno della griglia va eseguita subito, e non solo quando si preme il pulsante del mouse, per cui le istruzioni di disegno della griglia vanno messe all'interno del paint (la spiegazione di come si disegna la griglia è stata vista in una pagina precedente):

  public void paint(Graphics g) {
    int i, j;
    
    for(i=0; i<=300; i=i+30) {
      g.drawLine(0,i,300,i);
      g.drawLine(i,0,i,300);
    }
  }

Il programma DrawAndClick.java disegna una griglia, più un quadratino per ogni punto in cui si preme il pulsante.

/*
  Disegna nella finestra, e aggiunge quadrati dove
viene premuto il pulsante del mouse.
*/

import java.awt.*;

public class DrawAndClick extends java.applet.Applet {
  public boolean mouseDown(Event e, int x, int y) {
    Graphics g=getGraphics();

    g.fillRect(x-2,y-2,4,4);

    return true;
  }

  public void paint(Graphics g) {
    int i, j;
    
    for(i=0; i<=300; i=i+30) {
      g.drawLine(0,i,300,i);
      g.drawLine(i,0,i,300);
    }
  }
}

Il risultato della esecuzione, premendo il pulsante in alcuni punti, è il seguente.

Il problema della cancellazione

Si provi ad eseguire il programma PuntiMouse.java, che disegna un rettangolo in ogni punto in cui viene premuto il pulsante del mouse, in questo modo:

Il risultato è che i punti che erano stati in precedenza disegnati sono spariti. Un risultato simile si ottiene con gli altri programmi visti fino ad ora. In particolare, si nota che tutti i disegni che vengono fatti da istruzioni che stanno dentro paint rimangono, mentre i disegni fatti da istruzioni che stanno dentro mouseDown sono spariti. Per esempio, nel programma CrociCerchiLinea.java la linea verticale viene disegnata da paint, e in effetti è l'unica cosa che rimane: tutte le croci e i cerchi, che sono disegnati dentro mouseDown, spariscono quando la finestra viene coperta e poi scoperta di nuovo.

A cosa è dovuto questo curioso fenomeno? Per dare una risposta a questa domanda, è necessario approfondire l'anatomia di un applet. Come abbiamo visto, gli applet hanno questa struttura:

import java.awt.*;

public class nomefile extends java.applet.Applet {
  public boolean mouseDown(Event e, int x, int y) {
    bloccoA
  }

  public void paint(Graphics g) {
    bloccoB
  }
}
Come si è già detto, le istruzioni che si trovano all'interno delle parentesi graffe di mouseDown(..) { ... } sono quelle che vengono eseguite ogni volta che il pulsante del mouse viene premuto. Le istruzioni all'interno delle parentesi graffe di paint(..) { ... } vengono invece eseguite quando si lancia l'applet. Finora si è detto che l'unico momento in cui queste istruzioni vengono eseguite è l'inizio. Questo non è del tutto esatto.

Prima di analizzare questo punto, occorre essere più precisi su quello che succede quando una finestra viene coperta. Quello che in effetti succede è che il contenuto della finestra (quello che è stato disegnato al suo interno) viene cancellato quando la finestra diventa invisibile. Se per esempio si copre la finestra con un'altra finestra, quello che era stato disegnato viene cancellato.

Questo sembra in contraddizione con il fatto che non c'è nessuna cancellazione di quello che è stato disegnato nel paint. Non c'è invece nessuna contraddizione. È semplicemente stata detta una cosa imprecisa a proposito di paint. Non è vero che le sue istruzioni vengono eseguite una sola volta all'inizio. Al contrario,

la sequenza paint viene eseguita ogni volta che parte della finestra diventa visibile
Quando una finestra viene coperta e poi scoperta, i disegni effettuati all'interno di paint vengono cancellati come anche tutti quelli fatti all'interno di mouseDown. Quando la finestra diventa di nuovo visibile, le istruzioni dentro paint vengono eseguite di nuovo, per cui i disegni che sono stati fatti da istruzioni dentro paint vengono semplicemente disegnati di nuovo.

Possiamo dire che non è vero che le cose disegnate da paint rimangono quando la finestra viene coperta e poi scoperta. Quello che succede in realtà è che tutto viene cancellato, solo che i disegni fatti da paint vengono disegnati di nuovo quando la finestra riappare.

Quando l'applet viene lanciato, la finestra diventa visibile e quindi vengono eseguite le istruzioni che stanno fra parentesi graffe in paint(..) { ... }. Ogni volta che la finestra (o una sua parte) diventa invisibile, il contenuto viene cancellato. Quando la finestra torna a essere visibile, le istruzioni del blocco paint vengono eseguite di nuovo, per cui viene fatto di nuovo il disegno.

Riassumendo, possiamo dire che:

Da questo si capisce perchè solo i disegni fatti da mouseDown non riappaiono: quando la finestra diventa di nuovo visibile, solo le istruzioni che stanno dentro paint vengono eseguite di nuovo.

Variabili globali

Vediamo come si può fare per evitare la cancellazione delle cose fatte da mouseDown. Prendiamo il programma CrociCerchiLinea.java e facciamo in modo che almeno l'ultimo punto non venga cancellato. Come si è detto, in effetti tutto viene cancellato quando la finestra è coperta. L'unica cosa che si può fare è fare in modo che i disegni fatti da mouseDown vengano ridisegnati di nuovo quando la finestra appare di nuovo.

Sappiamo che quando la finestra appare vengono eseguite le istruzioni dentro paint. Quindi, è necessario che le cose disegnate da mouseDown vengano disegnate di nuovo da paint quando la finestra diventa di nuovo visibile.

Iniziamo con un caso semplice: modifichiamo l'applet CrociCerchiLinea.java in modo che l'ultima figura disegnata da mouseDown sia permanente. Per fare questo è necessario memorizzare la posizione e forma di quest'ultima figura in modo tale che paint possa leggere questo valore, e ridisegnare la figura quando serve. Il meccanismo che abbiamo per memorizzare dati è quello delle variabili. Resta però il problema di dove dichiarare queste variabili.

Supponiamo infatti di voler memorizzare le coordinate x e y dell'ultima posizione in cui il pulsante è stato premuto. Questi due numeri sono chiaramente sufficienti per disegnare di nuovo l'ultima figura fatta. Usiamo quindi due variabili xultimo e yultimo. Il problema è che la posizione dell'ultimo click viene memorizzata dentro mouseDown, ma questi dati sono necessari a paint per poter disegnare di nuovo l'ultima figura quando la finestra diventa nuovamente visibile. Se dichiariamo queste due variabili dentro mouseDown, la procedura paint non la può usare; se le dichiariamo dentro paint allora mouseDown non ci può scrivere; dichiarare queste due variabili in entrambe le procedure non serve, dal momento che variabili in procedure diverse sono diverse (corrispondono a zone di memoria diverse) anche se hanno lo stesso nome.

Il meccanismo che si usa per permettere ai due blocchi interni a mouseDown e paint di condividere delle variabili è quello di dichiararle come variabili globali. Una variabile è globale se viene dichiarata all'interno delle parentesi graffe di class ... { ... } ma prima delle procedure (cioè prima di public boolean mouseDown). Le variabili dichiarate in questo modo si dicono appunto globali, e sono variabili che tutte le procedure possono usare. Nel nostro caso, dichiarando una variabile globale xultimo di tipo intero, sia mouseDown che paint possono accedere ad essa. In questo modo, all'interno di mouseDown possiamo memorizzare in questa variabile la coordinata x dell'ultimo punto in cui il pulsante è stato premuto, mentre all'interno di paint possiamo utilizzare il valore che è stato memorizzato in precedenza.

Per poter ripetere l'ultima figura disegnata sono chiaramente necessari due valori, che corrispondono alle due coordinate dell'ultimo punto in cui il pulsante del mouse è stato premuto. Le seguenti dichiarazioni creano due variabili globali in cui è possibile memorizzare questi valori.

import java.awt.*;

public class CrociCerchiLineaUltimo extends java.applet.Applet {

  int xultimo=-4, yultimo=-4;

        /* risponde al click del mouse */
  public boolean mouseDown(Event e, int x, int y) {

  ...
All'interno di mouseDown occorre aggiungere delle istruzioni che memorizzano i valori di x e y nelle variabili xultimo e yultimo. Le due istruzioni necessarie sono chiaramente xultimo=x e yultimo=y.

L'ultima figura deve venire poi disegnata di nuovo da paint. Per fare questo, aggiungiamo, all'interno di paint, le istruzioni che disegnano la figura appropriata alle coordinata xultimo e yultimo, che rappresentano le coordinate dell'ultimo punto in cui il pulsante del mouse è stato premuto. Il codice completo del programma CrociCerchiLineaUltimo.java è riportato qui sotto.

/*
  Disegna croci e cerchi, piu' la linea
  che li separa.
*/

import java.awt.*;

public class CrociCerchiLineaUltimo extends java.applet.Applet {

  int xultimo=-4, yultimo=-4;

	/* risponde al click del mouse */
  public boolean mouseDown(Event e, int x, int y) {
    Graphics g=getGraphics();

    if( x<=200 ) {
      g.drawLine(x,y-5,x,y+5);
      g.drawLine(x-5,y,x+5,y);
    }
    else {
      g.drawOval(x-5,y-5,10,10);
    }

    xultimo=x;
    yultimo=y;

    return true;
  }

	/* disegna la linea di separazione */
  public void paint(Graphics g) {
    g.drawLine(200,0,200,500);

    if( xultimo<=200 ) {
      g.drawLine(xultimo,yultimo-5,xultimo,yultimo+5);
      g.drawLine(xultimo-5,yultimo,xultimo+5,yultimo);
    }
    else {
      g.drawOval(xultimo-5,yultimo-5,10,10);
    }

  }
}

Va fatta una precisazione sul perchè le variabili xultimo e yultimo hanno valori iniziali -4. Si tratta di un espediente per evitare il disegno di una figura geometrica quando l'applet parte. Come si è detto, quello che sta dentro paint viene eseguito una prima volta, sempre, quando si esegue il programma (dopo che la finestra è stata creata). All'inizio il pulsante del mouse non è stato mai premuto, per cui le istruzioni di mouseDown non sono mai state eseguite. Quindi, le variabili xultimo e yultimo non contengono l'ultimo punto (semplicemente perchè non esiste un punto in cui il mouse è stato premuto).

Quando l'applet viene lanciato, si esegue la procedura paint. Questa disegna la linea verticale, e poi un cerchio o una croce. Dal momento che inizialmente le due variabili xultimo e yultimo valgono -4, la figura disegnata è fuori dall'area visibile.

Ogni volta che viene premuto il pulsante del mouse, viene disegnata una croce oppure un cerchio. Inoltre, i valori delle coordinate della posizione del cursore vengono salvate nelle variabilie xultimo e yultimo. Se si preme nuovamente il pulsante, si fa il nuovo disegno, e nelle variabili xultimo e yultimo ci si mettono le nuove coordinate. Quindi, queste variabili contengono sempre le coordinate dell'ultimo punto di click. Quando la finestra viene coperta e poi scoperta di nuovo, viene eseguita la procedura paint, che disegna di nuovo la linea verticale, e poi disegna una croce oppure un cerchio alle coordinate xultimo e yultimo. Il programma fa esattamente quello che era richiesto: se la finestra viene coperta e poi scoperta, l'ultima figura disegnata rimane.

Rendere permanenti tutte le modifiche

Analizziamo ora il problema di evitare la cancellazione di tutte le figure geometriche che vengono disegnate da mouseDown. In effetti, abbiamo già tutte le informazioni necessarie per realizzare un programma del genere. Si tratta infatti di memorizzare le caratteristiche di tutti i punti in cui il mouse viene premuto in delle variabili globali, in modo tale che paint possa fare tutti i disegni da capo.

Consideriamo quindi il programma DrawAndClick.java. Cosí come è scritto, i quadratini che corrispondono ai click del mouse non vengono disegnati ogni volta che la finestra diventa visibile. Il motivo è chiaro: viene fatto solo quello che si trova nel blocco del paint(){ ... }, e il disegno dei quadratini non si trova lí dentro.

Per rendere questi quadratini permanenti, è necessario che all'interno di paint vengano disegnati tutti i quadratini che corrispondono a tutti i punti in cui è stato premuto il pulsante. È quindi necessario memorizzare via via tutti i punti in cui è stato premuto il pulsante.

Dal momento che occorre memorizzare un numero non noto di valori per le coordinate x e y di ogni punto, usiamo due vettori: xvett e yvett. Ci serve poi sapere quanti punti sono stati disegnati fino a questo momento, e per questo usiamo una variabile intera npunti.

All'inizio, il numero dei punti disegnati è zero: questo è quindi il valore iniziale della variabile npunti. Il primo punto viene memorizzato nelle variabili xvett[0] e yvett[0], e a questo corrisponde un incremento della variabile npunti. Al secondo click del mouse, le variabili da usare sono xvett[1] e yvett[1], e si incrementa nuovamente npunti. In poche parole: a ogni click si memorizza il valore di x in xvett[npunti] e il valore di y in yvett[npunti]. La struttura di mouseDown() diventa quindi:

  public boolean mouseDown(Event e, int x, int y) {
    int i;

    g.fillRect(x-2,y-2,4,4);

    xvett[npunti]=x;
    yvett[npunti]=y;
    npunti=npunti+1;

    return true;
  }
Per quello che riguarda il disegno: all'interno di paint() occorre fare il disegno di tutti i punti che sono stati toccati fino a questo momento. Dal momento che le coordinate del primo punto sono memorizzate in xvett[0] e yvett[0], mentre le ultime sono in xvett[npunti-1] e yvett[npunti-1], è chiaro che occorre fare un ciclo in cui una variabile va da 0 a npunti-1 aumentando di 1 ogni volta:

  public void paint(Graphics g) {
    int i;

    for(i=0; i<=npunti-1; i++) 
      g.fillRect(xvett[i]-2,yvett[i]-2,4,4);
  }

I due vettori xvett e yvett e la variabile npunti vanno dichiarati come variabili globali, dal momento che sia mouseDown che paint li devono usare.

public class PuntiMouseDue extends java.applet.Applet {
  int xvett[] = new int[100];
  int yvett[] = new int[100];
  int npunti=0;

  public boolean mouseDown(Event e, int x, int y) {
    ...
  }

  public void paint(Graphics g) {
    ...
  }
}
Con questo tipo di dichiarazione, i due vettori xvett e yvett e la variabile npunti si possono usare in entrambi i blocchi di istruzioni, e sono esattamente gli stessi. In questo modo, ogni volta che viene premuto il pulsante del mouse, si attiva la sequenza mouseDown, che disegna il punto, memorizza le posizioni nei vettori e incrementa la variabile npunti. Ogni volta che la finestra diventa visibile, si ridisegnano tutti i punti memorizzati nei vettori. Il programma finale PuntiMouseDue.java risulta quindi il seguente:

/*
  Disegna dei punti, sulla base del click del mouse
*/

import java.awt.*;

public class PuntiMouseDue extends java.applet.Applet {
  int xvett[] = new int[100];
  int yvett[] = new int[100];
  int npunti=0;

  public boolean mouseDown(Event e, int x, int y) {
    Graphics g=getGraphics();

    g.fillRect(x-2,y-2,4,4);

    xvett[npunti]=x;
    yvett[npunti]=y;
    npunti=npunti+1;

    return true;
  }

  public void paint(Graphics g) {
    int i;

    for(i=0; i<=npunti-1; i++) 
      g.fillRect(xvett[i]-2,yvett[i]-2,4,4);
  }
}

Sposta una figura

Il problema da risolvere è questo: scrivere un applet che disegna un quadratino riempito in ogni punto in cui si preme il pulsante del mouse; inoltre, quando si preme di nuovo il pulsante, va disegnato un nuovo rettangolo, ma il precedente va cancellato.

Questo programma è completamente centrato intorno alla funzione mouseDown. Infatti, le uniche operazioni da fare sono:

Disegnare il quadrato nella nuova posizione è facile: basta mettere l'istruzione g.drawRect(x-5,y-5,10,10) dentro mouseDown.

La cosa più complicata è cancellare il quadrato che era stato disegnato in precedenza. Per fare questo è necessario, ogni volta che si disegna un quadrato, memorizzare la sua posizione, in modo tale che al click successivo il quadrato si possa cancellare. Quindi, occorre salvare i valori di x e y in modo che siano disponibili la successiva volta che si preme il pulsante.

Un metodo che a prima vista può sembrare valido è questo:

  public boolean mouseDown(Event e, int x, int y) {
    Graphics g=getGraphics();
    int xultimo, yultimo;		/* PROGRAMMA ERRATO */

    g.clearRect(xultimo-5,yultimo-5,10,10);

    g.fillRect(x-5,y-5,10,10);

    xultimo=x;
    yultimo=y;

    return true;
  }
In effetti, in questa funzione i valori di x e y vengono memorizzati nelle variabili xultimo e yultimo; alla successiva chiamata, viene cancellato il quadratino messo in questa posizione. Questo sistema non funziona perchè vale la seguente regola:

quando una procedura termina, il contenuto delle sue variabili locali viene cancellato.
Nel nostro caso, quando viene premuto il pulsante, i valori di x e y vengono copiati nelle variabili xultimo e yultimo, però queste due variabili vengono cancellate quando si arriva alla fine della procedura mouseDown. Al successivo click del mouse non sarà più possibile cancellare il quadratino precedente.

Visto che la variabili locali vengono cancellate alla fine della esecuzione della procedura, l'unico altro metodo che abbiamo per memorizzare in modo permanente questi valori è quello di copiarne il valore in due variabili globali. Definiamo quindi xultimo e yultimo come variabili globali. Ogni volta che si preme il pulsante del mouse, i valori di x e y vengono copiati in queste due variabili. Dal momento che xultimo e yultimo sono globali, il loro valore non viene cancellato quando mouseDown finisce (la cancellazione avviene solo per le variabili locali). Quindi, la volta dopo che il pulsante viene premuto, le variabili globali contengono ancora la posizione del quadrato precedente. È quindi possibile cancellarlo.

Riassumendo, la funzione mouseDown è fatta cosí: per prima cosa si cancella il quadrato precedente, poi si disegna il quadrato nuovo, e poi si memorizza la posizione nelle variabili globali. Manca ora solo da dire i valori iniziali delle variabili globali. Infatti, la prima volta che si esegue mouseDown viene comunque fatta l'operazione di cancellazione del quadrato posizionato in xultimo e yultimo, e quindi viene fatta la cancellazione di un quadrato le cui coordinate sono date dai valori iniziali di queste variabili. Possiamo scegliere di dare inizialmente -10 a xultimo e yultimo, in modo tale che la cancellazione avvenga fuori dall'area visibile della finestra.

Il programma completo Sposta.java risulta quindi fatto nel modo seguente:

/*
  Sposta il rettangolo nel punto in cui viene premuto
  il pulsante.
*/

import java.awt.*;

public class Sposta extends java.applet.Applet {
  int xultimo=-10, yultimo=-10;

  public boolean mouseDown(Event e, int x, int y) {
    Graphics g=getGraphics();

    g.clearRect(xultimo-5,yultimo-5,10,10);

    g.fillRect(x-5,y-5,10,10);

    xultimo=x;
    yultimo=y;

    return true;
  }
}

Disegna una linea spezzata

Scrivere un applet che disegna una linea spezzata seguendo i click del mouse. In altre parole, ogni volta che si fa un click, va aggiunto un segmento fra l'ultimo punto della spezzata e quello in cui si trova il cursore.

Lasciamo da parte il problema di rendere permanente la spezzata, ossia di fare in modo che venga ridisegnata ogni volta che la finestra viene coperta e torna nuovamente visibile. Ci concentriamo invece sul problema del disegno.

Dal momento che si tratta di fare qualcosa ogni volta che si preme il pulsante del mouse, occorre scrivere qualcosa nella procedura mouseDown. In particolare, occorre disegnare un segmento fra l'ultimo punto disegnato e quello in cui si trova il cursore in questo momento. La posizione attuale del cursore è memorizzata in x e y come al solito. Serve anche sapere la posizione del penultimo punto in cui il pulsante era stato premuto. Occorre quindi che la procedura stessa salvi la posizione in due variabili globali, in modo tale che la volta dopo di sappia la posizione precedente.

La funzione mouseDown è quindi fatta in questo modo (per ora):

  public boolean mouseDown(Event e, int x, int y) {
    Graphics g=getGraphics();

    g.drawLine(xultimo,yultimo,x,y);

    xultimo=x;
    yultimo=y;

    return true;
  }
Le due variabili xultimo e yultimo sono dichiarate come variabili globali. Resta ancora un punto da decidere: quale valore iniziale devono avere queste due variabili? Se mettiamo per esempio 0 in entrambe, abbiamo che il primo punto della spezzata si trova sempre nella posizione (0,0). D'altra parte, noi vorremmo far partire la spezzata nel punto in cui il pulsante è stato premuto per la prima volta.

Il problema si risolve facilmente considerando che, la prima volta che il pulsante viene premuto, non occorre disegnare nessuna linea: infatti, questo è il punto iniziale della spezzata; la prima linea sarà fra questo punto e il successivo. Il problema, a questo punto, è: come facciamo, all'interno di mouseDown a capire se stiamo al primo click del mouse oppure no? Il problema non è difficile come sembra.

Supponiamo di mettere -1 come valore iniziale di xultimo. Quando mouseDown viene eseguito per la prima volta, xultimo vale quindi -1. In questa variabile viene poi messa la coordinata x del punto attuale del cursore, che è un numero maggiore o uguale a 0. Alla successiva chiamata, nella variabile c'è ancora questo valore, che viene poi modificato nuovamente con la nuova coordinata. Si può quindi dire che la variabile vale -1 soltanto la prima volta che la procedura viene eseguita, ossia soltanto la prima volta che viene premuto il pulsante del mouse.

Questo ci permette di decidere se disegnare o no la linea. Come si è detto, la linea va disegnata soltanto se questa non è la prima volta che il pulsante è stato premuto. Abbiamo visto che xultimo vale -1 soltanto la prima volta che il pulsante è stato premuto. Quindi, la linea va fatta solo se xultimo non vale -1.

Il programma finale DisegnaSpezzata.java contiene la procedura mouseDown modificata, in cui la linea viene disegnata solo se xultimo ha un valore diverso da -1.

/*
  Disegna un poligono, dati i punti successivi
*/

import java.awt.*;

public class DisegnaSpezzata extends java.applet.Applet {
  int xultimo=-1, yultimo=-1;

  public boolean mouseDown(Event e, int x, int y) {
    Graphics g=getGraphics();

    if( xultimo != -1 ) {
      g.drawLine(xultimo,yultimo,x,y);
    }

    xultimo=x;
    yultimo=y;

    return true;
  }
}

Un cerchio oppure un quadrato

Realizzare un applet che disegna un cerchio. In un angolo, vanno disegnati due rettangoli. Premendo il mouse nel rettangolo in alto il cerchio deve diventare un quadrato, mentre premendo dentro il rettangolo in basso deve tornare a essere un quadrato.

In pratica, il problema si risolve cosí: quando si preme il pulsante, si va a controllare se la posizione è all'interno di uno dei due rettangoli. Se lo è, si cancella la figura disegnata in precedenza e si disegna quella nuova.

Per rendere il disegno permanente, occorre che la procedura paint sappia se la figura corrente è un quadrato oppure un cerchio. Dal momento che la figura viene cambiata da mouseDown, serve una variabile globale. Usiamo quindi una variabile globale intera cerchio. Vogliamo che questa variabile valga 1 se la figura corrente è un cerchio, 0 se è un rettangolo. La variabile deve inizialmente valere 1 (si comincia con il cerchio) e poi deve venire cambiata quando si preme il pulsante in uno dei rettangoli. La procedura paint può usare il valore di questa variabile per capire se deve disegnare un quadrato oppure un cerchio.

Serve solo una ultima precisazione prima di mostrare il codice: in Java, l'unica istruzione per cancellare è la clearRect. Non esiste una istruzione clearOval. Per cancellare il rettangolo usiamo la clearRect, e questo è abbastanza chiaro. Per cancellare il cerchio non possiamo usare clearOval, dal momento che non esiste. Usiamo ancora la clearRect, cancellando una zona rettangolare che contiene il cerchio.

Il programma finale CerchioQuadrato.java è qui sotto:

/*
  Disegna un cerchio oppure un quadrato.
*/

import java.awt.*;

public class CerchioQuadrato extends java.applet.Applet {
  int cerchio=1;

  public boolean mouseDown(Event e, int x, int y) {
    Graphics g=getGraphics();
    
    if( (x>=10) && (x<=70) && (y>=10) && (y<=30) ) {
      g.clearRect(100,100,200,200);

      g.drawRect(150,150,100,100);

      cerchio=0;
    }

    if( (x>=10) && (x<=70) && (y>=50) && (y<=70) ) {
      g.clearRect(100,100,200,200);

      g.drawOval(150,150,100,100);

      cerchio=1;
    }

    return true;
  }

  public void paint(Graphics g) {
    g.drawRect(10,10,60,20);
    g.drawString("quadrato",15,25);

    g.drawRect(10,50,60,20);
    g.drawString("cerchio",15,65);

    if( cerchio==1 ) {
      g.drawOval(150,150,100,100);
    }
    else {
      g.drawRect(150,150,100,100);
    }
  }
}

Repaint

Il programma PuntiMouseDue.java ha una struttura abbastanza tipica: mouseDown, (risposta a un click) effettua dei disegni, e poi memorizza delle informazioni che permettono a paint di ripetere questo disegno ogni volta che la finestra diventa visibile.

Un metodo che permette di semplificare leggermente questo schema è di limitare la mouseDown alla sola memorizzazione delle informazioni, lasciando che sia paint a fare tutti i disegni. Questo però genera un ulteriore problema: le cose che vengono memorizzate da mouseDown non sono visibili fino a che la finestra non viene coperta e poi scoperta. Il problema è che se il disegno viene fatto da paint, allora viene fatto solo in questa condizione.

È però possibile risolvere il problema usando la istruzione repaint() che forza il disegno dell'applet. Si può dire che, ogni volta che si esegue l'istruzione repaint(), è come se l'applet venisse coperto e poi scoperto di nuovo. Nel nostro caso, vogliamo che la finestra venga disegnata di nuovo ogni volta che si preme un pulsante del mouse, per cui l'istruzione repaint() va eseguita per ultima alla fine del blocco mouseDown. Il programma completo PuntiMouseTre.java è riportato qui sotto.

/*
  Disegna dei punti, sulla base del click del mouse
*/

import java.awt.*;

public class PuntiMouseTre extends java.applet.Applet {
  int xvett[] = new int[100];
  int yvett[] = new int[100];
  int npunti=0;

  public boolean mouseDown(Event e, int x, int y) {
    xvett[npunti]=x;
    yvett[npunti]=y;
    npunti=npunti+1;

    repaint();

    return true;
  }

  public void paint(Graphics g) {
    int i;

    for(i=0; i<=npunti-1; i++) 
      g.fillRect(xvett[i]-2,yvett[i]-2,4,4);
  }
}

È importante notare che la funzione mouseDown, in questo caso, non contiene la istruzione Graphics g=getGraphics(). Questa istruzione è necessaria solo se nella funzione si intende disegnare qualcosa. Nel programma di sopra il disegno viene fatto dalla funzione paint, mentre all'interno di mouseDown non si disegna niente, per cui questa istruzione non è necessaria.

Conta i punti

Questo esercizio richiede l'uso della funzione di rilevazione dei punti in cui il pulsante del mouse viene premuto. In particolare, si tratta di disegnare un rettangolo, e poi di contare quante volte il pulsante è stato premuto al suo interno. Questo numero viene poi scritto al di sopra del rettangolo stesso.

Il rettangolo è fatto in questo modo: il punto in alto a sinistra è (20,20), la sua larghezza e altezza sono pari a 100. Lasciamo uno spazio di 20 punti al di sopra del rettangolo per poter scrivere il numero di volte che il pulsante viene premuto al suo interno.

Il problema è il seguente: dobbiamo memorizzare il numero di click all'interno del rettangolo. Questa variabile viene modificata ogni volta che si preme il pulsante, per cui viene modificata dalla funzione mouseDown. Inoltre, viene usata dalla funzione paint, che la stampa sopra il rettangolo. Dal momento che serve in due funzioni diverse, la dichiariamo come variabile globale.

import java.awt.*;

public class ContaPunti extends java.applet.Applet {
  int quantevolte=0;
In questa variabile memorizziamo il numero di volte che il pulsante è stato premuto all'interno del rettangolo. Lasciamo per un momento da parte la funzione mouseDown, e vediamo invece la funzione paint. Per prima cosa, dobbiamo disegnare il rettangolo, e poi dobbiamo disegnare sopra di esso il numero di click al suo interno. Dal momento che questo numero sta nella variabile quantevolte, il programma risulta il seguente:

  public void paint(Graphics g) {
    int i;

    g.drawRect(20,20,100,100);

    g.drawString(""+quantevolte,60, 18);
  }
Passiamo ora alla funzione mouseDown. Questa funzione riceve la posizione del cursore in cui il pulsante è stato premuto: deve controllare se questo punto si trova all'interno del rettangolo, ed eventualmente aumentare di uno la variabile che conta il numero di click.

Il punto si trova all'interno del rettangolo se il valore di x è compreso fra 20 e 120, e se il valore di y sta fra 20 e 120. Se questa condizione è verificata, aumentiamo di uno il valore della variabile quantevolte.

  public boolean mouseDown(Event e, int x, int y) {
    if( (y>=20) && (y<=120) && (x>=20) && (x<=120) ) { 
      quantevolte=quantevolte+1;
    }
    repaint();
    return true;
  }
Chiaramente, dopo aver aumentato il valore della variabile che conta il numero di click, occorre fare di nuovo il disegno, dal momento che va stampato questo nuovo valore al posto di quello vecchio.

Il programma complessivo ContaRect.java viene riportato qui sotto.

/*
  Conta il numero di volte che il pulsante del mouse
  viene premuto all'interno di ognuno dei rettangoli
  disegnati.
*/

import java.awt.*;

public class ContaRect extends java.applet.Applet {
  int quantevolte=0;

  public boolean mouseDown(Event e, int x, int y) {
    if( (y>=20) && (y<=120) && (x>=20) && (x<=120) ) { 
      quantevolte=quantevolte+1;
    }
    repaint();
    return true;
  }

  public void paint(Graphics g) {
    int i;

    g.drawRect(20,20,100,100);

    g.drawString(""+quantevolte,60, 18);
  }
}

Segue l'immagine di una finestra durante l'esecuzione.

Punti in più rettangoli

Questo esercizio è simile al precedente: in particolare, si tratta di disegnare dei rettangoli affiancati, e poi di contare quante volte il pulsante è stato premuto in ognuno di essi. Questo numero viene poi scritto al di sopra del rettangolo stesso.

Supponiamo quindi di avere dieci rettangoli affiancati, ognuno dei quali è largo 20 e alto 100. Lasciamo uno spazio di 20 punti al di sopra del rettangolo per poter scrivere il numero di volte che il pulsante viene premuto all'interno di ciascuno di questi rettangoli.

Il problema è il seguente: dobbiamo memorizzare il numero di click all'interno di ognuno dei rettangoli. Dal momento che si tratta di dieci rettangoli, non è il caso di usare dieci variabili normali. Conviene invece usare un vettore. Dopo la intestazione del programma, dichiariamo quindi un vettore di interi con dieci componenti:

import java.awt.*;

public class ContaPunti extends java.applet.Applet {
  int quantevolte[]={0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
Ogni elemento di questo vettore rappresenta il numero di volte che il pulsante è stato premuto all'interno di ognuno dei rettangoli. Inizialmente tutti gli elementi di questo vettore valgono zero, dal momento che il mouse non è mai stato premuto quando si inizia l'esecuzione dell'applet. Lasciamo per un momento da parte la funzione mouseDown, e vediamo invece la funzione paint. Per prima cosa, dobbiamo disegnare i rettangoli. Questi sono costituiti da due linee orizzontali e da undici linee verticali. Non è difficile verificare che il modo giusto per disegnare queste linee è il seguente:

  public void paint(Graphics g) {
    int i;

    g.drawLine(0,20,20*quantevolte.length,20);
    g.drawLine(0,120,20*quantevolte.length,120);

    for(i=0; i<=quantevolte.length; i=i+1) {
      g.drawLine(i*20, 20, i*20, 120);
    }

    for(i=0; i<=quantevolte.length-1; i=i+1) {
      g.drawString(""+quantevolte[i],i*20+5, 18);
    }
  }
Le prime due istruzioni drawLine disegnano le due linee orizzontali, mentre il ciclo disegna le linee verticali. Il secondo ciclo visualizza il contenuto del vettore quantevolte. Questo è richiesto dal problema, dal momento che sopra ogni rettangolo bisogna visualizzare il numero dei click, che è rappresentato appunto dalle variabili di questo vettore.

Passiamo ora alla funzione mouseDown. Questa funzione riceve la posizione del cursore in cui il pulsante è stato premuto: deve controllare se questo punto si trova all'interno di uno dei rettangoli, ed eventualmente aumentare di uno la variabile che conta il numero di click di quel rettangolo.

La cosa si può fare in questo modo: per prima cosa, se y è minore di 20 o maggiore di 120, siamo sicuramente fuori da tutti i rettangoli. Una volta appurato che 20 <= y <= 120, dobbiamo ancora capire in quale rettangolo ci troviamo; inoltre, la x potrebbe essere troppo grande (cioè siamo a destra dell'ultimo rettangolo). Il primo rettangolo comprende i punti con coordinate x comprese fra 0 e 19, mentre il secondo comprende i punti in cui x va da 20 a 39, ecc. Quindi, se x ha un valore qualsiasi, si può capire in quale rettangolo ci si trova facendo la divisione intero x/20. Infatti, se x è compreso fra 0 e 19 questa espressione vale 0, se x sta fra 20 e 39 l'espressione vale 1, ecc. Quindi, se il pulsante è stato premuto nel punto x, la variabile da aumentare di 1 è quantevolte[x/20]. Da questo si capisce anche che, se x/20 super quantevolte.lenght, allora siamo fuori dalla fila di rettangoli.

Il programma completo ContaPunti.java, che include la funzione mouseDown spiegata sopra, è il seguente:

/*
  Conta il numero di volte che il pulsante del mouse
  viene premuto all'interno di ognuno dei rettangoli
  disegnati.
*/

import java.awt.*;

public class ContaPunti extends java.applet.Applet {
  int quantevolte[]={0, 0, 0, 0, 0, 0, 0, 0, 0, 0};

  public boolean mouseDown(Event e, int x, int y) {
    if( (y>=20) && (y<=120) && (x<20*quantevolte.length) ) { 
      quantevolte[x/20]=quantevolte[x/20]+1;
    }
    repaint();
    return true;
  }

  public void paint(Graphics g) {
    int i;

    g.drawLine(0,20,20*quantevolte.length,20);
    g.drawLine(0,120,20*quantevolte.length,120);

    for(i=0; i<=quantevolte.length; i=i+1) {
      g.drawLine(i*20, 20, i*20, 120);
    }

    for(i=0; i<=quantevolte.length-1; i=i+1) {
      g.drawString(""+quantevolte[i],i*20+5, 18);
    }
  }
}

Rilasciamento pulsante: mouseUp

La funzione mouseUp viene chiamata ogni volta che il pulsante del mouse, che precedentemente era stato premuto, è stato rilasciato. In altre parole, quello che suceede quando si preme il pulsante del mouse è:

si preme il pulsante del mouse
nella posizione (x,y)
viene attivata la funzione mouseDown
si rilascia il pulsante, mentre il mouse è
nella posizione (x,y)
viene attivata la funzione mouseUp

Va notato che la posizione in cui il pulsante viene rilasciato non è necessariamente la stessa in cui il pulsante è stato premuto. Infatti, l'utente potrebbe aver premuto il pulsante, poi mosso il cursore tenendo il pulsante premuto, e poi rilasciato il pulsante in una posizione diversa da quella iniziale. In effetti, questo metodo di tenere premuto il pulsante spostandosi viene spesso usato dai programmi di disegno, quando si vogliono disegnare figure geometriche che sono caratterizzate da due punti (per esempio, rettangoli).

Facciamo un esempio: disegnamo un quadrato nel punto il cui il pulsante viene premuto, e un cerchio nel punto in cui viene rilasciato. Chiaramente, se si preme e si rilascia il pulsante senza muovere il cursore, questi due punti sono lo stesso. D'altra parte, se si muove il mouse mentre il pulsante viene tenuto premuto, questi due punti sono differenti. Per realizzare due figure differenti nel caso di pressione e rilascio, dobbiamo semplicemente definire sia mouseDown che mouseUp. La prima disegna il rettangolo, mentre le seconda disegna il cerchio. Il programma completo QuadratiCerchi.java è questo:

/*
  Quadrati dove viene premuto il pulsante,
  cerchi dove viene rilasciato.
*/

import java.awt.*;

public class QuadratiCerchi extends java.applet.Applet {

  public boolean mouseDown(Event e, int x, int y) {
    Graphics g=getGraphics();

    g.drawRect(x-5,y-5,10,10);

    return true;
  }

  public boolean mouseUp(Event e, int x, int y) {
    Graphics g=getGraphics();

    g.drawOval(x-5,y-5,10,10);

    return true;
  }
}

Un altro esempio: il programma DisegnaCancella.java disegna un quadrato nel punto in cui viene premuto il pulsante, e lo cancella quando il pulsante viene rilasciato.

/*
  Disegna un punto finche' il mouse e' premuto,
  poi lo cancella.
*/

import java.awt.*;

public class DisegnaCancella extends java.applet.Applet {

  public boolean mouseDown(Event e, int x, int y) {
    Graphics g=getGraphics();

    g.fillRect(x-3,y-3,7,7);

    return true;
  }

  public boolean mouseUp(Event e, int x, int y) {
    Graphics g=getGraphics();

    g.clearRect(x-3,y-3,7,7);

    return true;
  }
}

Va notato che non necessariamente i quadrati vengono cancellati. Infatti, se si preme il pulsante in un punto e si sposta il cursore tenendolo premuto, allora la coordinate (x,y) della funzione mouseUp sono diverse dal punto (x,y) della funzione mouseDown. Il programma che effettivamente cancella il quadrato disegnato deve memorizzare la sua posizione in una variabile globale, come viene fatto in DisegnaCancellaDue.java, che viene riportato qui sotto.

/*
  Disegna un punto finche' il mouse e' premuto,
  poi lo cancella.
*/

import java.awt.*;

public class DisegnaCancellaDue extends java.applet.Applet {

  int lx,ly;

  public boolean mouseDown(Event e, int x, int y) {
    Graphics g=getGraphics();

    lx=x;
    ly=y;

    g.fillRect(x-3,y-3,7,7);

    return true;
  }

  public boolean mouseUp(Event e, int x, int y) {
    Graphics g=getGraphics();

    g.clearRect(lx-3,ly-3,7,7);

    return true;
  }
}

Va notato che le variabili globali non si possono chiamare x e y. Infatti, se si facesse cosí non si potrebbero usare all'intero delle funzioni mouseUp e mouseDown, che hanno le loro variabili con gli stessi nomi.

Disegno di linee con il mouse

Il programma seguente è un esempio di uso della funzione mouseUp. Si vuole disegnare della linee nella finestra usando il mouse, in questo modo: l'utente preme il pulsante in un punto, e quello è il punto iniziale della linea. Tenendo premuto il pulsante, si sposta il cursore nel punto in cui si vuole che la linea finisca, e lí si rilascia il pulsante.

La funzione mouseDown viene usata per memorizzare il punto iniziale della linea. La funzione mouseUp può disegnare la linea usando la posizione in cui il mouse è stato premuto e quella in cui è stato rilasciato. Dal momento che la posizione iniziale viene memorizzata dalla funzione mouseDown e poi usata dalla funzione mouseUp, deve essere rappresentata usando variabili globali.

Usiamo quindi due variabili globali xi e yi per indicare la posizione in cui il pulsante è stato premuto. La funzione mouseDown si limita a memorizzare le coordinate in cui il pulsante è stato premuto (che è memorizzato nei suoi argomenti x e y) in queste variabili. La funzione mouseUp invece disegna la linea fra il punto iniziale (che è memorizzato in xi e yi) e il punto in cui il pulsante è stato sollevato, che è rappresentato dai suoi argomenti x e y. Il programma completo UpDown.java è riportato qui sotto.

/*
  Disegna linee fra i punti di pressione e
  rilasciamento del pulsante.
*/

import java.awt.*;

public class UpDown extends java.applet.Applet {
	/* punto iniziale della linea */
  int xi, yi;

	/* memorizza il punto iniziale della linea */
  public boolean mouseDown(Event e, int x, int y) {
    xi=x;
    yi=y;
   
    return true;
  }

	/* disegna la linea fra il punto iniziale e quello corrente */
  public boolean mouseUp(Event e, int x, int y) {
    Graphics g=getGraphics();

    g.drawLine(xi,yi,x,y);

    return true;
  }
}

Naturalmente, le linee disegnate vengono cancellate ogni volta che la finestra viene coperta o ridotta a icona. Per rendere le linee disegnate permanenti, occorre che siano disegnate anche dalla funzione paint, per cui è necessario usare quattro vettori per memorizzare le coordinate dei punti iniziali e finali di ogni linea.

Distanza fra due punti

Si scriva un applet che calcola la distanza fra due punti. I due punti vengono indicati con il mouse, in questo modo: il primo punto è quello in cui il mouse è stato premuto, il secondo è quello in cui il pulsante è stato rilasciato. Si tratta quindi di trovare la distanza fra il punto in cui il mouse viene premuto e quello in cui viene rilasciato.

Cominciamo con la funzione mouseUp. Questa funzione deve stampare la distanza fra il punto in cui è stato premuto il pulsante e quello in cui è stato sollevato. Questa funzione ha a disposizione le coordinate del punto di rilascio (nelle variabili x e y), ma non quelle del punto di click.

D'altra parte, le coordinate del punto di pressione sono note quando si esegue mouseDown. È quindi necessario fare in modo che mouseDown memorizzi queste coordinate in modo tale che mouseUp le possa usare. Usiamo quindi delle variabili globali, che si possono accedere sia da mouseDown che da mouseUp.

Riassumendo: mouseDown memorizza le coordinate del punto in cui si è premuto il pulsante in due variabili globali xpressione e ypressione; in mouseUp si calcola la distanza fra i punti (x,y), che è la coordinata del punto in cui il pulsante è stato lasciato, e (xpressione,ypressione). Il valore di questa differenza viene poi stampato.

Il programma finale Distanza.java contiene anche il disegno di una croce nel punto di pressione, croce che viene poi cancellata quando si lascia il pulsante del mouse.

/*
  Distanza fra due punti.
*/

import java.awt.*;

public class Distanza extends java.applet.Applet {
  int xpremuto, ypremuto;

  public boolean mouseDown(Event e, int x, int y) {
    Graphics g=getGraphics();

    g.drawLine(x-10,y,x+10,y);
    g.drawLine(x,y-10,x,y+10);

    xpremuto=x;
    ypremuto=y;

    return true;
  }

  public boolean mouseUp(Event e, int x, int y) {
    Graphics g=getGraphics();
    double d;

    g.clearRect(xpremuto-10,ypremuto-10,21,21);
    g.clearRect(0,0,200,20);

    d=Math.sqrt((double) (x-xpremuto)*(x-xpremuto) + (y-ypremuto)*(y-ypremuto));

    g.drawString(""+d,10,20);

    return true;
  }
}

Rilevazione trascinamento del mouse: mouseDrag

Spesso è utile poter rilevare i casi in cui l'utente ha premuto il pulsante del mouse e ha poi spostato il cursore tenendo premuto il pulsante. Una operazione di questo tipo si chiama trascinamento (drag). Molti programmi di disegno richedono l'uso di questa operazione.

La rilevazione del trascinamento avviene attraverso una funzione mouseDrag, simile alla funzione di rilevazione del click. In particolare, ha gli stessi argomenti:

  public boolean mouseDrag(Event e, int x, int y) {
    Graphics g=new Graphics();

    istruzioni

    return true;
  }
Questa funzione viene attivata quando viene rilevato un movimento del mouse mentre il pulsante è tenuto premuto. È anche importante notare che questa funzione non viene attivata in tutti i punti attraversati dal cursore. Si può dire che vale invece l'inverso: se la funzione viene attivata (ossia si eseguono le istruzioni al suo interno) allora vuol dire che il punto di coordinate x e y è stato attraversato mentre il pulsante è stato tenuto premuto.

Il seguente esempio PuntiDrag.java disegna dei punti nelle posizioni in cui il mouse viene premuto, come fa anche PuntiMouse.java. In più, disegna dei punti anche se il cursore viene spostato tenendo il pulsante premuto.

/*
  Disegna dei punti, sulla base del click del mouse
*/

import java.awt.*;

public class PuntiDrag extends java.applet.Applet {
  public boolean mouseDown(Event e, int x, int y) {
    Graphics g=getGraphics();

    g.fillRect(x-2,y-2,4,4);

    return true;
  }

  public boolean mouseDrag(Event e, int x, int y) {
    Graphics g=getGraphics();

    g.fillRect(x-2,y-2,4,4);

    return true;
  }
}

Un possibile miglioramento è quello di fare in modo che i punti non vengano cancellati ogni volta che la finestra diventa invisibile. È chiaramente sufficiente introdurre delle variabili globali per rappresentare i punti, e una funzione paint che li ridisegna ogni volta.

Disegno di rettangoli usando mouseDown, mouseDrag e mouseUp

Vediamo ora un programma che disegna dei rettangoli nella finestra. Per disegnare un rettangolo, l'utente deve premere il pulsante nel punto in alto a sinistra del rettangolo, e poi muovere il cursore tenendo premuto il pulsante. Il punto in cui il pulsante viene rilasciato è il punto in basso a destra del rettangolo.

Il metodo da usare è molto semplice: ci servono quattro vettori per rappresentare i dati di ciascuno dei rettangoli disegnati (per ogni rettangolo ci servono le coordinate del punto in alto a sinistra, la larghezza e l'altezza). Questi dati sono modificati dalle funzioni mouseDown e mouseDrag, e sono poi usati da paint per disegnare effettivamente i rettangoli. Quindi, questi vettori devono essere variabili globali. Un'altra variabile globale che ci serve è quella che contiene il numero di rettangoli che sono stati disegnati fino ad ora. Il programma inizia quindi cosí:

import java.awt.*;

public class RettangoliMouse extends java.applet.Applet {
  int xvett[] = new int[100];
  int yvett[] = new int[100];
  int wvett[] = new int[100];
  int hvett[] = new int[100];
  int nrettangoli=0;
La funzione mouseDown viene chiamata ogni volta che il pulsante viene premuto. A questo deve corrispondere la definizione del punto in alto a sinistra di un nuovo rettangolo. Quindi, la funzione mouseDown contiene le due istruzioni:

  public boolean mouseDown(Event e, int x, int y) {

    xvett[nrettangoli]=x;
    yvett[nrettangoli]=y;

    return true;
  }
La funzione mouseUp viene chiamata quando il pulsante viene rilasciato. Quindi, questa funzione deve definire la largezza e l'altezza del rettangolo. Inoltre, dato che il rettangolo è terminato, deve incrementare di uno il valore della variabile che conta il numero di rettangoli. A questo punto, si può disegnare di nuovo tutta la figura.

  public boolean mouseUp(Event e, int x, int y) {
    wvett[nrettangoli]=x-xvett[nrettangoli];
    hvett[nrettangoli]=y-yvett[nrettangoli];

    nrettangoli=nrettangoli+1;

    repaint();

    return true;
  }
Il programma RettangoliMouse.java effettua anche il tracciamento dei rettangoli durante il disegno.

/*
  Disegna dei rettangoli con il trascinamento del mouse.
*/

import java.awt.*;

public class RettangoliMouse extends java.applet.Applet {
  int xprec=0, yprec=0;

  int xvett[] = new int[100];
  int yvett[] = new int[100];
  int wvett[] = new int[100];
  int hvett[] = new int[100];
  int nrettangoli=0;


  public boolean mouseDown(Event e, int x, int y) {
    xvett[nrettangoli]=x;
    yvett[nrettangoli]=y;

    xprec=0;
    yprec=0;

    return true;
  }

  public boolean mouseUp(Event e, int x, int y) {
    wvett[nrettangoli]=x-xvett[nrettangoli];
    hvett[nrettangoli]=y-yvett[nrettangoli];

    nrettangoli=nrettangoli+1;

    repaint();

    return true;
  }

  public boolean mouseDrag(Event e, int x, int y) {
    Graphics g=getGraphics();

    g.setColor(getBackground());
    g.drawRect(xvett[nrettangoli],yvett[nrettangoli],
               xprec-xvett[nrettangoli],yprec-yvett[nrettangoli]);

    g.setColor(Color.black);
    g.drawRect(xvett[nrettangoli],yvett[nrettangoli],
               x-xvett[nrettangoli],y-yvett[nrettangoli]);

    xprec=x;
    yprec=y;

    return true;
  }

  public void paint(Graphics g) {
    int i;

    for(i=0; i<=nrettangoli-1; i++) 
      g.drawRect(xvett[i],yvett[i],wvett[i],hvett[i]);
  }
}

Battaglia Navale


Si usi la funzione Math.random() che ritorna un numero reale a caso compreso fra 0 e 1.

Questo programma usa la funzione start, in cui si decidono le posizioni dei sottomarini (questo va fatto in start e non in paint, altrimenti si ricomincia da capo ogni volta che si scopre la finestra.


BattagliaNavale.java

/*
  Battaglia navale (solo sottomarini).
*/

import java.awt.*;

public class BattagliaNavale extends java.applet.Applet {

	/* posizioni dei sottomarini */
  int xsott[]=new int[10];
  int ysott[]=new int[10];

	/* i colpi sparati */
  int xcolpi[]=new int[500];
  int ycolpi[]=new int[500];
  int ncolpi=0;

	/* inizio: si mettono i sottomarini in posizioni casuali */
  public void start() {
    int i;

    for(i=0; i<=xsott.length-1; i=i+1) {
      xsott[i]=Math.round((float) Math.random()*10);
      ysott[i]=Math.round((float) Math.random()*10);

      System.out.println(xsott[i]+" "+ysott[i]);
    }
  }

	/* mouseDown: si memorizza un nuovo colpo */
  public boolean mouseDown(Event e, int x, int y) {
    if( (x<=400) && (y<=400) ) {
      ncolpi=ncolpi+1;
      xcolpi[ncolpi-1]=x/40;
      ycolpi[ncolpi-1]=y/40;

      repaint();
    }

    return true;
  }

	/* paint: si stampa la griglia, e poi per ogni colpo
	   si mette x oppure o a seconda se c'e' un sottomarino */
  public void paint(Graphics g) {
    int x,y;
    int c,s;
    int colpito;

	/* la griglia */
    for(x=0; x<=400; x=x+40) {
      g.drawLine(x,0,x,400);
    }

    for(y=0; y<=400; y=y+40) {
      g.drawLine(0,y,400,y);
    }


	/* disegna i colpi */
    for(c=0; c<=ncolpi-1; c=c+1) {
      colpito=0;

      for(s=0; s<=xsott.length-1; s=s+1) {
        if( (xcolpi[c]==xsott[s]) && (ycolpi[c]==ysott[s]) ) {
          colpito=1;
        }
      }
      
      if( colpito==1 ) {
          g.drawLine(xcolpi[c]*40+5,ycolpi[c]*40+5,
                     xcolpi[c]*40+35,ycolpi[c]*40+35);
          g.drawLine(xcolpi[c]*40+5,ycolpi[c]*40+35,
                     xcolpi[c]*40+35,ycolpi[c]*40+5);
      }
      else {
          g.drawOval(xcolpi[c]*40+5,ycolpi[c]*40+5,30,30);
      }
    }
  }
}