GUI e MVC Programmazione in rete e laboratorio Gli oggetti prima di tutto: GUI, Event-driven programming e l’architettura MVC Matteo Baldoni Dipartimento di Informatica Universita` degli Studi di Torino C.so Svizzera, 185 I-10149 Torino [email protected] http://www.di.unito.it/~baldoni/didattica 2 Graphical User Interface Contatore GUI 0 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; } 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 chiude la finestra decrementa il Desideriamo una interfaccia contatore di 1 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) visualizza il valore corrente un bottone per abbandonare inizializza il contatore a zero l’interfaccia chiude la finestra incrementa il contatore di 1 3 L’architettura Model View Controller 4 L’architettura Model View Controller http://www.cis.ksu.edu/~schmidt/CIS200 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 nuovamente 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 5 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) richiede alla vista di visualizzare il risultato della computazione La vista interroga il modello sul suo nuovo stato interno e visualizza l’informazione all’utente 6 1 Architettura MVC: vantaggi MVC: sequenza dei messaggi 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) ⑤ view 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()) model.getVal() model ⑤ 0 -1 view.updateView() ④ ② model.decr() event ③ Action Listener actionPerformed(…) controller 7 Event-Driven Programming 8 Delegation Event Model 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) 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 Object Event Source Event Listener Listener Registration 9 Event-Driven Programming 10 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) ② ③ ④ ⑤ 11 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 view model.getVal() model ⑤ ① 0 -1 view.updateView() ② ④ model.decr() ActionEvent event ④ Action Listener actionPerformed(event) ③ controller (e` registrato come ActionListener di Decrementa) 12 2 Event-Driven Programming AWT/Swing 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 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) 13 AWT/Swing 14 Contatore GUI 0: view Componente Finestre (windows): contenitori che possono essere visualizzati direttamente sullo schermo JLabel JPanel FlowLayout JPanel valore contatore: ... BorderLayout CENTER SOUTH Frame: finestre con titolo e menu visualizzate permanentemente sullo schermo durante l’esecuzione di un programma Decrementa Reset Incrementa JPanel FlowLayout Dialog: finestre visualizzate temporaneamente sullo schermo durante l’esecuzione di un programma (es. visualizzano messaggi di errore, input file, ecc) JButton Contenitori JButton JButton Componenti (in realta` sono a loro volta contenitori, etichette, icon, …) Struttura del contenitore JPanel 15 Contatore GUI 0: view Contatore GUI 0: view NORTH CENTER EAST Creazione del contenitore JPanel della vista Uso del costruttore super Layout scelto: BorderLayout WEST 16 panelCenter: pannello da aggiungere al centro del BorderLayout del pannello principale Layout scelto: FlowLayout valore contatore: ... SOUTH label panelCenter […] label = new JLabel(“Valore contatore: "); JPanel panelCenter = new JPanel(new FlowLayout()); panelCenter.add(label); add(panelCenter, BorderLayout.CENTER); […] public class CounterView extends JPanel { public CounterView(Counter model){ super(new BorderLayout()); // alternativa: setLayout(new BorderLayout()); […] } 17 18 3 Contatore GUI 0: view panelSouth: pannello a Sud nel pannello principale della vista Layout scelto: FlowLayout Contatore GUI 0: view valore contatore: ... Decrementa Reset Incrementa […] JPanel panelSouth = new JPanel(new FlowLayout()); JButton bottoneDecr = new JButton("Decrementa"); panelSouth.add(bottoneDecr); JButton bottoneReset = new JButton("Reset"); panelSouth panelSouth.add(bottoneReset); JButton bottoneIncr = new JButton("Incrementa"); panelSouth.add(bottoneIncr); add(panelSouth, BorderLayout.SOUTH); […] Definizione del metodo updateView() per l’aggiornamento della vista Uso del modello (contatore) per reperire le informazioni necessarie per l’aggiornamento della vista valore contatore: ... Decrementa public void updateView(){ label.setText("Valore Contatore: " + contatore.getVal()); } 20 Contatore GUI 0: aggancio del controller 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 CounterView extends JPanel { public CounterView(Counter model){ […] creazione del controllore contatore = model; […] controlloCounter = new CounterControl(contatore, this); […] JButton bottoneDecr = new JButton("Decrementa"); bottoneDecr.addActionListener(controlloCounter); […] aggancio JButton bottoneReset = new JButton("Reset"); bottoneReset.addActionListener(controlloCounter); […] JButton bottoneIncr = new JButton("Incrementa"); bottoneIncr.addActionListener(controlloCounter); […] updateView(); 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(); } } } 21 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 ② view crea 22 Contatore GUI 0: overview invia messaggi model ① 0 crea Incrementa label 19 Reset invia messaggi crea • la vista riceve il modello tra i suoi main eventi controller parametri Action Listener • il controllore riceve tra actionPerformed(event) i suoi parametri la vista e il modello ③ 23 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 24 4 Contatore GUI 0: frame Finestra: JFrame Contatore GUI 0: frame setTitle(…) Contatore GUI Contatore GUI windowClosing(…) Contenitore all’ interno del JFrame Contatore GUI 1: view valore contatore: ... Container Container (pannello del contenuto) (pannello del contenuto) BorderLayout CENTER SOUTH Exit JButton Componente Decrementa Reset Incrementa BorderLayout CENTER SOUTH Exit Struttura del Container 25 Contatore GUI 0: frame 26 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()); il bottone cp.add(contatoreVista, BorderLayout.CENTER); di Exit cp.add(new ExitButton(), BorderLayout.SOUTH); addWindowListener(new ExitFrame()); setTitle("Contatore GUI"); per la chiusura setSize(300, 140); sull “X” della finestra setVisible(true); } public static void main(String[] args) { ContatoreFrame frame = new ContatoreFrame(); } private Counter contatoreModello; private CounterView contatoreVista; il main e` tutto qua!! } Classi per la chiusura dell’applicativo mediante la “X” sulla finestra e un bottone “Exit” Vanno bene per molti applicativi diversi dal contatore GUI 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); } } 27 Contatore GUI 1: l’indipendenza dalla vista Si desidera rendere indipendente il controllore dalla vista L’idea è quella che il controllore faccia riferimento ad una interfaccia anziché direttamente alla vista 28 Contatore GUI 1: controller public interface CounterInterfaceView { void updateView(); } public class CounterView extends JPanel implements CounterInterfaceView { […] public updateView(){ […] } […] } 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(); } } 29 30 5 Contatore GUI 1( bis): l’indipendenza dalla vista Contatore GUI 1: overview Diagramma delle classi per il contatore GUI 1 Introduzione di una interfaccia per la 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 31 Contatore GUI 2 32 Contatore GUI 2: controller Variante: il controllo contiene i bottoni public class CounterControl extends JPanel implements ActionListener { […] private JButton decrButton; […] public CounterControl(Counter cont, CounterInterfaceView contVista){ […] decrButton = new JButton("Decrementa"); add(decrButton); posso determinare decrButton.addActionListener(this); piu` facilemente la […] } sorgente essendo public void actionPerformed(ActionEvent e){ questa interna alla Object source = e.getSource(); classe stessa if (source == decrButton) contatore.decr(); else if (source == incrButton) contatore.incr(); else contatore.init(0); contatoreVista.updateView(); } } I bottoni sono il controllo E` piu` facile determinare la sorgente 33 Inside Contatore GUI 1 34 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 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 Interfaccia ascoltatore Ascoltatore: implementa il metodo specificato nell’interfaccia 35 36 6 Inside Contatore GUI 1 Contatore GUI 3 visualizza il valore corrente o 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(); } } chiude la finestra Modifichiamo l’applicativo l’eventuale errore input del valore precedente in modo da poter iniziale del contatore 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 inizializza il contatore con il valore immesso decrementa il contatore di 1 incrementa il contatore di 1 chiude la finestra 37 Contatore GUI 3: view 38 Contatore GUI 3: frame setTitle(…) JTextField windowClosing(…) Contatore GUI JLabel JLabel JPanel valore iniziale: FlowLayout 0 valore iniziale: JPanel valore contatore: ... BorderLayout NORTH CENTER SOUTH JPanel FlowLayout Decrementa Inizializza Incrementa JButton Container (pannello del contenuto) valore contatore: ... JPanel Decrementa FlowLayout JButton Contatore GUI 3: view 0 JButton Inizializza BorderLayout CENTER SOUTH Incrementa Exit JButton 39 Contatore GUI 3: controller 40 La Serie dei Contatori 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()); } lettura del valore } in input nel campo else { JTextField tramite if (source == incrButton) contatore.incr(); interrogazione della else contatore.decr(); contatoreVista.setAnswer(); vista } contatoreVista.updateView(); } 41 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 42 7 Contatore GUI 5 Contatore GUI 6 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 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 43 44 Event-Driven Programming with Observers 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 È possibile scrivere programmi che attivano i propri eventi internamente per mezzo della classe Observable e dell’interfaccia Observer È 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) 45 Contatore GUI 7 46 Contatore GUI 7: Model import java.util.*; 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 47 public class Counter extends Observable { […] public void init(int val){ c = val; setChanged(); notifyObservers(); Definisce un oggetto } osservabile ed eredita public void incr(){ due nuovi metodi che c++; sono usati per setChanged(); generare un evento notifyObservers(); } public void decr(){ c--; NB: il contatore non setChanged(); sa chi sono i suoi notifyObservers(); } osservatori !! […] } 48 8 Contatore GUI 7: Controller Contatore GUI 7: Aggancio del Controller […] public class CounterControl extends JPanel implements ActionListener { private Counter contatore; […] NB: il public CounterControl(Counter cont){ controller […] // NON c'e` piu` bisogno della seguente!! non //contatoreVista = contVista; menziona […] nessuna } vista !! 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(); } } 49 ① 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 Implementa l’interfaccia Observer public class CounterView extends JPanel implements CounterInterfaceView, Observer { […] public CounterView(Counter model){ Implementa il […] metodo update } public void updateView(){ label.setText("Valore Contatore: " + contatore.getVal()); panelCounter.repaint(); } public void update(Observable ob, Object extra_arg) { updateView(); } } 51 Event-Driven Programming with Observers 50 E.-D. P. with Observers Contatore GUI 7: View […] import java.util.*; […] 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()); contatoreVista cp.add(contatoreVista, BorderLayout.CENTER); si dichiara un cp.add(new ExitButton(), BorderLayout.SOUTH); ascoltatore del addWindowListener(new ExitFrame()); contatore setTitle("Contatore GUI"); setSize(320, 220);; setVisible(true); } public static void main(String[] args) { ContatoreFrame frame = new ContatoreFrame(); } } ③ la procedura actionPerformed (event) del controllore e` eseguita ④ il controllo invia l’opportuno messaggio al modello ⑤ il modello notifica ai suoi ascoltatore l‘avvenuto l‘aggiornamento ⑥ gli ascoltatori eseguono il propio update ⑥ model.getVal() view.update(...) model 0 -1 ⑤ ② model.decr() ActionEvent event ④ Action Listener actionPerformed(event) ③ controller (e` registrato come ActionListener di Decrementa) 52 La Serie dei Contatori 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 view 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 Svantaggi non è sempre possibile utilizzare questo schema perché il modello deve ‘estendere’ la classe Observable 53 54 9 Contatore GUI 8 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); Update della add(panelCounter, BorderLayout.CENTER); […] sola Jlabel e non } piu` repaint!! E` il Contatore GUI 7 dove la vista grafica e` completamente disacoppiata dalla vista principale (ma solo ospitata nel pannello) public void updateView(){ label.setText("Valore Contatore: " + contatore.getVal()); } public void update(Observable ob, Object extra_arg) { updateView(); } } 55 Contatore GUI 8: View 2 Contatore GUI 9 […] public class JPanelCounter extends JPanel implements CounterInterfaceView, Observer { public JPanelCounter(Counter model) { contatore = model; } public void paintComponent(Graphics g) { […] } public void updateView(){ repaint(); } 56 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); […] } […] Si autogestisce l’update essendo a sua volta un Observer del contatore public void update(Observable ob, Object extra_arg) { updateView(); } } 57 Per orientarsi: Event Object 58 Per orientarsi: Event Listener 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; } } 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); } 59 60 10 Per orientarsi: Event Listener Per orientarsi: Event Listener Registration Un ascolatore per un determinato evento deve implementare la relativa interfaccia che specifica il metodo che tratta tale evento class MyClass implements KeyboardListener { 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 keyPressed(KeyboardEvent ke) { // implementation of the method } public void addKeyboardListener (KeyboardListener ke) { … } public void removeKeyboardListener (KeyboardListener ke) { … } public void keyReleased(KeyboardEvent ke) { // implementation of the method } } 61 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. 62 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. CounterEvent CounterChangeListener Counter 63 Delegation Event Model: proviamo a costruirlo da noi 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; […] Ogni contatore estende public void run() { la classe Thread while(true) { try { sleep((int)Math.round(Math.random()*3000)); } catch (InterruptedException e) {} count++; Questo è il codice notifyCounterChange(count); eseguito in un thread } separato } public void startCounting() { this.start(); } continua ... 64 Ogni volta che il valore del contatore cambia viene eseguita la notifica a tutti gli ascoltatori del contatore stesso memorizzati in un apposito Vector 65 Questo è il metodo di notifica dell’evento ad ogni ascoltatore 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); } listeners è una } Quando si estrae un oggetto daun Vector è risorsa condivisa! necessario fare un downcast per poterlo continua ... vedere come ascoltatore degli eventi continua ... CounterEvent e poter invocare il metodo counterChange(ce) 66 11 Delegation Event Model: proviamo a costruirlo da noi continua ... Delegation Event Model: proviamo a costruirlo da noi listener è una risorsa condivisa! public class CounterEvent extends java.util.EventObject { private int count; public synchronized void addCounterChangeListener(CounterChangeListener ccl) throws java.util.TooManyListenersException { listeners.addElement(ccl); } CounterEvent(Object source, int count) { super(source); this.count = count; } public synchronized void removeCounterChangeListener(CounterChangeListener ccl){ listeners.removeElement(ccl); } } public int getCount() { return(count); } } Un CounterEvent contiene anche la sorgente dell’evento, cioe` il contatore incrementato. registrazione e deregistrazione degli ascoltatori presso un contatore 67 68 Delegation Event Model: proviamo a costruirlo da noi public interface CounterChangeListener extends java.util.EventListener { void counterChange(CounterEvent e); L’interfaccia! } public class CountTest implements CounterChangeListener { public static void main(String args[]) { CountTest ct = new CountTest(); } public CountTest() { Registrazione dell’ascoltatore try { presso la sorgente degli eventi Counter c = new Counter(); c.addCounterChangeListener(this); Viene fatto partire un thread c.startCounting(); in cui il metodo run del contatore } catch(Exception err) { è eseguito System.out.println("Error: " + err); } Metodo eseguito ogni volta che } viene ascoltato un evento public void counterChange(CounterEvent evt) { System.out.println("Counter value has changed: " + evt.getCount()); } 69 } 12