zdarzenia.doc

(508 KB) Pobierz
DELEGACYJNY MODEL OBSŁUGI ZDARZEŃ

Autor wykładu:

 

Krzysztof Barteczko

 

 

 

 

 

DELEGACYJNY MODEL OBSŁUGI ZDARZEŃ

 

 

Mechanizm obsługi zdarzeń – na przykładzie obslugi zdarzeń akcji

 

Reguły ogólne i mechanizm obsługi zdarzeń

Anonimowe klasy wewnętrzne dla obsługi zdarzeń

Uzyskiwanie informacji o zdarzeniach

Specjalizowani uniwersalni słuchacze zdarzeń

Właściwość clientProperty i jej wykorzystanie przy obsłudze zdarzeń

Selekcja obsługiwanych zdarzeń i komponentów

Dynamiczne zmiany funkcjonalności: przyłączanie i odłączanie słuchaczy

Separacja

 

 

Hierarchia klas zdarzeniowych

 

Obsługa zdarzeń – interfejsy nasłuchu, metody, reguły nazewnicze

 

Obsługa zdarzeń myszki

 

Menu kontekstowe

 

Fokus

 

Obsługa klawiatury

 

Obsługa okien

Zdarzenia na komponentach wyboru


Reguły ogólne i mechanizm obsługi zdarzeń

 

·         Interakcja użytkownika z  GUI naszej aplikacji polega na wywoływaniu zdarzeń (np. kliknięcie w przycisk, wciśnięcie klawisza na klawiaturze etc).

·         Zatem programowanie GUI jest programowaniem zdarzeniowym. Jest ono oparte na koncepcji tzw. funkcji callback. Funkcja typu callback zawarta w naszym programie jest wywoływana (zazwyczaj) nie przez nasz program, ale przez sam system na przykład w reakcji na zdarzenia takie jak wciśnięcie klawisza czy kliknięcie myszką. Funkcja ta obsługuje zdarzenie.

·         W Javie dodatkowo zastosowano koncepcję delegacyjnego modelu obsługi zdarzeń, która umożliwia przekazanie obsługi zdarzenia, które przytrafia się jakiemuś obiektowi (Źródło zdarzenia) do innego obiektu (Słuchacza zdarzenia).

·         Zdarzenia są obiektami odpowiednich klas, określających rodzaj zdarzeń.

·         Słuchacze obiektami klas implementujących interfejsy nasłuchu. Interfejsy nasłuchu określają zestaw metod obsługi danego rodzaju zdarzeń.

·         W klasach słuchaczy definiuje się metody odpowiedniego interfejsu nasłuchu zdarzeń, które określają co ma się dziać w wyniku zajścia określonego zdarzenia (metody obsługi odpowiednich zdarzeń)

·         Zdarzenie (obiekt odpowiedniej klasy zdarzeniowej) jest przekazywane do obsługi obiektowi-słuchaczowi tylko wtedy gdy Słuchacz ten jest przyłączony do Źródła zdarzenia. (przyłączenie za pomocą odwołania z.addNNNListener(h), gdzie: z – Źródło zdarzenia, NNN -  rodzaj zdarzenia, h – Słuchacz danego rodzaju zdarzenia)

·         Przekazanie zdarzenia do obsługi polega na wywołaniu odpowiedniej dla danego zdarzenia metody obsługi zdarzenia (zdefiniowanej w klasie Słuchacza) z argumentem obiekt-zdarzenie.

·         Argument (obiekt klasy zdarzeniowej)  zawiera informacje o okolicznościach zajścia zdarzenia (np. komu się przytrafiło? kiedy? jakie ma inne właściwości?). Jako parametr w metodzie obsługi może być odpytany o te informacje.

 

W Javie standardowo zdefiniowano bardzo dużo różnych rodzajów zdarzeń i interfejsów ich nasłuchu.  Rozpatrzmy ogólne zasady na przykładzie zdarzenia "akcja" (obiekt klasy ActionEvent), które powstaje:

·         po kliknięciu w przycisk lub naciśnięciu spacji, gdy przycisk ma fokus (zdolność do przyjmowania zdarzeń z klawiatury),

·         po naciśnięciu ENTER w polu edycyjnym,

·         po wyborze opcji menu,

·         po podwójnym kliknięciu / ENTER na liście AWT (ale tylko AWT, nie dotyczy to listy Swingu - JList) lub na liście rozwijalnej Swingu - JComboBox

·         w innych okolicznościach, ew. zdefiniowanych przez program

 

(powstaje - o ile do Źródła przyłączono odpowiedniego Słuchacza akcji – czyli obiekt klasy implementującej interfejs nasłuchu akcji  -  ActionListener)

 

Uwaga: zdarzenia "akcja" jest zdarzeniem semantycznym - niezależnym od fizycznego kontekstu (zauważmy jak w różnych fizycznych okolicznościach ono powstaje – kliknięcie, naciśnięcie spacji lub ENTER, w różnych "fizycznych" kontekstach – na przycisku, w polu edycyjnym, w menu). Nie należy go mylić ze zdarzeniem kliknięcia myszką (czysto fizyczne zdarzenie).


Wyobraźmy sobie teraz, że w naszym GUI mamy jeden przycisk:

 

import javax.swing.*;

class GUI extends JFrame {

   public static void main(String[] a) { new GUI(); }

   GUI() {

      JButton b = new JButton("Przycisk");

      getContentPane().add(b);

      pack(); 

      show();

   }

}

 

Klikając w ten przycisk (w tym programie) nie uzyskamy żadnych efektów (oprócz widocznej – zagwarantowanej przez domyślny look&feel – zmiany stanu przycisku: wyciśnięty – wciśnięty – wyciśnięty).

Co zrobić, by po kliknięciu w przycisk (lub naciśnięciu SPACJI, gdy przycisk ma fokus) uzyskać jakiś  użyteczny efekt? Choćby wyprowadzenie na konsolę napisu "Wystąpiło zdarzenie!".

Wiemy już, że kliknięcie lub naciśnięcie spacji na przycisku może wywołać zdarzenie  "akcja". Wywoła je, jeśli do przycisku przyłączymy słuchacza akcji.

Zgodnie z podanymi wcześniej regułami musimy zatem stworzyć obiekt – słuchacza akcji i przyłączyć go do przycisku. Klasa słuchacza akcji musi implementować interfejs nasłuchu akcji (ActionListener),  i w konsekwencji - zdefiniować jego jedyną metodę – actionPerformed. (zobaczcie w dokumentacji - jest!) W tej metodzie zawrzemy kod, który zostanie uruchomiony po kliknięciu w przycisk,  np. wyprowadzenie na konsolę napisu "Wystąpiło zdarzenie!".

Ideowy schemat rozwiązania naszego problemu przedstawia poniższy rysunek.

Interfejs nasłuchu



Interfejs ActionListener zawiera tylko deklarację:

        void actionPerformed(ActionEvent e);

;

Zdarzenie "akcja" -

obiekt klasy

ActionEvent



Klasa słuchacza



implementacja





Przycisk

[ Żródło ]

 

(zmienna b)

class Handler implements ActionListener {

 

  public void actionPerformed(ActionEvent e) {

     System.out.println("Wystąpiło zdarzenie!"); 

  }

}



 

 

 







call

 

 

Handler h = new Handler();

 

                   klik



 

Słuchacz akcji

 

Przyłączenie:

b.addActionListener(h);


 



 

Zgodnie z tym schematem zmodyfikujemy nasz program:

 



Konieczne dla obsługi zdarzeń AWT

(m.in. zdarzenia "akcja")

import java.awt.event.*;

import javax.swing.*;

 

class Handler implements ActionListener {

  public void actionPerformed(ActionEvent e)   {

     System.out.println("Wystąpiło zdarzenie!"); 

  }

}

 

class GUI extends JFrame {

   public static void main(String[] a) { new GUI(); }

   GUI() {

      JButton b = new JButton("Przycisk");

      Handler h = new Handler();

      b.addActionListener(h);

      getContentPane().add(b);

      pack(); 

      show();

   }

}

 

Dopiero teraz nasz przycisk będzie reagował na kliknięcia (lub wciśnięcie spacji) – w efekcie na konsoli uzyskamy komunikat "Wystąpiło zdarzenie!".

 

Pytanie: czy jest to jedyny sposób kodowania? Zdecydowanie nie. Przecież każda klasa może być klasą słuchacza zdarzeń (jeśli tylko implementuje odpowiedni interfejs).

Możemy zatem implementować interfejs ActionListener w klasie, definiującej okno naszej aplikacji:

 

class GUI extends JFrame implements ActionListener {

 

   public static void main(String[] a) { new GUI(); }

  

  GUI() {

      JButton b = new JButton("Przycisk");

      b.addActionListener(this);    // TEN obiekt będzie słuchaczem akcji

      getContentPane().add(b);

      pack(); 

      show();

   }

 

   public void actionPerformed(ActionEvent e)   {

     System.out.println("Wystąpiło zdarzenie!"); 

  }

 

}

Możemy także użyć anonimowych klas wewnętrznych (por. wyklad 1).

 

Anonimowe klasy wewnętrzne dla obsługi zdarzeń

 

Nic nie stoi na przeszkodzie, by implementację interfejsu nasłuchu umieścić w anonimowej klasie wewnętrznej.

 

//  konieczne importy: javax.swing.*; i java.awt.event.*;

class GUI extends JFrame {

 

Anonimowa

klasa

wewnętrzna

   ActionListener handler = new ActionListener() {



            public void actionPerformed(ActionEvent e)   {

                System.out.println("Wystąpiło zdarzenie!"); 

           }

   };

 

   public static void main(String[] a) { new GUI(): }

   GUI() {

      JButton b = new JButton("Przycisk");

      b.addActionListener(handler) {

      getContentPane().add(b);

      pack(); 

      show();

   }

}

 

Jest to rozwiązanie podobne do implementacji interfejsu w klasie GUI (bo mamy jeden obiekt, obsługujący zdarzenia, który możemy przyłączyć do różnych źródeł). W przypadku interfejsów z jedną metodą obsługi zdarzeń jest w zasadzie kwestią gustu wybór jednego z tych dwóch rozwiązań (dodajmy jednak, że anonimowa klasa wewnętrzna tworzy dodatkowy plik klasowy; implementując interfejs nasłuchu w nazwanej klasie możemy używać jej obiektów do obsługi zdarzeń w innych klasach, co nie jest możliwe przy anonimowej klasie wewnętrznej; za to kod anonimowej klasy wewnętrznej może być lepiej zlokalizowany pod względem czytelności dużego programu). Natomiast w przypadku interfejsów z wieloma metodami obsługi, spośród których interesuje nas tylko jedna lub dwie, implementacja interfejsu w klasie GUI obciąża nas koniecznością zdefiniowania pustych metod obsługi (metod obsługi tych zdarzeń, którymi nie jesteśmy zainteresowani). Wtedy lepszym rozwiązaniem może okazać się odziedziczenie odpowiedniego adaptera w anonimowej klasie wewnętrznej.

 

Prawie wszystkie interfejsy nasłuchu zdarzeń (za wyjątkiem niektórych z pakietu javax.swing.event) mają odpowiadające im adaptery.

 

Np. interfejs nasłuchu zdarzeń związanych z myszką deklaruje pięć metod:

 

public interface MouseListener extends EventListener {

public void mouseClicked(MouseEvent e);    // obsługa kliknięcia

public void mousePressed(MouseEvent e);    // obsługa wciśnięcia klawisza myszki

public void mouseReleased(MouseEvent e);  // obsługa zwolnienia klawisza myszki

public void mouseEntered(MouseEvent e);    // wejście kursora w obszar komponentu

public void mouseExited(MouseEvent e);      // wyjście kursora z obszaru komponentu

}

 

W klasie implementującej musimy zdefiniować wszystkie 5 metod, choć być może interesuje nas tylko obsługa wciśnięcia klawisza myszki (mousePressed).

 

Dla ułatwienia sobie życia możemy skorzystać z gotowego adaptera (klasa MouseAdapter), definiującego puste metod interfejsu, i przedefiniować tylko te które nas interesują:

 

class GUI extends JFrame {

 

   MouseListener handler = new MouseAdapter() {

            public void mousePressed(MouseEvent e)   {

                System.out.println("Myszka wciśnięta!"); 

           }

   };

 

   public static void main(String[] a) { new GUI(): }

   GUI() {

      JButton b = new JButton("Przycisk");

      b.addMouseListener(handler) {

      getContentPane().add(b);

      pack(); 

      show();

   }

}

 

 

Jeżeli chcemy mieć tylko jeden obiekt klasy słuchacza do obsługi jednego źródła zdarzenia to dobrym rozwiązaniem będzie  lokalna  anonimowa klasa wewnętrzna:

 

//  konieczne importy: javax.swing.*; i java.awt.event.*;

class GUI extends JFrame {

   public static void main(String[] a) { new GUI(): }

...

Zgłoś jeśli naruszono regulamin