Paola Magillo, Univestita' di Genova, Corso di Interfacce Utente per Informatica, a.a. 2004-2005.

LABORATORIO - INTERFACCE GRAFICHE IN JAVA

Contesto generale

L'obiettivo e' disegnare in Java il labirinto (ved. materiale alla tappa 1 del progetto).

In pratica dovete definire un vostro componente Java personalizzato che "si disegna" disegnando su se stesso il labirinto. Tale componente sara' poi inserito all'interno della gerarchia di contenimento della vostra interfaccia, e quindi visualizzato.

Ci sono vari punti chiave da risolvere:

  1. come scegliere la classe di componente da cui ereditare per definire la nuova classe
  2. come accedere al labirinto da dentro alla classe nuova che definiamo
  3. come disegnare il labirinto
  4. se volete usare immagini

Non occorre consegnare nulla, questo e' solo un esercizio che vi sara' utile per il progetto finale del corso.

SUGGERIMENTO:
E' molto utile programmare tenendo aperta a fianco una finestra sul sito con il manuale on-line delle classi Java: http://java.sun.com/j2se/1.4.2/docs/api/index.html

1 - Come scegliere la classe di componente da cui ereditare

In genere non e' necessario che l'area destinata a contenere il disegno del labirinto abbia un comportamento particolare. Quindi possiamo definire la nostra nuova classe semplicemente come sottoclasse di Panel (in AWT) o JPanel (in Swing).

Nel seguito chiamo NOME_DELLA_CLASSE questa nostra sotto-classe di pannello.

Un pannello in Java non contiene nulla, fino a che non aggiungiamo componenti al suo interno. L'uso che facciamo ora del pannello e' anomalo nel senso che non lo usiamo per contenere altri componenti ma per disegnare sul suo sfondo. Quindi lo usiamo vuoto.
Siccome Java assegna dimensione preferita nulla a un pannello vuoto, e' opportuno ridefinire, nella nostra sotto-classe NOME_DELLA_CLASSE, la dimensione preferita.
Questa si ridefinisce in Swing chiamando la funzione setPreferredSize (ved. manuale on-line), e in AWT reimplementando la funzione getPreferredSize (ved. manuale on-line) in quanto AWT non fornisce la funzione setPreferredSize.

2 - Come vedere il labirinto da dentro alla classe nuova

La nostra classe NOME_DELLA_CLASSE deve poter accedere al labirinto, lo stesso oggetto labirinto che e' in uso da parte di tutta l'interfaccia.

Questo oggetto labirinto compare come variabile in qualche altra classe della vostra interfaccia, in genere nella classe che realizza la finestra principale.

La nostra classe avra' anch'essa come variabile un oggetto di classe Labirinto.
Tale variabile dovra' contenere lo stesso oggetto contenuto nella variabile di classe Labirinto che sta all'interno della finestra principale.
Il costruttore della nostra classe NOME_DELLA_CLASSE prendera' in argomento un oggetto di classe Labirinto e lo assegnera' alla sua variabile interna.
Tale costruttore e' chiamato dal costruttore della classe che corrisponde alla finestra principale dell'interfaccia passando come argomento la variabile di classe Labirinto che sta all'interno della finestra principale.
In questo modo la finestra principale e il pannello condividono lo stesso labirinto.

In pratica, nella classe principale:

  Labirinto global_lab;
  NOME_DELLA_CLASSE lab_pan;
Nella classe NOME_DELLA_CLASSE da noi definita per disegnare il labirinto:
  Labirinto local_lab;
Il costruttore della classe NOME_DELLA_CLASSE:
  NOME_DELLA_CLASSE(Labirinto l,...)
  {
    local_lab = l;
    ...
  }
Nel costruttore dela classe principale:
  global_lab = new Labirinto(...);
  ...
  lab_pan = new NOME_DELLA_CLASSE(global_lab...);

Nota: la chiamata al costruttore new Labirinto(...) puo' sollevare eccezione se le dimensioni passate non sono almeno 3x3. Pertanto occorre inserire la suddetta chiamata in un blocco del tipo:

  try
  {  global_lab = new Labirinto(...);
     ...altre istruzioni dell'esecuzione normale...
  }
  catch (Exception ex)
  {  ...istruzioni per la gestione dell'errore...
     (se siamo sicuri che l'errore non ci sara' mai perche' il controllo
     sui parametri e' fatto a priori, qui possiamo non mettere nulla)
  }
oppure dichiarare che la funzione chiamante puo' sollevare a sua volta eccezione aggiungendo dopo la sua intestazione "throws Exception", per esempio:
  void miaFunzione() throws Exception
  { ....
  }

3 - Come disegnare il labirinto

Scandire la griglia di celle che compongono il labirinto.
Per ogni cella farsi dire di che tipo sono le quattro pareti che la delimitano.
Disegnare queste pareti, con grafica diversa a seconda del tipo di parete, nel posto giusto all'interno del pannello.
Disegnare poi Teseo e il minotauro alla loro posizione e con il loro orientamento.

Per scandire la griglia basta fare due cicli "for" annidati, uno lungo le x e uno lungo le y. Se lab e' la variabile che contiene il labirinto, le x variano tra 0 e lab.dimensioneX()-1, le y variano tra 0 e lab.dimensioneY()-1.
Il tipo di parete di una cella (x,y) si legge con la funzione lab.tipoParete(x,y,d) dove d e' un punto cardinale (Labirinto.NORD, Labirinto.SUD, Labirinto.EST, Labirinto.OVEST).
La posizione e la direzione di Teseo e del minotauro si ottengono con lab.xTeseo(), lab.yTeseo(), lab.xMinotauro(), lab.yMinotauro(), lab.dirTeseo() e lab.dirMinotauro().

In realta' non occorre disegnate tutte e quattro le pareti di una cella, ma solo le pareti non condivise con celle gia' disegnate.
Per esempio posso disegnare solo le pareti NORD e EST per ciascuna cella, tranne che per le celle sul bordo SUD e sul bordo OVEST della griglia. Queste infatti, non avendo celle confinanti sul lato SUD e/o OVEST, devono disegnare anche le pareti che hanno da questi lati.

4 - Se volete usare immagini

Non si richiede di usare immagini. Tuttavia so che alcuni di voi hanno intenzione di usarle.

Java permette di disegnare in un componente un'immagine caricata da file. I formati accettati sono quelli standard per il web (come .gif, .jpg).
Il seguente programma EsempioImmagine.java mostra come si carica un'immagine da file e come la si disegna in un pannello.