GUI e MVC
Programmazione in Rete e Laboratorio
Matteo Baldoni Dipartimento di Informatica Universita` degli Studi di Torino C.so Svizzera, 185 I-10149 Torino baldoni@di.unito.it
http://www.di.unito.it/~baldoni/didattica
2
Gli oggetti prima di tutto: GUI, Event-driven programming e l’architettura MVC
3
Graphical User Interface
■ Un programma che fa uso di di strumenti grafici come bottoni, menu`, disegni, finestre, ecc. per facilitare le operazioni di input e visualizzazione dell’output
■ Un GUI per il contatore: una finestra che permetta di controllare l’invio dei messaggi di incremento, decremento, inizializzazione di un contatore, nonche` la visualizzazione del suo valore corrente
public class Counter { public Counter() {
[…]
} […]
public void init(int val){
c = val;
}
public void incr(){
c++;
}
public void decr(){
c--;
}
public int getVal(){
return c;
} […]
private int c;
private String nomeContatore;
}
4
Contatore GUI 0
■ Desideriamo una interfaccia grafica per un contatore (descritto nelle lezioni precedenti) che contenga le seguenti funzionalita`:
◆un display per il valore corrente
◆tre bottoni per le operazioni di incr(), decr() e init(0)
◆un bottone per abbandonare l’interfaccia
decrementa il contatore di 1
incrementa il contatore di 1
chiude la finestra visualizza il valore corrente
inizializza il contatore a zero
chiude la finestra
5
L’architettura
Model View Controller
Un programma si compone di
■ Modello (Model): modella e calcola il problema che desideriamo risolvere
■ Vista (View): rappresenta una
“fotografia” dello stato interno del modello spesso per facilitarne la sua lettura/interpretazione all’utente umano
■ Controllore (Controller):
controlla il flusso di dati nel programma, dalla vista al modello e quindi nuovamenta alla vista
http://www.cis.ksu.edu/~schmidt/CIS200
■ Ha origine negli applicativi sviluppati in Smalltalk
■ E` stato utilizzato in Java per lo sviluppo delle componenti AWT/Swing
6
L’architettura
Model View Controller
■ L’utente agisce sulla vista di un programma agendo su una delle sue componenti di controllo (es. bottone)
■ Il controllore e` avvertito di tale evento ed esamina la vista per rilevarne le informazioni aggiuntive
■ Il controllore invia tali informazioni al modello che effettua la computazione richiesta e aggiorna il proprio stato interno
■ Il controllo (o il modello)
http://www.cis.ksu.edu/~schmidt/CIS200
richiede alla vista di visualizza-re il risultato della computazione
■ La vista interroga il modello sul suo nuovo stato interno e visulizza l’informazione all’utente
7
Architettura MVC: vantaggi
■ Le classi che formano l’applicativo possono essere piu`
facilmente riutilizzate
■ L’applicativo e` organizzato in parti piu` semplici e comprensibili (ogni parte ha le sue specifiche finalita`)
■ La modifica di una parte non coinvolge e non interferisce con le altre parti (maggiore flessibilita` nella manutenzione del software)
8
MVC: sequenza dei messaggi
① viene premuto il bottone
“Decrementa”
② l’evento e’ ascoltato dal controller
③ il controller invia il messaggio di decr() al modello
④ il controller invia il messaggio di updateView() alla vista
⑤ la vista richiede i dati al modello per aggiornarsi (getVal())
controller model view
model.decr() view.updateView()
model.getVal()
event
①
②
③
④
⑤ 0 -1
actionPerformed(…) Action Listener
9
Event-Driven Programming
■ E` alla base della programmazione delle GUI
■ E` il nuovo tipo di input che deve essere trattato nella programmazione delle GUI (pressione di bottoni, mouse on/off, ecc.)
■ L’utente genera tramite la GUI una serie di eventi a cui il controllore deve prontamente reagire in maniera opportuna
■ Handling events: “processare” gli eventi
■ Il controllore che processa gli eventi e` chiamato event handler o event listener
■ le informazioni sull’evento in Java sono memorizzate in opportuni oggetti (EventObject)
10
Delegation Event Model
■ Gli eventi messaggi passati dall’oggetto sorgente ad uno o piu` oggetti ascoltatori
■ Quando un evento e` passato causa l’invocazione di un metodo dell’oggetto ascoltatore
■ Gli eventi sono oggetti contenenti le informazioni relative al particolare evento che li ha determinati
Event Source Event Object Event Listener
Listener Registration
11
Event-Driven Programming
■ La computazione e` guidata completamente dalla serie di eventi generati dall’utente
■ Il programma processa gli eventi come input e aggiorna il proprio modello interno e lo visualizza tramite la vista
■ Il controllo ha il compito di gestire il flusso di eventi e dati dalla vista al modello e quindi nuovamente verso la vista
■ Piu` controllori, viste e modelli possono coesistere a formare un programma
■ Gli eventi devono essere generabili in maniera coerente da parte dell’utente (disabilita/abilita)
12
Event-Driven Programming
controller (e`
registrato come ActionListener di Decrementa)
model view
model.decr() view.updateView()
model.getVal()
ActionEvent event
①
②
③
④
⑤ 0 -1
actionPerformed(event) Action Listener
① OS intercetta l’evento
“click di un bottone” e lo comunica all’AWT/Swing
② AWT/Swing determina la sorgente dell’evento, crea un ActionEvent e lo invia all’incaricato ActionListener
③ la procedura actionPerformed (event) del controllore e`
eseguita
④ il controllo invia gli opportuni messaggi al modello e alla vista
⑤ la vista si aggiorna interrogando il modello
④
13
Event-Driven Programming
■ Ogni componente grafico (per esempio, un bottone) mantiene al suo interno una lista di listener objects (oggetti in ascolto)
■ Un listener object ob è aggiunto alla lista di un oggetto b tramite il messaggio b.addActionListener(ob)
■ In generale, un componente grafico può avere molti listener objects e un listener object può “ascoltare” più componenti
■ Quando un evento occorre, la lista viene scandita e a ogni listener object viene inviato il messaggio
actionPerformed
14
AWT/Swing
■ Componenti (component): oggetti che possono avere una posizione e una dimensione nello schermo e nei quali possono occorrere eventi
■ Contenitori (container): componenti che possono contenere al loro interno altre componenti come, ad esempio, i pannelli (panel)
15
AWT/Swing
■ Finestre (windows): contenitori che possono essere visualizzati direttamente sullo schermo
■ Frame: finestre con titolo e menu visualizzate
permanentemente sullo schermo durante l’esecuzione di un programma
■ Dialog: finestre visualizzate temporaneamente sullo schermo durante l’esecuzione di un programma (es.
visualizzano messaggi di errore, input file, ecc)
16
Contatore GUI 0: view
JPanel BorderLayout
CENTER SOUTH JPanel FlowLayout
JPanel FlowLayout JButton JButton
JButton JLabel
valore contatore: ...
Incrementa Decrementa Reset
Contenitori Struttura del
contenitore JPanel Componenti (in realta` sono
a loro volta contenitori, etichette, icon, …)
Componente
17
■ Creazione del contenitore JPanel della vista
■ Uso del costruttore super
■ Layout scelto:
BorderLayout
Contatore GUI 0: view
public class CounterView extends JPanel { public CounterView(Counter model){
super(new BorderLayout());
// alternativa: setLayout(new BorderLayout());
[…]
}
CENTER
SOUTH NORTH
WEST EAST
18
Contatore GUI 0: view
■ panelCenter: pannello da aggiungere al centro del BorderLayout del pannello principale
■ Layout scelto: FlowLayout
valore contatore: ...
[…]
label = new JLabel(“Valore contatore: ");
JPanel panelCenter = new JPanel(new FlowLayout());
panelCenter.add(label);
add(panelCenter, BorderLayout.CENTER);
[…]
panelCenter label
19
Contatore GUI 0: view
■ panelSouth: pannello a Sud nel pannello principale della vista
■ Layout scelto: FlowLayout
valore contatore: ...
Incrementa Decrementa Reset
[…]
JPanel panelSouth = new JPanel(new FlowLayout());
JButton bottoneDecr = new JButton("Decrementa");
panelSouth.add(bottoneDecr);
JButton bottoneReset = new JButton("Reset");
panelSouth.add(bottoneReset);
JButton bottoneIncr = new JButton("Incrementa");
panelSouth.add(bottoneIncr);
add(panelSouth, BorderLayout.SOUTH);
[…]
panelSouth
20
Contatore GUI 0: view
■ Definizione del metodo updateView() per l’aggiornamento della vista
■ Uso del modello (contatore) per reperire le informazioni necessarie per l’aggiornamento della vista
public void updateView(){
label.setText("Valore Contatore: " + contatore.getVal());
}
valore contatore: ...
Incrementa Decrementa Reset
label
21
Contatore GUI 0: controller
■ Tratta gli oggetti di tipo ActionEvent creati dall’AWT/Swing contenenti tutte le informazioni sull’evento occorso nell’interfaccia (vista)
■ implementazione di ActionListener e definizione del metodo actionPerformed(ActionEvent)
public class CounterControl implements ActionListener { private Counter contatore;
private CounterView contatoreVista;
public CounterControl(Counter cont, CounterView contVista){
contatore = cont;
contatoreVista = contVista;
}
public void actionPerformed(ActionEvent e){
JButton source = (JButton)e.getSource(); // notare il cast!
if (source.getText().equals("Decrementa")) contatore.decr();
else if (source.getText().equals("Incrementa")) contatore.incr();
else contatore.init(0);
contatoreVista.updateView();
} }
22
Contatore GUI 0:
aggangio del controller
public class CounterView extends JPanel { public CounterView(Counter model){
[…]
contatore = model;
[…]
controlloCounter = new CounterControl(contatore, this);
[…]
JButton bottoneDecr = new JButton("Decrementa");
bottoneDecr.addActionListener(controlloCounter);
[…]
JButton bottoneReset = new JButton("Reset");
bottoneReset.addActionListener(controlloCounter);
[…]
JButton bottoneIncr = new JButton("Incrementa");
bottoneIncr.addActionListener(controlloCounter);
[…]
updateView();
}
creazione del controllore
aggancio
23
Contatore GUI 0: MVC
① Dal main si crea il modello …
② … e la vista
③ la vista crea il controllore (listener bottoni) e lo aggancia ai bottoni
• la vista riceve il modello tra i suoi parametri
• il controllore riceve tra i suoi parametri la vista e il modello
model view
①
②
③
0
actionPerformed(event) Action Listener
controller main
crea crea
crea
invia messaggi
invia messaggi
eventi
24
Contatore GUI 0: overview
■ Diagramma delle classi per il contatore GUI 0
■ Introduzione di una interfaccia per la vista
■ ContatoreFrame contiene il main e quindi crea la vista e il modello
■ ExitButton e ExitFrame controllano l’uscita dal programma principale
25
Contatore GUI 0: frame
Exit
Container (pannello del contenuto)
BorderLayout CENTER SOUTH
JButton
windowClosing(…) setTitle(…)
Contatore GUI Finestra: JFrame
Struttura del Container Componente
Contenitore all’
interno del JFrame
26
Contatore GUI 0: frame
valore contatore: ...
Incrementa Decrementa Reset
Exit
Container (pannello del contenuto)
BorderLayout CENTER
SOUTH Contatore GUI 1: view Contatore GUI
27
Contatore GUI 0: frame
public class ContatoreFrame extends JFrame { public ContatoreFrame(){
contatoreModello = new Counter(0);
contatoreVista = new CounterView(contatoreModello);
Container cp = getContentPane();
cp.setLayout(new BorderLayout());
cp.add(contatoreVista, BorderLayout.CENTER);
cp.add(new ExitButton(), BorderLayout.SOUTH);
addWindowListener(new ExitFrame());
setTitle("Contatore GUI");
setSize(300, 140);
setVisible(true);
}
public static void main(String[] args) { ContatoreFrame frame = new ContatoreFrame();
}
private Counter contatoreModello;
private CounterView contatoreVista;
}
per la chiusura sull “X” della finestra
il bottone di Exit
il main e` tutto qua!!
28
Contatore GUI 0: frame
class ExitFrame extends WindowAdapter { public void windowClosing(WindowEvent e) {
System.exit(0);
} }
class ExitButton extends JButton implements ActionListener { public ExitButton () {
super("Exit");
addActionListener(this);
}
public void actionPerformed(ActionEvent e) { System.exit(0);
} }
■ Classi per la chiusura dell’applicativo mediante la “X” sulla finestra e un bottone “Exit”
■ Vanno bene per molti applicativi diversi dal contatore GUI
29
■ Si desidera rendere indipendente il controllore dalla vista
■ L’idea è quella che il controllore faccia riferimento ad una interfaccia anziché direttamente la vista
Contatore GUI 1: l’indipendenza dalla vista
public class CounterView extends JPanel
implements CounterInterfaceView { […]
public updateView(){
[…]
} […]
}
public interface CounterInterfaceView { void updateView();
}
30
Contatore GUI 1: controller
■ Il controllore non fa più riferimento ad un oggetto di tipo CounterView ma all’interfaccia di tipo CounterInterfaceView
■ Tramite il binding dinamico si risolverà il riferimento al metodo updateView()
public class CounterControl implements ActionListener { private Counter contatore;
private CounterInterfaceView contatoreVista;
public CounterControl(Counter cont, CounterInterfaceView contVista){
contatore = cont;
contatoreVista = contVista;
}
public void actionPerformed(ActionEvent e){
JButton source = (JButton)e.getSource(); // notare il cast!
if (source.getText().equals("Decrementa")) contatore.decr();
else if (source.getText().equals("Incrementa")) contatore.incr();
else contatore.init(0);
contatoreVista.updateView();
} }
31
Contatore GUI 1: overview
■ Diagramma delle classi per il contatore GUI 1
■ Introduzione di una interfaccia per la vista
32
Contatore GUI 1( bis): l’indipendenza dalla vista
■ È estremamente semplice cambiare la vista CounterView con una nuova vista CounterViewBis, che allinea verticalmente i vari bottoni, senza toccare il codice della classe CounterControl
33
Contatore GUI 2
■ Variante: il controllo contiene i bottoni
■ I bottoni sono il controllo
■ E` piu` facile determinare la sorgente
34
Contatore GUI 2: controller
public class CounterControl extends JPanel
implements ActionListener { […]
private JButton decrButton;
[…]
public CounterControl(Counter cont, CounterInterfaceView contVista){
[…]
decrButton = new JButton("Decrementa");
add(decrButton);
decrButton.addActionListener(this);
[…]
}
public void actionPerformed(ActionEvent e){
Object source = e.getSource();
if (source == decrButton) contatore.decr();
else if (source == incrButton) contatore.incr();
else contatore.init(0);
contatoreVista.updateView();
} }
posso determinare piu` facilemente la sorgente essendo questa interna alla classe stessa
35
Inside Contatore GUI 1
Sorgente:
contiene i metodi per registrare e deregistrare gli ascoltatore e il per inviare l’evento oggetto a tutti gli ascoltatori
Interfaccia ascoltatore Ascoltatore:
implementa il metodo specificato nell’interfaccia
36
Inside Contatore GUI 1
import java.util.*;
public class JButton {
private ActionListener[] arrayOfActionListener;
private Vector listOfActionListener = new Vector();
public synchronized void addActionListener(ActionListener al) { listOfActionListener.add(l);
}
public synchronized void removeActionListener(ActionListener al) { listOfActionListener.remove(l);
}
protected void notifyAction(Event e) { ActionEvent ae = new ActionEvent(this, e) synchronized (this) {
arrayOfActionListener = listOfActionListener.toArray();
}
for (int i=0; i<arrayOfActionListener.length; i++) { arrayOfActionListener[i].actionPerformed(ae);
} }
} Nota: per binding dinamico verra` eseguira il metodo actionPerformed definito in CounterControl
37
Inside Contatore GUI 1
public interface ActionListener extends java.util.EventListener { public void actionPerformed(ActionEvent e);
}
public class CounterControl implements ActionListener { […]
public void actionPerformed(ActionEvent e){
JButton source = (JButton)e.getSource();
if (source.getText().equals("Decrementa")) contatore.decr();
else if (source.getText().equals("Incrementa")) contatore.incr();
else
contatore.init(0);
contatoreVista.updateView();
} }
38
Contatore GUI 3
■ Modifichiamo l’applicativo precedente in modo da poter inserire il valore iniziale del contatore
■ E` importante controllare il valore immesso, cioe` verificare se questo e` un numero intero e segnalare l’eventuale errore
decrementa il contatore di 1
incrementa il contatore di 1 chiude la
finestra visualizza il
valore corrente o l’eventuale errore
inizializza il contatore con il valore immesso
chiude la finestra input del valore iniziale del contatore
39
Contatore GUI 3: view
JPanel BorderLayout
NORTH CENTER SOUTH JPanel FlowLayout
JButton JButton JButton
valore contatore: ...
Incrementa Decrementa Inizializza
JPanel FlowLayout
JLabel
JLabel valore iniziale:
JTextField
JPanel FlowLayout
0
40
Contatore GUI 3: frame
Exit Contatore GUI
valore contatore: ...
Incrementa Decrementa Inizializza
valore iniziale: 0
Container (pannello del contenuto)
BorderLayout CENTER
SOUTH
JButton windowClosing(…) setTitle(…)
Contatore GUI 3: view
41
Contatore GUI 3: controller
public void actionPerformed(ActionEvent e){
Object source = e.getSource();
if (source == initButton) { try {
int input = Integer.parseInt((contatoreVista.getInput()).trim());
contatore.init(input);
contatoreVista.setAnswer();
} catch(RuntimeException err) {
contatoreVista.setError(err.getMessage());
} } else {
if (source == incrButton) contatore.incr();
else contatore.decr();
contatoreVista.setAnswer();
}
contatoreVista.updateView();
}
lettura del valore in input nel campo JTextField tramite interrogazione della vista
42
La Serie dei Contatori
■ Contatore GUI 4: in un frame due contatori (due modelli) con rispettivi viste (due viste) e controllori (due controllori)
■ Contatori GUI 5: si puo` facilmente cambiare vista senza cambiare ne` l’implementazione del modello ne` quello del controllore
■ Contatore GUI 6: una vista un po’ piu` complicata, una etichetta (JLabel) e un pannello grafico per illustrare il valore del modello
43
Contatore GUI 5
■ Una nuova vista per il contatore e`
rappresentata dalla classe
CounterViewDraw
■ Il valore del contatore e`
rappresentato nella vista da un pannello con delle palline:
◆rosse se positivo
◆blu se negativo
44
Contatore GUI 6
■ Le due viste precedenti sono unite in una unica:
CounterView ospita un pannello della classe JPanelCounter
■ Il metodo updateView deve occuparsi dell’aggiornamento sia del pannello grafico sia della etichetta di tipo JLabel
45
Contatore GUI 7
■ Piu` l’interfaccia si presenta complessa piu` diventa complesso il lavoro del controllore
■ Il controllore deve conoscere tutti gli oggetti che compongono la vista e contattarli tutti dopo aver inviato il messaggio al modello
■ Observer/Observable: permettono di rendere ignorante il controllore della presenza delle viste.
■ E` il Contatore GUI 6 realizzato con Observer/Observable
46
Event-Driven Programming with Observers
■ È possibile scrivere programmi che attivano i propri eventi internamente per mezzo della classe Observable e dell’interfaccia Observaber
■ È possibile implementare listener objects per componenti non grafiche
■ Quando un oggetto genera un evento, gli “Osservatori”
dell’oggetto ricevono un messaggio di update
■ Un oggetto ob è aggiunto alla lista di “osservatori” di un oggetto b tramite il messaggio b.addObserver(ob)
47
Contatore GUI 7
■ Nessuna relazione di associazione tra il controllo e la vista!
■ Observer: e` una interfaccia nel package java.util
■ Observable: e`
una classe nel package java.util
48
Contatore GUI 7: Model
import java.util.*;
public class Counter extends Observable { […]
public void init(int val){
c = val;
setChanged();
notifyObservers();
}
public void incr(){
c++;
setChanged();
notifyObservers();
}
public void decr(){
c--;
setChanged();
notifyObservers();
} […]
}
Definisce un oggetto osservabile ed eredita due nuovi metodi che sono usati per generare un evento
NB: il contatore non sa chi sono i suoi osservatori !!
49
Contatore GUI 7: Controller
[…]
public class CounterControl extends JPanel implements ActionListener { private Counter contatore;
[…]
public CounterControl(Counter cont){
[…]
// NON c'e` piu` bisogno della seguente!!
//contatoreVista = contVista;
[…]
}
public void actionPerformed(ActionEvent e){
Object source = e.getSource();
if (source == decrButton) contatore.decr();
else if (source == incrButton) contatore.incr();
else contatore.init(0);
// NON c'e` piu` bisogno della seguente!!
// contatoreVista.updateView();
} }
NB: il controller nonmenziona nessuna vista !!
50
Contatore GUI 7: Aggancio del Controller
[…]
public class ContatoreFrame extends JFrame { public ContatoreFrame(){
Counter contatoreModello = new Counter(0);
CounterView contatoreVista = new CounterView(contatoreModello);
contatoreModello.addObserver(contatoreVista);
Container cp = getContentPane();
cp.setLayout(new BorderLayout());
cp.add(contatoreVista, BorderLayout.CENTER);
cp.add(new ExitButton(), BorderLayout.SOUTH);
addWindowListener(new ExitFrame());
setTitle("Contatore GUI");
setSize(320, 220);;
setVisible(true);
}
public static void main(String[] args) { ContatoreFrame frame = new ContatoreFrame();
} }
contatoreVista si dichiara un ascoltatore del contatore
51
Contatore GUI 7: View
[…]
import java.util.*;
public class CounterView extends JPanel
implements CounterInterfaceView, Observer { […]
public CounterView(Counter model){
[…]
}
public void updateView(){
label.setText("Valore Contatore: " + contatore.getVal());
panelCounter.repaint();
}
public void update(Observable ob, Object extra_arg) { updateView();
} }
Ridefinisce il metodo update
Implementa l’interfaccia Observer
52
E.-D. P. with Observers
controller (e`
registrato come ActionListener di Decrementa)
model view
model.decr() view.update(...) model.getVal()
ActionEvent event
①
②
③
⑤
0 -1
actionPerformed(event) Action Listener
① OS intercetta l’evento “click di un bottone” e lo comunica all’AWT/Swing
② AWT/Swing determina la sorgente dell’evento, crea un ActionEvent e lo invia all’incaricato ActionListener
③ la procedura actionPerformed (event) del controllore e`
eseguita
④ il controllo invia l’oppor-tuno messaggio al modello
⑤ il modello notifica ai suoi ascoltatore l‘avvenuto l‘aggiornamento
⑥ gli ascoltatori eseguono il propio update
④
⑥
53
Event-Driven Programming with Observers
■ Vantaggi
◆stile di programmazione che disaccoppia ulteriormente le componenti del sistema
◆il controller, a differenza degli esempi precedenti, non ha più la necessità di conoscere le viste del modello
■ Svantaggi
◆non è sempre possibile utilizzare questo schema perché il modello deve ‘estendere’ la classe Observable
54
La Serie dei Contatori
■ Contatore GUI 8: e` il Contatore GUI 7 dove la vista grafica e` completamente slegata dalla vista principale (ma solo ospitata nel pannello)
■ Contatore GUI 9: e` il Contatore GUI 8 replicato 4 volte nella vista principale
55
Contatore GUI 8
■ E` il Contatore GUI 7 dove la vista grafica e`
completa- mente disacoppiata dalla vista principale (ma solo ospitata nel pannello)
56
Contatore GUI 8: View 1
[…]
public class CounterView extends JPanel
implements CounterInterfaceView, Observer { […]
public CounterView(Counter model){
[…]
JPanelCounter panelCounter = new JPanelCounter(model);
model.addObserver(panelCounter);
add(panelCounter, BorderLayout.CENTER);
[…]
}
public void updateView(){
label.setText("Valore Contatore: " + contatore.getVal());
}
public void update(Observable ob, Object extra_arg) { updateView();
} }
Update della sola Jlabel e non piu` repaint!!
57
Contatore GUI 8: View 2
[…]
public class JPanelCounter extends JPanel implements CounterInterfaceView, Observer { public JPanelCounter(Counter model) { contatore = model;
}
public void paintComponent(Graphics g) { […]
}
public void updateView(){
repaint();
}
public void update(Observable ob, Object extra_arg) { updateView();
} }
Si autogestisce l’update essendo a sua volta un Observer del contatore
58
Contatore GUI 9
■ E` il Contatore GUI 8 replicando quattro volte la vista grafica nella vista principale
■ Estrema facilita` nel gestire viste complesse
[…]
public class CounterView extends JPanel
implements CounterInterfaceView, Observer { private JPanelCounter[] arrayPanelCounter;
public CounterView(Counter model){
[…]
JPanel panelCenter = new JPanel(new GridLayout(2,2));
JPanelCounter[] arrayPanelCounter = new JPanelCounter[4];
for(int i=0;i<arrayPanelCounter.length;i++) { arrayPanelCounter[i] = new JPanelCounter(model);
model.addObserver(arrayPanelCounter[i]);
panelCenter.add(arrayPanelCounter[i]);
}
add(panelCenter, BorderLayout.CENTER);
[…]
} […]
59
Per orientarsi: Event Object
■ Ogni oggetto evento in Java estende la classe java.util.EventObject
public class KeyboardEvent extends java.util.EventObject { private char key;
KeyboardEvent (java.awt.Component source, char key) { super(source);
this.key = key;
} }
60
Per orientarsi: Event Listener
■ Ogni ascoltatore puo` essere rappresentato da un metodo in una data classe
■ Ognuno di questi metodi e` invocato quando un particolare evento si verifica
■ Questi metodi possono essere logicamente raggruppati in una interfaccia che condividono lo stesso tipo di evento che estendono, in Java, la classe java.util.EventListener
interface KeyboardListener extends java.util.EventListener { void keyPressed(KeyboardEvent ke);
void keyReleased(KeyboardEvent ke);
}
61
Per orientarsi: Event Listener
■ Un ascolatore per un determinato evento deve
implementare la relativa interfaccia che specifica il metodo che tratta tale evento
class MyClass implements KeyboardListener { public void keyPressed(KeyboardEvent ke) { // implementation of the method }
public void keyReleased(KeyboardEvent ke) { // implementation of the method }
}
62
Per orientarsi: Event Listener Registration
■ Rappresenta il collegamento di un ascoltatore presso la/le sorgente/i degli eventi che vuole ascoltare
■ Tecnicamente questo e` denominato event registration
■ Ogni oggetto sorgente di un evento deve provvedere due metodi per la registrazione e la deregistrazione degli eventuali ascoltatori
public void addKeyboardListener (KeyboardListener ke) { …
}
public void removeKeyboardListener (KeyboardListener ke) { …
}
63
Per orientarsi: Event Listener Registration
■ E` consigliabile che i metodi di registrazione e deregistrazione presso la sorgente siano definiti synchronized
■ L’oggetto sorgente si incarica di mantenere una lista di tutti gli ascoltatori registrati presso di lui
■ L’oggetto sorgente deve notificare l’evento occorso a tutti i suoi ascoltatori, questo e` realizzato inviando ad ognuno di essi l’oggetto evento mediante invocazione dell’opportuno metodo dell’ascoltatore.
64
Delegation Event Model: proviamo a costruirlo da noi
■ Tratto da: D. J. Berg e J. S. Fritzinger, Advanced Techniques for Java Developers, John Wiley & Sons, Inc., 1998, Cap. 2, pag. 13-22.
■ Si vuole creare una classe Counter e una classe CounterEvent, la classe Counter permette di creare dei contatori che vengono incrementati ad intervalli random di tempo. Quando un contatore viene incrementato un oggetto CounterEvent è inviato agli ascoltatori
CounterChangeListener registrati.
Counter
CounterEvent CounterChangeListener
65
Delegation Event Model: proviamo a costruirlo da noi
public class Counter extends Thread {
private java.util.Vector listeners = new java.util.Vector();
private int count = 0;
[…]
public void run() { while(true) { try {
sleep((int)Math.round(Math.random()*3000));
}
catch (InterruptedException e) {}
count++;
notifyCounterChange(count);
} }
public void startCounting() { this.start();
} continua ...
Ogni contatore estende la classe Thread
Questo è il codice eseguito in un thread separato
Ogni volta che il valore del contatore cambia viene eseguita la notifica a tutti gli ascoltatori del contatore stesso memorizzati in un apposito Vector
66
Delegation Event Model: proviamo a costruirlo da noi
continua ...
protected void notifyCounterChange(int count) { java.util.Vector tmpList;
CounterEvent ce = new CounterEvent(this, count);
synchronized(this) {
tmpList = (java.util.Vector) listeners.clone();
}
for (int i=0; i<tmpList.size(); i++) {
((CounterChangeListener)tmpList.elementAt(i)).
counterChange(ce);
} }
continua ...
Questo è il metodo di notifica dell’evento ad ogni ascoltatore
listeners è una
risorsa condivisa! Quando si estrae un oggetto daun Vector è necessario fare un downcast per poterlo vedere come ascoltatore degli eventi CounterEvent e poter invocare il metodo counterChange(ce)
67
Delegation Event Model: proviamo a costruirlo da noi
continua ...
public synchronized void
addCounterChangeListener(CounterChangeListener ccl) throws java.util.TooManyListenersException { listeners.addElement(ccl);
}
public synchronized void
removeCounterChangeListener(CounterChangeListener ccl){
listeners.removeElement(ccl);
} }
listener è una risorsa condivisa!
registrazione e deregistrazione degli ascoltatori presso un contatore
68
Delegation Event Model: proviamo a costruirlo da noi
public class CounterEvent extends java.util.EventObject { private int count;
CounterEvent(Object source, int count) { super(source);
this.count = count;
}
public int getCount() { return(count);
} }
Un CounterEvent contiene anche la sorgente dell’evento, cioe` il contatore incrementato.
69
Delegation Event Model: proviamo a costruirlo da noi
public class CountTest implements CounterChangeListener { public static void main(String args[]) {
CountTest ct = new CountTest();
}
public CountTest() { try {
Counter c = new Counter();
c.addCounterChangeListener(this);
c.startCounting();
}
catch(Exception err) {
System.out.println("Error: " + err);
} }
public void counterChange(CounterEvent evt) {
System.out.println("Counter value has changed: " + evt.getCount());
} }
public interface CounterChangeListener extends java.util.EventListener { void counterChange(CounterEvent e);
} L’interfaccia!
Registrazione dell’ascoltatore presso la sorgente degli eventi
Viene fatto partire un thread in cui il metodo run del contatore è eseguito
Metodo eseguito ogni volta che viene ascoltato un evento