![]() |
![]() |
![]() ![]() ![]() ![]() ![]() ![]() ![]() |
![]() | ||
![]() |
![]()
|
![]() |
![]() |
![]() 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'eventoLa 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
|
![]() |
![]() | |||||
![]() | |||||
|