![]() |
| |
Sommario Internet ID Indici di BETA Redazione Mailing list Installazione Mirror ufficiali Licenza Pubbl. Beta Cerca Stampa
|
Scriviamo un applicazione client/server JDBCJava: Lettura e scrittura di informazioni in un database JDBC compatibile.(Prima Parte)
Stefano Carfagna
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.
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 bordoIl 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.
Creazione di un menu personalizzatoSfruttando 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 :
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.
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 \ MouseMotionListenerOgni 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.
Assembliamo il tuttoTerminiamo 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.
- SRicerca : è un pannello per le ricerche.
- SAbout : visualizza informazioni sull'applet
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.
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
|
||||||||||||||||||||||||||||||||||||