BETA Programmazione
BETACopertinaSommarioInternet IDInformazioniBrowserGuida
BETA

Sommario
Internet ID
Indici di BETA
Redazione
Mailing list
Installazione
Mirror ufficiali
Licenza Pubbl. Beta
Cerca
Stampa





BETA sul Web
Beta.it:
Menu Settori e sezioni

Scriviamo un applicazione client/server JDBC

Java: Lettura e scrittura di informazioni in un database JDBC compatibile.

(Prima Parte)

Stefano Carfagna
Contributore, BETA

Iniziamo la serie di articoli con una breve descrizione delle problematiche che affronteremo. Il linguaggio Java permette la creazione di applicazioni che possono essere eseguite in un browser: le applet. Verranno create due applicazioni una sul lato client (l'applet) ed una sul lato server : la prima permetterà di visualizzare, editare ed inserire nuove informazioni; la seconda soddisferà delle richieste (lettura e scrittura) in arrivo un canale di comunicazione (socket) e aggiornerà un database SQL.

Figura 1
Figura 1

Procederemo così nella descrizione dei singoli elementi che verranno di volta in volta creati per presentare nell'epilogo dell'articolo l'unione delle parti.

Creazione di un pannello con bordo

Il primo componente necessario è un pannello che visualizzi un bordo sul contorno e si ridisegni utilizzando il "double buffering" per minimizzare l'effetto di sfarfallamento. Iniziamo riscrivendo il metodo "paint" del componente.


  /**
   * ridisegna il componente
   */
  public void paint(Graphics g) {
      // paint ereditato 
      super.paint(g);
     
      // disegna il rettangolo di bordo 
      drawRect(g);
  }
Il metodo "drawRect".

  /**
   * disegna un rettangolo di bordo
   */
  protected void drawRect(Graphics g) {       
    // prende le dimensioni
    Dimension d = getSize();    
    
    // disegna il bordo 
    g.setColor(Color.lightGray);
    g.drawRect(0, 0, d.width-1, d.height-1);
    g.drawRect(1, 1, d.width-3, d.height-3);

    g.setColor(Color.gray);
    g.drawRect(2, 2, d.width-5, d.height-5);
 
    g.setColor(Color.black);
    g.drawRect(3, 3, d.width-7, d.height-7);    
  }
Il double buffering necessita di una immagine temporanea dove disegnare l'area visibile del componente prima che essa vada sullo schermo.

  // immagine temporanea dell'area ridisegnabile
  // utile per il double buffering 
  Image offscreen;
L' "invalidate" viene invocato quando le dimensioni del componente sono variate e si ha la necessità di ricalcolare la grandezza dell'immagine temporanea.

  /**
   * il componente deve essere ridisegnato ed il layout 
   * ricalcolato.
   */
  public void invalidate() {
      // ...
      super.invalidate();
      // l'immagine temporanea deve essere ricalcolata
      offscreen = null;
  }
L' "update" è stato modificato per evitare la cancellazione dell'area ridisegnabile e per ridirigere il "paint" in un contesto grafico bufferizzato. L'aspetto del componente è aggiornato solo quando il disegno è terminato. Viene ora mostrata l'implementazione "logica" dell' "update" standard che provoca l'effetto di flickering.

  /**
   * Vecchio metodo update
   */
  public void update(Graphics g) {
      g.setColor(getBackground());
      g.fillRect(0, 0, getSize().width, getSize().height);
      g.setColor(getForeground());

      paint(g);
  }
La nuova versione.

  /**
   * riscrittura del metodo update :
   *  - evita di cancellare il background prima del ridisegno
   *  - disegna prima in una immagine di buffer e poi nel 
   *    contesto grafico del componente
   */
  public void update(Graphics g) {
      // crea l'immagine di buffer se non è impostata 
      if (offscreen == null) {
         offscreen = createImage(getSize().width, getSize().height);
      }

      // prende il contesto grafico dell'immagine di buffer
      Graphics og = offscreen.getGraphics();
      
      // cancella l'area disegnabile dell'immagine di buffer
      og.setColor(getBackground());
      og.fillRect(0, 0, getSize().width, getSize().height);
      og.setColor(getForeground());

      // imposta il clipping dell'area ridisegnabile
      og.setClip(0, 0, getSize().width, getSize().height);

      // chiama il paint 
      paint(og);
      
      // disegna l'immagine dell'area ridisegnabile nel contesto 
      // grafico associato al componente
      g.drawImage(offscreen, 0, 0, null);
      
      // libera le risorse usate per il contesto grafico 
      // dell'immagine
      og.dispose();      
  }
Il risultato ottenuto è il seguente.

Figura 2
Figura 2

Creazione di un menu personalizzato

Sfruttando ancora l'ereditarietà scriviamo un semplice menu ad albero che ci permetta di selezionare le opzioni della nostra applicazione lato client. Il nuovo componente accetterà come parametro nel suo costruttore un array di stringhe rappresentanti le voci del menù.


  // costruttore
  public SMenu(String[] items);
L'idea è quella di riscrivere il metodo paint in modo che esso possa disegnare ogni voce indentandola in funzione del numero di spazi che precedono il singolo item. Ad ogni spazio corrisponde un numero di pixel di offset.

  "Menu A"
  " Sub A1"
  " Sub A2"
  "  Sub A2-1"
  "Menu B"
  " Sub B1"
Lo schema utilizzato per il disegno, utile per ritrovare in base alla posizione del mouse l'item corrispondente, è il seguente :
Figura 3
Figura 3
Successivamente intercettando gli eventi del mouse sarà possibile risalire dalla posizione sullo schermo alla voce selezionata. Nel momento in cui l'utente esegue un click un evento di "selezione avventa" verrà notificato alla parte di codice che si occupa della gestione. Vediamo ora in particolare come si possa disegnare il menu a partire dall'array degli item.
Occorre un metodo che ritorni il numero di spazi iniziali di ogni stringa.

  /* conta gli spazio iniziali di una stringa
   */
  private int spaces(String item) {
    int space = 0;

    while (item.charAt(space) == ' ') {
      space++;
    }

    return space;
  }
Un metodo che disegni ogni item in base alla sua posizione ed il numero di spazi.

  /* disegna la stringa della singola
   * voce del menu
   */
  private void menuItem(Graphics g, String item, int i, int h, int y) {
    // trim della stringa
    String tmp = item.trim();

    // conta gli spazi dell'item
    int space = spaces(item);

    // offset del menu sulle x
    int offX = WOFF + XGAP * space;

    // offset del menu sulle y
    int offY = y + HOFF + (HGAP * 2 + h) * i;

    // se sto disegnando la prima voce del menù
    // provoca un effetto ombra per evidenziare
    if (space == 0) {
      // ombra
      g.setColor(Color.orange);
      // disegna item
      g.drawString(tmp, offX - 1, offY - 1);
    }

    // disegna la stringa descrizione
    g.setColor(Color.black);
    // disegna item
    g.drawString(tmp, offX, offY);
  }
Un metodo che disegni un rettangolo azzurro per indicare la voce selezionata o se l'utente sta eseguendo un click.

  /* disegna il rettangolo della singola
   * voce del menu correntemente evidenziata. Ne visualizza uno
   * più spesso in caso di pressione di un bottone del mouse.
   */
  private void menuRect(Graphics g, int i, int h) {
    // dimensioni
    final Dimension d = getSize();

    // offset del menu sulle x
    int offX = WOFF - HOFF / 2;

    // offset del menu sulle y
    int offY = HOFF + (HGAP * 2 + h) * i - HGAP;

    // dimension
    int dim = d.width - offX - 14 + HOFF / 2;

    // disegna la selezione
    if (i == hilightPos) {
      // pressed ?
      if (pressed) {
        // crea un effetto di evidenza del rettangolo
        // di selezione
        // bordo blue
        g.setColor(Color.blue);
        g.drawRect(offX - 1, offY - 1, dim + 2, HGAP * 2 + h + 2);
        // bordo grigio
        g.setColor(Color.gray);
        g.drawRect(offX - 2, offY - 2, dim + 4, HGAP * 2 + h + 4);
      }

      // arancione se premuto blue se selezionato
      g.setColor(pressed ? Color.orange : Color.blue);
      g.drawRect(offX, offY, dim, HGAP * 2 + h);
    }
  }
Il cuore della parte grafica è naturalmente il metodo paint che viene richiamato ogni qual volta lo stato del componente subisce un cambiamento; i tre compiti svolti dal paint sono i seguenti : - preparare le informazioni per il ridisegno ricavate dal metodo "getFont", - scorrere tutte le voci del menu per passarle ai metodi sopra descritti - disegnare un bordo intorno al menu

  /* disegna il menu
   */
  public void paint(Graphics g) {
    super.paint(g);

    // prende font metrics
    final FontMetrics fm = getFontMetrics(getFont());
    // prende l'altezza del font
    final int h = fm.getHeight();
    // calcola l'offset sulle y
    final int y = (fm.getAscent() + (h - (fm.getAscent() + \
    fm.getDescent())) / 2);

    // prende le dimensioni
    final Dimension d = getSize();

    /* disegna gli item
     * del menu
     */
    for (int i = 0; i < menu.length; i++) {
      // rettangolo di selezione
      menuRect(g, i, h);

      // disegna la stringa descrizione
      menuItem(g, menu[i],  i, h, y);
    }

    // disegna il bordo
    g.setColor(Color.lightGray);
    g.drawRect(0, 0, d.width-1, d.height-1);
    g.drawRect(1, 1, d.width-3, d.height-3);

    g.setColor(Color.gray);
    g.drawRect(2, 2, d.width-5, d.height-5);

    g.setColor(Color.black);
    g.drawRect(3, 3, d.width-7, d.height-7);
  }
Il nostro componente ora può ridisegnarsi ma non conosce ancora le modalità di interazione con l'utente; è ancora inanimato. Per renderlo attivo occorre intercettare gli eventi del mouse che arrivano ad esso grazie al sistema operativo. Java mette a disposizione, a tale scopo, una modalità di gestione denominata "delegation model" in cui ci sono delle sorgenti capaci di generare eventi e delle destinazione in grado di intercettarli.

Figura 4
Figura 4

Una sorgente è una qualsiasi classe che genera chiamate a metodi presenti in altre. La garanzia che il metodo di una classe in ascolto esita è data dalle interfacce. Nel nostro caso la sorgete è il menu stesso e la destinazione una classe al suo interno (inner class). Quest'ultima implementa i metodi che rappresentano l'interazione con il mouse :


  // mouse motion adapter
  private class MouseResponse extends MouseAdapter implements \
  MouseMotionListener
Ogni qual volta si verifica un evento il codice associato ai metodi di gestione cambierà lo stato del componente e ne ridisegnerà il suo aspetto con il "repaint". Il compito principale è quello di stabilire quale voce sia attualmente selezionata; con un semplice calcolo dalla posizione sullo schermo (in base alla 1898fg3) si risale alla posizione nell'array degli item. Ciò fatto si deve stabilire se la voce è selezionabile (non ha spazi che la precedono) ed aggiornare la variabile corrispondente con un valore > 0, altrimenti con -1 (nessuna selezione).

    // moved
    public void mouseMoved(MouseEvent e) {
      // prende font metrics
      final FontMetrics fm = getFontMetrics(getFont());
      // prende l'altezza del font
      final int h = fm.getHeight();

      // calcola la voce selezionata del menu
      int yPos = (e.getY() - HOFF) / (HGAP * 2 + h);

      // se la posizione è quella nelle voci del menu
      if (yPos < menu.length) {
        // prende l'item del menu selezionato
        String item = new String(menu[yPos]);

        // controlli che non sia il primo nodo
        if (item.charAt(0) != ' ') yPos = -1;

        // ridisegna il menu solo se
        // è cambiata la selezione
        if (yPos != hilightPos) {
          hilightPos = yPos;
          repaint();
        }
      }
    }
Il metodo "mousePressed" è utilizzato per capire quando l'utente ha premuto un bottone del mouse.

    // pressed
    public void mousePressed(MouseEvent e) {
      pressed = true;
      repaint();
    }
Il metodo "mouseReleased" per il rilascio. Questo metodo deve anche notificare ad ascoltatori che un'azione di selezione si è verificata nel menù. La classe con la funzione di ascoltatore è, a sua volta, un generatore di eventi. Avremo modo più avanti in questo articolo, di completare la descrizione delle modalità di generazione di eventi.

    // released
    public void mouseReleased(MouseEvent e) {
      if (hilightPos > 0) {
        // creazione dell'evento che indica la selezione di un item
      ActionEvent ae = new ActionEvent(SMenu.this, hilightPos, "SELECTED");
        // invio dell'evento agli ascoltatori
        fireActionEvent(ae);
      }

      pressed = false;
      repaint();
    }
Il metodo "mouseExited", chiamato quando l'utente sta muovendosi fuori dal componente, annulla ogni selezione nel menu.

    // exited
    public void mouseExited(MouseEvent e) {
      hilightPos = -1;
      repaint();
    }
Arrivati a questo punto il nostro componente può interagire con l'utente; ma non è ancora capace di inviare eventi. Esso può uscire dal suo isolamento diventando una sorgente e notificando ad ascoltatori che la selezione di una voce è avvenuta. A tale scopo utilizziamo la classe ActionEvent dell'AWT come descrittore del nostro evento :

    public ActionEvent(Object source, int id, String command)

      source  : l'oggetto che ha generato l'evento 
      id      : id per identificare l'evento
      command : descrizione dell'evento
La sorgente deve conoscere quali sono gli ascoltatori interessati. Essi saranno registrati in un vettore.

  /* lista degli ascoltatori 
   * per gli eventi generati dal menu 
   */
  private Vector actionListeners = new Vector();
Occorrono dei metodi per la registrazione e la cancellazione dall'elenco. Essendo java multi-thread è stato necessario evitare l'accesso contemporaneo ai metodi di inserimento e rimozione di un ascoltatore (parola chiave "syncronized"). Se più thread accedono contemporaneamente a tali funzioni si potrebbe verificare, per esempio, che un thread voglia cancellare un ascoltatore non ancora inserito. Per una descrizione più dettagliata della gestione dei "monitor" in Java si rimanda alla documentazione forniata dalla Sun.

  /**
   * aggiunge un ascoltatore alla lista.
   * la sincronizzazione evita che durante la notifica di un evento
   * ci sia la variazione dell'elenco degli ascoltatori
   */
  public synchronized void addActionListener(ActionListener l) {
    actionListeners.addElement(l);
  }

  /**
   * rimuove un ascoltatore dalla lista.
   * la sincronizzazione evita che durante la notifica di un evento
   * ci sia la variazione dell'elenco degli ascoltatori
   */
  public synchronized void removeActionListener(ActionListener l) {
    actionListeners.removeElement(l);
  }
Occorre un metodo per l'invio degli eventi. Per evitare che la lista degli ascoltatori sia modificata dall'esterno, di essa, è stata fatta una clonazione (copia area dati) sincronizzata (accesso esclusivo). L'invio degli eventi avviene scorrendo gli elementi del vettore di copia, sicuramente non modificabile da thread esterni. Per una descrizione più dettagliata della gestione dei "monitor" in Java si rimanda alla documentazione forniata dalla Sun.

  /**
   * invia dell' evento agli ascoltatori.
   */
  protected void fireActionEvent(ActionEvent event) {
    // vettore copia degli ascoltatori
    Vector targets;
		
    // copia del vettore di ascoltatori sincronizzata 
    // evita che altri thread modifichino la lista degli ascoltatori
    // durante la creazione del clone
    synchronized (this) {
      targets = (Vector) actionListeners.clone(); 
    }
	
    // scorre tutti gli ascoltatori ed invia l'evento;
    // lavorando sulla copia della lista non c'è pericolo che un 
    // thread modifichi il vettore durante la notifica
    for (Enumeration e = targets.elements(); e.hasMoreElements() ;) {
      // prende ascoltatore
      final ActionListener l = (ActionListener) e.nextElement(); 
      
      // invia l'evento, chiamando il metodo di gestione 
      l.actionPerformed(event);
    }
  }  
Abbiamo terminato la creazione del nostro nuovo menu; Il risultato ottenuto è il seguente.

Figura 5
Figura 5

Assembliamo il tutto

Terminiamo questo primo articolo presentando l'applet che utilizza i componenti creati. Sulla destra appare il menu; gli items sono i seguenti.

  
  // items
  String[] menuItems = {
    "Archivio",
    " Anagrafica",  
    " Ricerca",  
    "Altro",
    " About"
  };
Sulla sinistra c'e la zona dei pannelli corrispondenti alle tre opzioni selezionabili :

- SAnagrafica : è un pannello per l'inserimento e la modifica dei dati.

Figura 6
Figura 6

- SRicerca : è un pannello per le ricerche.

Figura 7
Figura 7

- SAbout : visualizza informazioni sull'applet

Figura 8
Figura 8

I sorgenti di queste tre classi si possono trovare allegati alla fine dell'articolo. Essendo molto semplici non presenteranno alcuna difficoltà per il lettore.


  // pannello che contiene gli altri 
  private CardLayout  card       = new CardLayout(5, 5);
  private Panel       panel      = new Panel(card); 
    
  // pannelli delle opzioni del menù
  private SAnagrafica anagrafica = new SAnagrafica();
  private SRicerca    ricerca    = new SRicerca();
  private SAbout      about      = new SAbout();
L'inizializzazione dispone i componenti ed imposta il card layout per visualizzare pannelli multipli.

  /**
   * Inizializza applet 
   */
  public void init() {        
    super.init();
        
    // crea menu nuovo menu 
    SMenu menu  = new SMenu(menuItems);
    // registra la classe che ascolterà gli eventi 
    // generati dal menu
    menu.addActionListener(new MenuActionAdapter());
    
    // crea un pannello con card layout
    panel = new Panel(card);
    panel.setBackground(Color.lightGray);
    
    // aggiunge i componenti al card layout 
    panel.add("1", anagrafica);
    panel.add("2", ricerca   );
    panel.add("3", about     );
                
    // imposta il border layout
    setLayout(new BorderLayout());

    // menu sulla sinistra
    add(BorderLayout.WEST,   menu );
    
    // pannelli al centro 
    add(BorderLayout.CENTER, panel); 
  }    
La classe che riceve gli eventi non deve fare altro che mostrare il pannello corrispondente all'opzione desiderata.

  // riceve e gestisce gli eventi generati dal componente
  // menu al momento che viene selezionato un item
  public class MenuActionAdapter implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      switch (e.getID()) {          
        case 1 :         
          card.show(panel, "1");
          break;
          
        case 2 : 
          card.show(panel, "2");
          break;

        case 4 : 
          card.show(panel, "3");
          break;
      }
    }
  }

Conclusioni

Siamo cosi giunti alla fine di questa primo articolo. Abbiamo visto come si possono creare dei componenti ereditando da quelli base di Java ed abbiamo scritto la prima parte dell'applicazione lato client.
Se volete, potete fare il download dei sorgenti Java presenti in questo Numero ed evitare di riscriverli.
A seguito delle prove eseguite su Netscape 4.05 e su Explorer consigliamo all'utente che voglia provare l'applet l'utilizzo di "appletviewer.exe" fornito tra gli strumenti di base del JDK Sun.

Clicca qui per maggiori informazioni


Stefano Carfagna è Programmatore Java e contributore di BETA da questo Numero - È raggiungibile su Internet tramite l'indirizzo e-mail scxscx@hotmail.com.

Copyright © 1998 Stefano Carfagna, tutti i diritti sono riservati. Questo Articolo di BETA, insieme alla Rivista, è distribuibile secondo i termini e le condizioni della Licenza Pubblica Beta, come specificato nel file LPB.


BETA Rivista | Copertina | Sommario | InternetID | Informazioni | Browser
BETA Sul Web: http://www.beta.it

Copertina Sommario Internet ID Informazioni Browser
Home Page BETA Rivista Indice Articoli Beta Editore, articoli e pubblicazioni Beta2, contributi esterni BETA Logo, siti premiati Premio BETA Logo Licenza Article/Document Definition Format Promozione Pubblicita' Mirroring Mirror Ufficiali BETA Navigatore NavSearch Novità BETA Stampa/Press Releases BETA Settori online Eventi Public books (libreria) Settori riservati Redazione