Programmazione ad Eventi (Parte I) Programmazione ad Eventi (Parte II) Motivazioni La libreria SWING La gerarchia di classi Il modello MVC (Model View Controller) Il layout manager Costruire le interfacce utenti in java Gli ambienti IDE (Eclipse, JBuilder, Netbeans) Implementiamo una chat… (interazione sincrona e asincrona) paradigma ad eventi Programmare la GUI Il La libreria AWT (modelli basati su ereditarietà e su delega) Interazione sincrona Leggi - elabora - scrivi Inizio (main classico) Esecuzione sequenziale di un insieme di istruzioni In punti prestabiliti il controllo viene passato all’utente Input public static void main(String[] argv) { … leggi … elabora … scrivi … } Elabora Output Fine (ritorno al sistema operativo) Comunicazione sincrona Chiamata a funzione Identificazione del chiamato Invocazione della funzione Passaggio dei parametri (opzionale) Esecuzione della funzione Ritorno dei valori (opzionale) Il chiamante aspetta la terminazione del chiamato Comunicazione asincrona Vorrei disporre di un meccanismo asincrono Il chiamante invia la comunicazione e continua la propria esecuzione Il chiamato si attiva quando riceve la comunicazione, esegue il codice e poi termina Non c’e’ il trasfermiento del controllo (L’esecuzione del chiamante e del chiamato sono disaccoppiate) 1 Paradigma ad eventi Progetto programmi che reagiscono a stimoli esterni: Gli eventi realtà Applicazione Il programma è una collezione di funzioni attivabili su richiesta Non devo prevedere a priori la sequenza delle azioni L’applicazione è puramente reattiva Non è possibile identificare staticamente un flusso di controllo unitario Occorrenza dell’ evento B Occorrenza dell’ evento A Non esiste il main classico Non c’e’ un unico algoritmo Non c’e’ un inizio ed una fine Programmazione ad eventi (caratteristiche) Programmazione ad eventi (architettura) Il main esiste ma si limita a inizializzare l’applicazione istanziando gli osservatori e gli handler Possono coesistere piu flussi di esecuzione, uno per ogni evento (c’è bisogno della concorrenza) GUI schema di funzionamento Osservatore evento A Handler evento A Osservatore evento B Handler evento A e B Handler evento B Esempi di applicazioni Sistemi operativi Applicazioni di rete Interrupt, driver di dispositivo client server, scambio messaggi, peer to peer Applicazione interattive con interfaccia grafica a finestre Programmazione ad eventi con un linguaggio procedurale Widget (osservatore) button Callback (handler) Void pressed() { … } Application loop Esempio il C in ambiente Xwindow e win32 I widget sono delle strutture dati Le callback sono delle funzioni in C Ogni callback è associata al rispettivo widget attraverso la chiamata ad una opportuna funzione di libreria Ho bisogno di un loop infinito per essere reattivo agli eventi While (true) { … } 2 Esempio Xwindows Esempio win32 /*-----------------------------------------------------------HELLOWIN.C -- Displays "Hello, Windows 98!" in client area (c) Charles Petzold, 1998 Changes made by Naomi Novik, 2001 ------------------------------------------------------------*/ #include #include #include #include <stdio.h> <X11/Intrinsic.h> <X11/StringDefs.h> <X11/Xaw/Command.h> #include <windows.h> // Use a 24-point font #define FONT_SIZE 24 // have to declare the windows procedure before the entry point LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ; void push(Widget w, XtPointer client_data, XtPointer event_data) { printf("Button pushed!\n"); } int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, i nt iCmdShow) { static TCHAR s zAppName[ ] = TEXT ("HelloWin") ; HWND h wnd ; MSG m sg ; WNDCLASS w ndclass ; void main(int argc, char *argv[]) { XtAppContext ac; Widget top, button; top = XtVaAppInitialize(&ac, "XButton", NULL, 0, &argc, argv, NULL, NULL); button = XtVaCreateManagedWidget("Press here", commandWidgetClass, top, NULL); // We need to define the window class, which is responsible for // identifying the window procedure that will be used to handle // messages to this window // can just stick with this for now -- indicates that window should // be repainted whenever horizontal (HREDRAW) or vertical (VREDRAW) // size changes wndclass.style = CS_HREDRAW | CS_VREDRAW ; XtAddCallback(button, XtNcallback, push, NULL); XtRealizeWidget(top); XtAppMainLoop(ac); // *** defines the procedure that handles the messages ***/ wndclass.lpfnWndProc = WndProc ; wndclass.cbClsExtra wndclass.cbWndExtra wndclass.hInstance wndclass.hIcon wndclass.hCursor wndclass.hbrBackground wndclass.lpszMenuName } Esempio wi32 (cont…) // The windows procedure handles all of the messages received by the // window LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { HDC hdc ; PAINTSTRUCT ps ; RECT rect ; int fontHeight; HFONT f ont; if (!RegisterClass (&wndclass)) { // If the preprocessor definition UNICODE is defined, then // RegisterClass will fail unless the operating system // has Unicode support (Win NT, Win2K) MessageBox (NULL, TEXT ("This program requires Windows NT or Windows 2000!"), szAppName, MB_ICONERROR) ; return 0 ; } Loop infinito 0 ; 0 ; hInstance ; LoadIcon (NULL, IDI_APPLICATION) ; LoadCursor (NULL, IDC_ARROW) ; (HBRUSH) GetStockObject (WHITE_BRUSH) ; NULL ; Esempio win32 (cont…) // must give the window class a name -- note declaration of szAppName // above. wndclass.lpszClassName = szAppName ; // Can create multiple windows based on a single window class -- this // creates one hwnd = CreateWindow (szAppName, // window class name TEXT ("The Hello Program"), // window caption WS_OVERLAPPEDWINDOW, // window style CW_USEDEFAULT, // initial x position CW_USEDEFAULT, // initial y position CW_USEDEFAULT, // initial x size CW_USEDEFAULT, // initial y size NULL, // parent window handle NULL, // window menu handle hInstance, // program instance handle NULL) ; // creation parameters = = = = = = = switch (message) { // message received on window creation case WM_CREATE: PlaySound (TEXT ("hellowin.wav"), NULL, SND_FILENAME | SND_ASYNC) ; callback return 0 ; // message received whenever window needs to be repainted case WM_PAINT: hdc = BeginPaint (hwnd, &ps) ; handler // create the font we're going to use // look up CreateFont documentation to see what all the args do // Height // the first argument to M ulDiv is the desired point size, // and note that this will work only if we're in the MM_TEXT // mapping mode. fontHeight = -MulDiv(FONT_SIZE, GetDeviceCaps(hdc, LOGPIXELSY), 72); // when displaying, pass on preferences from args to W inMain ShowWindow (hwnd, iCmdShow) ; UpdateWindow (hwnd) ; font = C reateFont(fontHeight, 0, 0, 0, FW_DONTCARE, FALSE, FALSE, FALSE, ANSI_CHARSET, OUT_TT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, VARIABLE_PITCH | FF_ROMAN, TEXT("Arial") ); // here's the message loop that dispatches Windows messages while (GetMessage (&msg, NULL, 0, 0)) { TranslateMessage (&msg) ; DispatchMessage (&msg) ; } return msg.wParam ; SelectObject(hdc, font); } GetClientRect (hwnd, &rect) ; Esempio win32 (cont…) DrawText (hdc, TEXT ("Hello, World!"), -1, &rect, DT_SINGLELINE | DT_CENTER | DT_VCENTER) ; Programmazione ad eventi in un linguaggio OO Esempio Java Due approcci possibili EndPaint (hwnd, &p s) ; return 0 ; // message received whenever window is destroyed case WM_DESTROY: // inserts a WM_QUIT message into queue -- causes GetMessage // in the message loop above to return 0 PostQuitMessage (0) ; return 0 ; } // MUST return default, otherwise things like quitting won't work return DefWindowProc (h wnd, message, wParam, lParam) ; } Ereditarietà AWT 1.0 Delegazione AWT 1.1 3 Approccio per ereditarietà (1) Ogni widget è un oggetto (i.e., una istanza) Esiste una classe Java per ogni tipo di widget Approccio per ereditarietà (2) class Button {…} Le classi che rappresentano i diversi widget sono organizzate in una opportuna gerarchia di ereditarietà class Button extends Component { … } Gli eventi sono descritti come oggetti di un’unica classe e differenziati attraverso un opportuno attributo id Approccio per ereditarietà (3) I widget (istanze) sono organizzati in una gerarchia di aggregazione class Container extends Component { … public Component add(Component c){…} public void remove(Component c) {…} } Tale gerarchia determina la posizione dei vari widget Le callback sono espresse come metodi delle classi dei widget class Button extends Component { … public boolean action(Event, Object) {} } AWT 1.0 Per creare un widget di un tipo T con associata una certa callback bisogna: creare una sottoclasse T1 di T ridefinire il metodo action inserendovi il codice della callback istanziare T1, creando il widget vero e proprio Gli eventi non gestiti sono propagati lungo la gerarchia di aggregazione AWT 1.0 dettaglio AWT 1.0 dettaglio Il metodo handleEvent, se non ridefinito, invoca il metodo appropriato (action, keyUp ecc.) Se questi ritorna true il metodo handleEvent ritorna true altrimenti invoca il metodo handleEvent del contenitore del widget (accessibile con il metodo getParent()) I metodi di gestione di specifici eventi (action, keyUp ecc.), se non ridefiniti, ritornano false 4 Esempio AWT 1.0 Osservazioni import java.awt.*; public class JButton { static public void main(String[] args) { Frame top=new Frame("JButton"); Button b=new MyButton("Press here"); top.add(b); top.pack(); top.show(); } } class MyButton extends Button { public MyButton(String s) { super(s); } public boolean action(Event e, Object o) { System.out.println("Button pushed!"); return true; } class MyFrame extends Frame { B utton b; p ublic M yFrame(String s) { s uper(s) ; b =new Button("Press here"); a dd(b) ; p ack(); } public b oolean handleEvent(Event evt) { i f(evt.target==this && e vt.id==Event.WINDOW_DESTROY) { d ispose(); S ystem.exit(1); r eturn true; } else i f(evt.target==b && e vt.id==Event.ACTION_EVENT) { S ystem.out.println("Button pushed!"); r eturn true; } else return s uper.handleEvent(evt); } Librerie di classi General purpose Non contengono logica applicativa Esempi: (Vector, List, Queue…) Framework Per evitare la creazione di due classi (MyFrame e MyButton) si può affidare anche la gestione degli eventi relativi al bottone al frame (propagazione degli eventi) Difetti di AWT 1.0 } Librerie vs framework In particolare la finestra non si chiude (e l’applicazione non termina) anche se si preme sull’opportuno bottone Una possibile soluzione è creare una classe MyFrame che gestisca tali eventi e usare questa classe per creare il frame principale } Esempio rivisto L’applicazione non gestisce altri eventi oltre quello di pressione del bottone Per ogni widget bisogna creare una classe diversa solo per ridefinire il metodo di gestione degli eventi (es. 1) Tali classi hanno spesso una sola istanza oppure… bisogna concentrare la gestione degli eventi relativi a tutti i widget contenuti in un container in un’unica procedura (es. 2) complessa, poco leggibile, difficile da modificare. Non è Object Oriented! (e’ simile alla callback dell’esempio in C) Non aiuta una separazione chiara tra codice relativo all’interfaccia e codice applicativo Tra osservatore dell’evento e gestore dell’evento (event handler) Modello basato su delega In generale è possibile passare da un progetto basato sull’ereditarietà (generalizzazione) ad uno basato sulla delega (aggregazione) e viceversa Indirizzati per un dominio specifico Hanno numerose classi astratte Inglobano in parte la logica di funzionamento dell’applicazione Ha un proprio ciclo di vita. Solitamente è il framework a chiamare te invece che tu a chiamare il framework Esempi (AWT 1.1, SWING) 5 Approccio per delega (1) Ogni widget è un oggetto (i.e., una istanza) Esiste una classe Java per ogni tipo di widget class Button {…} Le classi sono organizzate in una opportuna gerarchia di ereditarietà class Button extends Component { … } I widget (istanze) sono organizzati in una gerarchia di aggregazione (component e container) L’evento diventa una classe e non più una chiamata a procedura Approccio per delega (2) concetto di Listener (ascoltatore) I widget rappresentano gli osservatori degli eventi Questi delegano ad opportuni “ event listener” la gestione degli eventi da loro originati Gli event listener sono descritti attraverso opportune interfacce Un event handler è un qualsiasi oggetto la cui classe implementi l’interfaccia di un event listener Gli eventi non gestiti da un opportuno handler NON sono propagati lungo la gerarchia di aggregazione realtà Applicazione Osservatore evento A Handler evento A Events e Listners Occorrenza dell’ evento B Occorrenza dell’ evento A ActionEvent, KeyEvent, ... Event source: buttons, checkboxes, choices, frames Event listener: una classe delegata a gestire certi eventi Un listener deve: Implementare una appropriata interfaccia; Informare il sorgente che è interessato a gestire gli eventi da lui generati. Un listener può ascoltare diverse tipologie di eventi da diverse sorgenti Anche il sorgente può essere un Listner (basta implementare l’interfaccia appropriata) Listeners possono essere delle classi complete oppure delle inner classes (metodo piu usato). Gerarchia degli eventi Java Handler evento A e B Osservatore evento B Handler evento B Esempi di eventi Window Events (resize, move, …) Buttons (pressed, relase, …) Mouse Events (click, doubleclick, …) Menus Dialog Boxes Text inputs, state changes, choice boxes, scroll bars 11 listners (interface) ActionListener AdjustmentListener ComponentListener ContainerListener FocusListener ItemListener KeyListener MouseListener MouseMotionListener TextListener WindowListener Ognuno è specializzato ad “ascoltare” determinati eventi 6 Mouse Events e Mouse Listners MouseListener mouseClicked mousePressed mouseReleased mouseEntered (enters a component) mouseExited (exits a component) MouseMotionListener mouseMoved mouseDragged MouseEvent int getX (), int getY, Point getPoint() getClickCount() Struttura dell’applicazione Creazione di un widget di tipo T con associata una callback: creare un oggetto della classe T (es Button) Creare una classe T1 che implementi l’opportuna interfaccia listener, inserendo il codice della callback nell’implementazione del metodo definito in questa interfaccia istanziare T1, creando l’event handler vero e proprio registrare tale event handler al widget (metodo addListener) notifica Event 1 Quando un mouse si muove sullo schermo vengono generati una infinità di eventi che, spesso ignorati dall’applicazione Tali eventi vengono messi in una coda e processati in FIFO Listner 1 addListner Widget 1 addListner Listner 2 notifica AWT 1.1: java.awt.* AWT 1.1: java.awt.event.* Esempio AWT 1.1 Piu ascoltatori import java.awt.*; import java.awt.event.*; public class JButton2 { static public void main(String[] args) { Frame top=new Frame("JButton"); Button b=new Button("Press here"); Handler h = new Handler(); b.addActionListener(h); top.add(b); top.pack(); top.show(); } } class Handler implements ActionListener { public void actionPerformed(ActionEvent e) { System.out.println("Button pushed!"); } } Widget. Sorgente degli eventi Creazione dell’osservatore registrazione dell’ osservatore presso la sorgente degli eventi Implementazione dell’osservatore E’ possibile associare più event listener allo stesso widget E’ sufficiente aggiungere i vari event listener tramite il metodo addXXXListener L’ordine di chiamate dei vari event listener non è fissato a priori (non determinismo) Un oggetto può essere “ascoltatore” di più eventi E’ sufficiente che la classe al quale appartiene implementi diverse interfacce di tipo event listener 7 Adapter (1) Adapter (2) Alcuni event listener comprendono numerose funzioni public abstract interface java.awt.event.WindowListener extends java.util.EventListener { Ridefinire tutte queste funzioni può essere molto noioso // Instance Methods public public public public public public public abstract abstract abstract abstract abstract abstract abstract void void void void void void void windowActivated (WindowEvent e); windowClosed (WindowEvent e); windowClosing (WindowEvent e); windowDeactivated (WindowEvent e); windowDeiconified (WindowEvent e); windowIconified (WindowEvent e); windowOpened (WindowEvent e); } Spesso siamo interessati ad una sola di queste Le classi astratte “adapter” risolvono questo problema Ne esiste una per ogni event listener “complesso” Implementano tutti i metodi del rispettivo event listener come metodi vuoti (i.e., che non fanno nulla) Adapter (3) import j ava.awt.*; import j ava.awt.event. *; public class JButton3 { static public void m ain(String[ ] args) { F rame top=new F rame("JButton"); B utton b=new Button("Press here"); H andler h = new Handler(); b .addActionListener(h) ; top.addWindowListener(h); t op.add(b) ; t op.pack(); top.show( ); } } class Handler extends WindowAdapter implements ActionListener p ublic void w indowClosing(WindowEvent e) { e .getWindow().dispose( ); System.exit(1); } p ublic void a ctionPerformed(ActionEvent e) { S ystem.out.println("Button pushed!"); } } 8