La gestione degli eventi e tecniche implementative

12
1 La gestione degli eventi e tecniche implementative Nelson Firmani Introduzione Ogni componenti di una GUI può essere interessato da eventi. L’evento indica il verificarsi di una situazione che richiede di essere gestita in generale in modo asincrono. Un esempio tipico di generazione di un evento è la sequenza di pressione-rilascio del pulsante del mouse (clic) su un componente (es: bottone) di una GUI: l’interrupt HW e le informazioni ad essa associata (codiceInterrupt, timeInterrupt, indirizzoInterrupt della periferica mouse, identificazione posizione puntatore mouse etc) è trasformata in evento SW e inoltrata al componente interessato (componente inteso come modulo SW che incapsula tutte le funzionalità, in questo caso, del bottone) (figura 1) CodInterrup IndInterrupt TimeInterrupt periferica etc MouseEvent Component SOFTWARE HARDWARE InterruptMouse INTERRUPT: Gestore risorse Figura 1: Dall’interrupt HW all’evento SW lo scatenarsi dell’evento deve provocare la risposta da parte dell’applicazione, quindi si pone il problema di come gestire gli eventi ricevuti da un componente nel contesto di un’applicazione che vede il componente come sorgente dell’evento. Una soluzione è incapsulare le funzionalità di risposte agli eventi nei metodi del componente (figura 2). Nella programmazione orientata agli oggetti l’implementazione di questa soluzione si basa sull’ereditarietà.

Transcript of La gestione degli eventi e tecniche implementative

Page 1: La gestione degli eventi e tecniche implementative

1

La gestione degli eventi e tecniche implementative

Nelson Firmani

Introduzione Ogni componenti di una GUI può essere interessato da eventi. L’evento indica il verificarsi di una situazione che richiede di essere gestita in generale in modo asincrono. Un esempio tipico di generazione di un evento è la sequenza di pressione-rilascio del pulsante del mouse (clic) su un componente (es: bottone) di una GUI: l’interrupt HW e le informazioni ad essa associata (codiceInterrupt, timeInterrupt, indirizzoInterrupt della periferica mouse, identificazione posizione puntatore mouse etc) è trasformata in evento SW e inoltrata al componente interessato (componente inteso come modulo SW che incapsula tutte le funzionalità, in questo caso, del bottone) (figura 1)

CodInterrupIndInterruptTimeInterruptperifericaetc

MouseEvent

Component

SOFTWARE

HARDWARE

InterruptMouse

INTERRUPT:

Gestore risorse

Figura 1: Dall’interrupt HW all’evento SW

lo scatenarsi dell’evento deve provocare la risposta da parte dell’applicazione, quindi si pone il problema di come gestire gli eventi ricevuti da un componente nel contesto di un’applicazione che vede il componente come sorgente dell’evento. Una soluzione è incapsulare le funzionalità di risposte agli eventi nei metodi del componente (figura 2). Nella programmazione orientata agli oggetti l’implementazione di questa soluzione si basa sull’ereditarietà.

Page 2: La gestione degli eventi e tecniche implementative

2

MyForm

operation(arg list)

ButtonInvia

+actionEvent();

ButtonCalcola

+actionEvent();

Button{abstract}

Figura 2: Schema di classi per soluzione basata sull’ereditarietà

Lo svantaggio principale di questa soluzione sta nell’accoppiamento stretto fra componente e applicazione. L’ereditarietà è una relazione statica, definita in compilazione, non è possibile durante l’esecuzione di un programma modificare la struttura gerarchica di una classe e questo generalmente è uno svantaggio. Quindi in contrapposizione all’ereditarietà si tende a far uso del meccanismo di composizione chiamato anche delegation, termine più astratto, che esula dal meccanismo di implementazione effettivamente utilizzato. L’idea è unicamente quella di un oggetto che delega a un altro determinati compiti (figura 3). Dunque il componente delega all’esterno la risposta all’evento. La risposta all’evento è un metodo del contenitore del componente.

Button

+getstate();

MyForm

+actEventOnClick();

eventSource l'implementazione diquesto metodo è legatoal contesto applicativoe si trova nel contesto

applicativo

nel contesto applicativo possiamoavere informazioni sull'evento:

eventSource.getState()

Figura 3: Schema di classi per soluzione basata su delega

Presenta il vantaggio di separare la sorgente degli eventi dal comportamento ad essi associati.

Page 3: La gestione degli eventi e tecniche implementative

3

Il modello event delegation Il modello si basa sulla separazione logica tra l’oggetto che fa partire l’evento EventSource e l’oggetto che si trova in ascolto EventListener per reagire all’evento. Ad esempio un componente di una GUI non sa cosa avverra al momento della sua sollecitazione: esso si limita a notificare ai propri ascoltatori che l’evento che essi attendevano è avvenuto, e questi provvederanno a produrre l’effetto desiderato. Dunque un Bottone (EventSource) notifica alla Form (EventListener) che l’utente lo ha premuto e il corrispondente codice da eseguire come risposta all’evento verra richiamato in modalità callback (figura 4). Con il termine callback si intende un modello di programmazione basato sui seguenti passi:

1) Registrazione di un oggetto EventListener (esempio: Form, Frame o in generale un contenitore di componenti) in un oggetto di attivazione EventSource (esempio: Bottone o in generale un componente).

2) Successiva attivazione dei metodi di EventListener da parte di EventSource.

EventSourceSender

es: Button

EventListenerListeneres: Form

Registrazione

Attivazionemetodo

t0

t1

t

In Delphi:Button1.OnClick := Form1.Button1Click;

In Java:Button1.addActionListener(Form1);

In Delphi:All'OnClick verra eseguita la porzione di codice puntata dal campo FOnClick (di tipo method-pointer):if Assigned(FOnClick) then FOnClick(Self,"Pulsante premuto");

In Java:Verrano richiamati i metodi (o il metodo) dell'interfaccia che la classe dell'ascoltatore implementa:public void actionPerformed(ActionEvent e) {

JOptionPane.showMessageDialog(this,"pulsante premuto"); }

Figura 4: Modello di programmazione basato sulla modalità callbak

Page 4: La gestione degli eventi e tecniche implementative

4

La collaborazione tra l’oggetto che genera l’evento EventSource e i vari ascoltatori EventListener ricalca il modello descritto dal pattern Observer (figura 5 e figura 6).

Subject

+add(in Observer)+remove(in Observer)+notify()

ConcreteSubject

-subjectstate;

+getstate();

ConcreteObserver

-observerstate;

+update();

Observer

+update();

1..m

observerState=subject.getstate();

Observers

subject

per ogni observer:observer.notify()

Figura 5: Schema di classi per pattern Observer

Page 5: La gestione degli eventi e tecniche implementative

5

EventSource

+add(in Listener)+remove(in Listener)+notify()

ConcreteEventSource

+getstate();

ConcreteListener

+actEvent(in EventSource);

EventListener

+actEvent();

1..m

Listeners

eventSource

per ogni observer:observer.notify()

Collegamento realizzatoimplicitamente attraverso ilpassaggio del parametro ditipo eventSource al metodoactEvent(EventSource). Il metodo actEvent(EventSource) riceve come

parametro un oggetto di tipo EventSource (Senderas TButton) che permette all'ascoltatore(ConcreteListener) di ottenere un riferimentosull'oggetto che ha mandato l'evento. Quindil'ascoltatore può ottenere informazioni dell'eventoattraverso la chiamata getState():

Event ConcreteEventSource;Event.getState();

Figura 6: Schema di classi per il modello event delegation Esistono sostanzialmente due tecniche implementative: puntatore a funzione e interfacce. Callback mediante puntatore a funzione:

1. il componente (EventSource) crea un puntatore a funzione per ogni evento che è in grado di innescare;

2. il contenitore (EventListener) fornisce gli indirizzi dei propri metodi di risposta. Questa è la soluzione implementativa usata dalla VCL (Visual Component Library) legata agli ambienti di sviluppo Borland (Delphi, C++ Builder). Un simile approccio è usato dal framework .NET che standardizza il processo di callback utilizzando

Page 6: La gestione degli eventi e tecniche implementative

6

l’oggetto Delegate appartenente alla classe System.Delegate che incapsula l’indirizzo (puntatore a funzione) della funzione implementata nel EventListener. Il Delegate contiene dei metodi che il Sender (EventSource) può utilizzare per invocare la funzione implementata nel EventListener, in modalità sincrona o asincrona. Interfacce:

1. il componente (SourceEvent) definisce un’interfaccia che comprende i metodi di risposta all’evento;

2. il contenitore (EventListener) implementa l’interfaccia e la registra nel componente;

3. allo scatenarsi di un evento il componente invoca i metodi dell’interfaccia che la classe dell’ascoltatore implementa.

Questa è la soluzione implementativa dell’event delegation model presente nelle classi AWT e riutilizzato in Swing della distribuzione standard di Java.

Implementazione del modello Event Delegation in Borland Delphi: Disegnamo l’applicazione:

Figura 7: Esempio di implementazione del modello event delegation in VCL delphi

Button1 è l’EventSource. Form1 è l’EventListener. Possiamo fornire il comportamento associato all’evento OnClick del componente Button1 semplicemente: Object Inspector Events doppio click su OnClick Il component editor automaticamente produrrà la seguente signature: procedure TForm1.Button1Click(Sender: TObject); begin // qui il codice che verrà richiamato allo scatenarsi dell’evento end; quindi inserire il codice che verrà richiamato allo scatenarsi dell’evento: procedure TForm1.Button1Click(Sender: TObject);

Page 7: La gestione degli eventi e tecniche implementative

7

begin ShowMessage('Premuto: '+TButton(Sender).Name); end; Dunque per lo sviluppatore dell’applicazione l’evento che il componente è in grado di innescare è una proprietà del componente che può essere assegnata a design time mediante l’object inspector. Questo meccanismo viene implementato mediante i puntatori a funzione (in oop meglio dire puntatori a metodi di oggetti), che in run time permettono di specificare l’indirizzo di porzioni di codice in memoria che deve essere richiamato dal componente. Infatti nella unit Classes della VCL sono stati definiti i tipi puntatori a metodi di oggetti (method-pointer o event-handler) degli eventi standard1. unit Classes; … type { Standard events } TNotifyEvent = procedure(Sender: TObject) of object; …

TWinControl

TButtonControl

TControl

TButton

TComponent

Figura 8: Gerarchia classe Tbutton della VCL delphi

1 TNotifyEvent viene utilizzato per notificare ai propri ascoltatori che l’evento che attendevano è avvenuto. Il parametro Sender è il riferimento al componente EventSource, questo permette agli ascoltatori di interrogare il componente che ha generato l’evento (tipo evento?, nome o altra proprietà del componente?). Altro esempio di tipo di puntatore a metodi di oggetti è il seguente: type TKeyPressEvent=procedure(Sender: TObject; var Key: Char) of object; Gli ascoltatori oltre al riferimento al Sender hanno l’informazione del tasto premuto attraverso il parametro Key.

Page 8: La gestione degli eventi e tecniche implementative

8

Dalla gerarchia di classi di figura 8 si ha che TButton deriva da TControl, nella classe TControl sono stati definiti gli eventi comuni a tutti i controlli come proprietà, ad esempio la proprietà OnClick rappresenta l’evento OnClick. La proprietà OnClick di tipo TNotifyEvent il cui valore viene memorizzato nel campo FOnClick della classe TControl è un puntatore a un metodo. TControl = class(TComponent) private … FOnClick: TNotifyEvent; … protected … procedure Click; dynamic; … property OnClick: TNotifyEvent read FOnClick write FOnClick stored IsOnClickStored; … end; Tutti i controlli ereditano un metodo dinamico che si limita a verificare l’assegnamento del relativo campo evento quindi demandare la gestione dello stesso all’ascoltatore. Esempio: if assigned(FOnClick) then FOnClick(Self); Nel caso dell’evento OnClick di TButton si ha il metodo dinamico click la cui implementazione è: procedure TControl.Click; begin { Call OnClick if assigned and not equal to associated action's OnExecute. If associated action's OnExecute assigned then call it, otherwise, call OnClick. } if Assigned(FOnClick) and (Action <> nil) and (@FOnClick <> @Action.OnExecute) then FOnClick(Self) else if not (csDesigning in ComponentState) and (ActionLink <> nil) then ActionLink.Execute(Self) else if Assigned(FOnClick) then FOnClick(Self); end; La proprietà OnClick può essere assegnata a design-time mediante l’Object Inspector o a run-time mediante: Button1.OnClick := Form1.Button1Click; Allo scatenarsi dell’evento OnClick l’oggetto Button1 (EventSource) delega all’oggetto Form1 (EventListener) la risposta all’evento. Si realizza la separazione tra la sorgente degli eventi dal comportamento ad essi associato.

Page 9: La gestione degli eventi e tecniche implementative

9

Gestione Eventi senza (imitando) la VCL. (creazione oggetti EventSource e EventListener) In linea generale le operazioni da compiere sono:

1. definizione di un tipo evento come puntatore a metodi di oggetti (method pointer); esempio: type TEvent = procedure (Sender: TObject; Msg: String) of object;

2. definizione della classe origine dell’evento (TEventSource): la quale deve contenere:

a. il metodo che scatena l’evento (esempio: procedure TriggerEvent);

b. un campo di tipo evento (definito in 1) che memorizza la proprietà (anch’essa di tipo evento) alla quale verrà assegnata (in fase di instanzazione) il metodo da richiamare allo scatenarsi dell’evento; esempio: TEventSource = class private FonEvent: TEvent; public procedure TriggerEvent; property onEvent: TEvent read FonEvent write FonEvent; end;

3. definizione della classe ascoltatore dell’evento: deve contenere il metodo da eseguire allo scatenarsi dell’evento; esempio: TEventListener = class public procedure ActEvent(Sender: TObject; Msg: String); end;

4. creazione degli oggetti (istanzazione):

qui deve avvenire l’assegnazione della proprietà di tipo evento, con il metodo dell’ascoltatore che risponde all’evento. Esempio: ASourceEvent.onEvent := AListener.ActEvent;

Listato 1: Esempio di creazione di un oggetto EventSource e il relativo EventListener in Delphi program gestione_eventi_delphi; {$APPTYPE CONSOLE} uses SysUtils;

Page 10: La gestione degli eventi e tecniche implementative

10

type TEvent = procedure (Sender: TObject; Msg: String) of object; TEventSource = class private FonEvent: TEvent; public procedure TriggerEvent; property onEvent: TEvent read FonEvent write FonEvent; end; TEventListener = class public procedure ActEvent(Sender: TObject; Msg: String); end; { TEventSource } procedure TEventSource.TriggerEvent; begin if Assigned(FonEvent) then FonEvent(Self, '!!! Evento scattato !!!'); end; { TEventListener } procedure TEventListener.ActEvent(Sender: TObject; Msg: String); begin Writeln('Questo messaggio è la risposta all''evento generato dall''oggetto'); Writeln('tipo EventSource. Il messaggio inviato dall''oggeto EventSource è: ', Msg); end; var ASourceEvent: TEventSource; AListener: TEventListener; begin ASourceEvent := TEventSource.Create; AListener := TEventListener.Create; ASourceEvent.onEvent := AListener.ActEvent; ASourceEvent.TriggerEvent; ASourceEvent.onEvent := nil; Readln; end.

Page 11: La gestione degli eventi e tecniche implementative

11

Implementazione del modello Event Delegation in Java In java la collaborazione tra l’oggetto che genera l’evento (EventSource) e l’oggetto ascoltatore (EventListener) ricalca perfettamente il modello UML di figura 6. Le classi che permettono di gestire gli eventi generati dai componenti GUI si trovano nei package: java.awt.event e javax.swing.event. I passaggi da eseguire quando si intende gestire gli eventi generati dai componenti awt, swing sono:

1. decidere a quali eventi un ascoltatore è interessato, (esempio: eventi dei pulsanti, eventi del mouse, eventi della tastiera, …) quindi implementare l’opportuna interfaccia del tipo XxxListener (esempio: ActionListener, MouseListener, KeyListener, …); esempio: class Listener implements ActionListener

2. implementare i metodi definiti nell’interfaccia (i metodi dell’interfaccia che la classe dell’ascoltatore implementa contengono il codice eseguito allo scatenarsi dell’evento); esempio: public void actionPerformed(ActionEvent e) { //codice eseguito allo scatenarsi dell’evento }

3. registrare l’ascoltatore presso il componente sorgente dell’evento. (Ogni componente EventSource dispone di un metodo addXxxListener() e removeXxxListener() per ogni evento supportato: per esempio i pulsanti hanno i metodi addActionListener() e removeActionListener() per gestire gli ascoltatori che implementano l’interfaccia di tipo ActionListener. Mediante questi metodi è possibile registrare l’ascoltatore al componente). Esempio: button1.addActionListener(new Listener());

Listato 2: Esempio di gestione degli eventi in Java package it.tinn.nelson.paper.esempi.eventi.es1; import java.awt.*; import java.awt.event.*; import javax.swing.*; /** * @(#)MyFrame.java * * @author Nelson Firmani * @version 1.00 06/08/23 */ public class MyFrame extends JFrame { private JButton button1; private Container cnt; public MyFrame(String str, int height, int width) { setTitle(str); setSize(new Dimension(height, width)); button1 = new JButton("Button1"); cnt = getContentPane(); cnt.setLayout(new FlowLayout(FlowLayout.CENTER));

Page 12: La gestione degli eventi e tecniche implementative

12

cnt.add(button1); button1.addActionListener(new Listener()); } }; class Listener implements ActionListener { public Listener() { } public void actionPerformed(ActionEvent e) { JOptionPane.showMessageDialog(null,"premuto pulsante con le seguenti proprietà: " + e.getSource().toString()); } } /** * @(#)EsGestioneEventi.java * * @author Nelson Firmani * @version 1.00 06/08/23 */ package it.tinn.nelson.paper.esempi.eventi.es1; public class EsGestioneEventi { public static void main(String[] args) { MyFrame frame = new MyFrame("frame1",200,200); frame.show(); } }

Author: Ing. Nelson Firmani ([email protected])

Last update: 28/08/2006