Aplety

Java jest najczęściej kojarzona z narzędziem przeznaczonym do pisania specjalnych programów tzw. apletów, umieszczanych na stronach WWW. Do tej pory, struktura Javy omawiana była na przykładzie aplikacji. W tym rozdziale zajmiemy się strukturą i sposobem pisania apletów.

Aplety od aplikacji różni środowisko, w którym są wykonywane, struktura programu oraz sposób wykonania. W aplikacji działanie rozpoczyna się od metody main, natomiast aplet nie zaczyna działania od metody main lecz ma swoją strukturę przedstawioną na rysunku: Cykl życia Apletu.

Przykładowa prosta aplikacja w Javie wypisująca na ekranie słowa: "Hello World" ma postać:

class HelloWorldApp 
{
	public static void main(String[] args) 
	{
		//Wyświetla na ekanie string
		System.out.println("Hello World!"); 
	}
}

Natomiast aplet, który ma zrobić to samo, wygląda następująco:

import java.applet.Applet;
import java.awt.Graphics;
public class HelloWorld extends Applet 
{
	public void paint(Graphics g) 
	{
  	g.drawString("Hello world!", 50, 25);
	}
}

Umieszczenie apletu na stronie WWW wymaga dodania nowego znacznika (ang. tag) <APPLET> . . . </APPLET> .

Plik HTML z apletem Javy HelloWorld przyjmuje zatem postać:

<HTML><HEAD><TITLE> Przykładowy aplet </TITLE></HEAD>
 <BODY>Tutaj jest wynik działania mojego apletu:
 <APPLET CODE="HelloWorld.class" WIDTH=150 HEIGHT=25></APPLET>
 </BODY>
 </HTML>

gdzie znacznik <APPLET> ma m.in. następujące atrybuty:

Po załadowaniu strony HTML do przeglądarki Internetowej na załadowanej stronie zobaczymy wynik działania apletu:

Ilustracja 2-12 Wynik działania apletu HelloWorld.

Każdy aplet dziedziczy z klasy java.applet.Applet: posiada więc metody zdefiniowane w tej klasie i odziedziczone z nadklas klasy java.lang.Applet co zobrazowano na poniższym rysunku.

Rysunek 2-7 Hierarchia dziedziczenia klasy Applet

Cykl życia apletu

Do głównych metod odpowiedzialnych za przepływ sterowania podczas działania apletu należą: init, start, stop, destroy oraz metoda paint. W przykładowym aplecie, mamy redefinicję tylko jednej metody: paint (pozostałe są dziedziczone z klasy java.applet.Applet), która jest wykonywana za każdym razem, gdy zaistnieje potrzeba wykreślenia apletu (metoda paint, zostanie omówiona dalej w tym rozdziale).

Znaczenie pozostałych metod jest następujące:

Nie każdy aplet musi redefiniować (ang. override) którąś z tych metod. Dla przykładu, prosty aplet zdefiniowany wcześniej nie redefiniuje żadnej z tych metod, dlatego, że służy on tylko do rysowania na ekranie słów "Hello World!".

Metoda init powinna zawierać kod, który zazwyczaj znajduje się w konstruktorze. Jest tak dlatego, ponieważ w konstruktorze apletu nie jest zagwarantowane, że dostępne jest całe środowisko potrzebne do inicjalizacji apletu. Przykładowo, metoda ładująca obrazki nie jest dostępna w konstruktorze apletu.

Zazwyczaj aplet, musi redefiniować metodę start. Metoda start odpowiada za wykonanie pracy apletu, lub uruchamia wątki, które wykonują pracę apletu.

Większość apletów, które redefiniują metodę start powinny także redefiniować metodę stop. Metoda stop wstrzymuje wykonanie apletu, tak więc zasoby systemowe, zajęte przez aplet, nie są zwracane nawet, jeśli użytkownik nie ogląda właśnie strony zawierającej aplet. Przykładowo, aplet wyświetlający animację powinien w metodzie stop zatrzymać próby rysowania, gdy użytkownik ogląda już następną stronę.

Metody start i stop często używane są do tworzenia, uruchamiania oraz odpowiednio zatrzymywania i niszczenia wątków (patrz ).

Wiele apletów nie używa metody destroy, ponieważ metoda stop przeważnie wykonuje wszystkie operacje potrzebne do zakończenia działania apletu. Mimo tego, użycie metody destroy jest możliwe w przypadku apletów, które tego jednak wymagają. Stosując metodę destroy można, zwolnić zasoby zarezerwowane w metodzie init;

Rysunek 2-8 Cykl życia Apletu

Ilustracją cyklu życia apletu jest następujący program:

Przykład 2.28 Kod apletu AppletLifeCycle

import java.applet.Applet;
import java.awt.Graphics;
import java.awt.Font;

public class AppletLifeCycle extends Applet 
{
	private int m_nInit;
	private int m_nStart;
	private int m_nStop;
	private int m_nDestroy;
	private int m_nPaint;
	private Font font;
	
	public void init() 
	{
		font = new Font("Arial", Font.BOLD + Font.ITALIC, 24);
		m_nInit = dodajInfo(m_nInit);
	}
	public void start() 
	{
		m_nStart = dodajInfo(m_nStart);
	}
	public void stop() 
	{
		m_nStop = dodajInfo(m_nStop);
	}
	public void destroy() 
	{
		m_nDestroy = dodajInfo(m_nDestroy);
	}
	int dodajInfo(int i) 
	{
		repaint();
		return ++i;
 	}
	public void paint(Graphics g) 
	{
		g.setFont(font);
		//Rysowanie ramki otaczającej aplet
		g.drawRect(0, 0, size().width - 1, size().height - 1);
		//rysowanie odpowiednich łańcuchów znakowych (w ramce)
   		g.drawString("m_nInit    "	+	m_nInit,10,20);
		g.drawString("m_nStart   "	+	m_nStart,10,40);
		g.drawString("m_nStop    "	+	m_nStop,10,60);
		g.drawString("m_nDestroy "  +	m_nDestroy,10,80);
		g.drawString("m_nPaint   "  +	++m_nPaint,10,100);
 	}
}

Poniżej przedstawiono efekt przykładowego uruchomienia powyższego apletu w środowisku programu Netscape Communicator 4.0.

Ilustracja 2-13 Jeden z możliwych rezultatów wykonania apletu AppletLifeCycle

Metody odpowiadające za rysowanie w aplecie

Jak już wspomniano, za rysowanie odpowiedzialna jest metoda paint (np. aplet HelloWorld lub aplet AppletLifeCycle w poprzednim punkcie).

Oprócz metody paint możemy także redefiniować metodę update, która "czyści" kolorem tła obszar, na którym aplet rysuje a następnie inicjuje wykonanie metody paint.

Brak redefinicji update może spowodować niekiedy migotanie rysowanego w metodzie paint obszaru. Jednym ze sposobów wyeliminowania tego niepożądanego efektu jest redefinicja metody update tak, aby "czyściła" tylko te elementy, które mają być usunięte.

Spójrzmy na przykładowy aplet:

Ilustracja 2-14 Efekt działania apletu MojRysownik.

Po uruchomieniu apletu w przeglądarce widzimy przesuwające się napisy: "animowany aplet Javy" (wraz z kółkami) między lewą a prawą krawędzią apletu oraz "autor: ARTUR TYLOCH" między górną a dolną krawędzią.

W poniższym przykładzie metody update i paint zdefiniowano tak, aby ograniczyć migotanie. Metoda update "czyści" niepotrzebne elementy starego rysunku (a nie jak standardowo cały rysunek), a następnie wykreśla nowy rysunek. Ponieważ wszystkie operacje związane z wykreślaniem znajdują się w update, metoda paint zawiera tylko wywołanie metody update. Jest to potrzebne dla przypadków, gdy metoda paint jest wołana bezpośrednio przez bibliotekę AWT (np. gdy okno z apletem zostaje przesłonięte przez inne okno).

Klasa MojRysownik implementuje także interfejs Runnable co umożliwia, uruchomienie wątku, który będzie obliczał nowe współrzędne potrzebne przy rysowaniu animacji.

Przykład 2.29 Aplet MojRysownik

A oto kod HTML strony zawierającej aplet:

<html><head><title>MojRysownik</title></head>
<body><hr>
<applet
		code=MojRysownik.class
		name=MojRysownik
		width=300
		height=100 >
</applet>
<hr><a href="MojRysownik.java">The source.</a>
</body></html>

i kod zawartego na niej apletu:

import java.applet.*;
import java.awt.*;
public class MojRysownik extends Applet implements Runnable
{
	// współrzędne, na podstawie których wykreślane są napisy
	private int m_X = 60, m_Y = 50, m_oldX, m_oldY;
	// wątek obliczający dane do animacji
	private Thread animWatek;
	// czcionka w której wyprowadzane są na ekran napisy 
	private Font font;
	
	public void init()
	{
		// ustawienie czcionki (typ, rodzaj, wielkość) w której
		// będą wyświetlane napisy
		font = new Font("Arial", Font.BOLD, 20);
		m_oldX=m_X;
		m_oldY=m_Y;
	}
	// metoda start tworzy i uruchamia wątek, którego działanie 
	// definiuje metoda run
	public void start()
	{
		if (animWatek == null) 
			animWatek = new Thread(this,"Rysownik");
		animWatek.start();
	}
	// zatrzymanie i usunięcie wątku w przypadku przejścia do innej 
	// strony w przeglądarce
	public void stop()
	{
		if (animWatek != null)
			animWatek.stop();
		animWatek=null;
	}
	// metoda run, oblicza współrzędne potrzebne przy kreśleniu 
	// napisów  
	public void run()
	{
		// zmienna lewo określa czy współrzędne mają być obliczane dla
		// przesuwania napisów w lewo(oraz dół) czy w prawo(oraz góra) 
		boolean lewo = true;
		while (Thread.currentThread() == animWatek) 
		{
			if (m_X > 110) lewo = true;
			if (m_X < 10) lewo = false;
			if (lewo)
			{			
				m_X--;	m_Y--;
			}
			else
			{
				m_X++;	m_Y++;
			}
			// po obliczeniu nowych współrzędnych żądamy przerysowania
			repaint();
			try {
				Thread.sleep(100);
			} catch (InterruptedException e){
			}
		}
	}
	public void paint(Graphics g)
	{
		// wołamy update, ponieważ AWT niekiedy 
		// woła paint() bezpośrednio
		update(g); 
	}
	public void update(Graphics g) 
	{
		g.setFont(font);
		// jesli konieczne to usuwamy stary rysunek,
		// (opisany przez współrzędne m_oldX i m_oldY)
		// poprzez przemalowanie go kolorem tła
		if ((m_oldX != m_X)  | (m_oldY != m_Y))
		{
			g.setColor(getBackground() );
			g.drawString("animowany aplet Javy", m_oldX, 20);
			g.drawString("autor: ARTUR TYLOCH", 20, m_oldY);
			kolka(g, m_oldX);
			m_oldX = m_X;
			m_oldY = m_Y;
		}	
		
		// przerysowanie apletu
		g.setColor(Color.black);
		g.fillRect(10,50,280,25);
		g.setColor(Color.white);
		g.drawString("animowany aplet Javy", m_X, 20);
		g.setColor(Color.yellow);
		g.drawString("autor: ARTUR TYLOCH", 20, m_Y);
		g.setColor(Color.black);
		kolka(g, m_X);
	}
	
	private void kolka(Graphics g, int x)
	{
		for (int i = 0; i<7; i++ )
			g.drawOval(x+i*21,30,20,20);
	}
}

W celu całkowitego usunięcia nieprzyjemnego efektu migotania obrazu na ekranie możemy zastosować buforowanie. Buforowanie polega na rysowaniu do bufora a następnie wyświetleniu jego zawartości na ekranie.

Odpowiednio zmodyfikowany aplet MojRysownik zawiera następujące zmiany:

	// Pole danych typu Graphics służy do rysowania na obiekcie bufor 
	//(typu Image), jest to tzw. kontekst graficzny 
	// (ang. graphics context)
	private Graphics buforGraf;
	// Obiekt, na którym rysujemy 
	private Image bufor;
	// Pole danych rozmiarBuf przechowuje rozmiar bufora,
	// a pole d rozmiar obszaru rysowania na ekranie
	private Dimension rozmiarBuf, d; 
		d = this.size(); // rozmiar na którym aplet może rysować
	public void update(Graphics g) 
	{
		// zmienna d przechowuje rozmiar obszaru rysowania na ekranie;
		// jeśli nie ma bufora lub obszar rysowania uległ zmianie 
		// tworzymy nowy bufor 
		if ( (buforGraf == null)  || (d.width != rozmiarBuf.width)  || (d.height != rozmiarBuf.height) ) 
		{
			rozmiarBuf = d;
			bufor = createImage(d.width, d.height);
			// wynikiem metody getGraphics()jest referencja do kontekstu 
			// graficznego obiektu bufor typu Image.
			buforGraf = bufor.getGraphics();
		}  
		// ustawienie fontu, który będzie użyty do rysowania
		buforGraf.setFont(font);
		// ustawienie jako koloru bieżącego, koloru tła 
		buforGraf.setColor(getBackground());
		// przemalowanie bufora kolorem tła (wyczyszczenie tła)
		buforGraf.fillRect(0, 0, d.width, d.height);

		//przerysowanie
		buforGraf.setColor(Color.black);
		buforGraf.fillRect(10,50,280,25);
		buforGraf.setColor(Color.white);
		buforGraf.drawString("animacja z buforowaniem", m_X, 20);
		buforGraf.setColor(Color.yellow);
		buforGraf.drawString("autor: arturt tyloch", 40, m_Y);
		buforGraf.setColor(Color.black);
		kolka(buforGraf, m_X);

		//zrzucenie bufora na ekran
		g.drawImage(bufor, 0, 0, this);
	}

Pozostałe metody klasy MojRysownik pozostają bez zmian. Cały kod apletu znajduje się w dodatkach.

Zdarzenia

Pierwotnie, model obsługi zdarzeń w Javie (ang. Handle Event) zawarty w bibliotece AWT (Abstract Window Toolkit) bazował na pojęciu dziedziczenia klas. Ostatnio, w bibliotece AWT pakietu Sun'a JDK 1.1 (kwiecień 1997) wprowadzono nowy, tzw. delegacyjny model obsługi zdarzeń, charakteryzujący się większymi możliwościami i wydajnością.

W okresie przejściowym oba modele są dopuszczalne, a kompilator po wykryciu tradycyjnej obsługi zdarzeń generuje ostrzeżenie o używaniu zdezaktualizowanego modelu.

Tradycyjny model obsługi zdarzeń

Poniżej przedstawimy przykład apletu, w którym zaimplementowano tradycyjny model obsługi zdarzeń.

Aplet MojAplet posiada trzy przyciski służące, odpowiednio, do: zwiększania, zmniejszania i zerowania wyświetlanej przezeń liczby. Poniższa ilustracja przedstawia efekt działania apletu po uruchomieniu w programie appletviewer.exe (dołączanym do pakietów JDK firmy Sun).

Ilustracja 2-15 Wygląd apletu MojAplet

W celu rozmieszczenia komponentów graficznych (przycisków i etykiety) w aplecie użyto "zarządcy rozmieszczenia komponentów" (ang. Layout Manager) typu BorderLayout (użycie zarządców rozkładu komponentów omówione zostanie w punkcie 2.7.1.3).

Najważniejsza w tym przykładzie jest obsługa zdarzeń zdefiniowana w metodzie action.

Przykład 2.30 Obsługa zdarzeń w modelu tradycyjnym

import java.applet.*;
import java.awt.*;
public class MojAplet extends Applet
{
	static int	m_nWartosc=0;
	Label  lblWartosc = new Label("0", Label.CENTER);		
	Button btnPoprzednia = new Button("<< Odejmij"),
		 		btnNastepna = new Button("Dodaj >>"),
		 		btnZerowanie = new Button("Zerowanie");
	// wynikiem wykonania tej metody jest panel zawierający dwa
 	// przyciski o etykietach:
	// Odejmij i Dodaj, panel ten zostaje umieszczony u dołu apletu. 
	Panel gridLayoutPanel()
	{
		Panel pan = new Panel();
		// Jako zarządce rozmieszczenia komponentów w panelu ustawiamy
		// GridLayout o 1 wierszu i 2 kolumnach.
		pan.setLayout(new GridLayout(1,2));
		pan.add(btnPoprzednia);
		pan.add(btnNastepna); 	
		return pan;
	}
	
	public void init()
	{				
		Panel panel = gridLayoutPanel();
		setLayout(new BorderLayout());
		add("South",panel); 	
		add("North",btnZerowanie);
		add("Center",lblWartosc);		
		lblWartosc.setFont(new Font("Arial", Font.BOLD, 30));
	}
	// Obsługa zdarzeń evt generowanych przez użytkownika 
	// (naciśnięcie przycisku)
	public boolean action(Event evt, Object arg)
	{
		// Gdy naciśnięto przycisk btnNastępna
		if (evt.target == btnNastepna)
		{
			lblWartosc.setText(Integer.toString(++m_nWartosc));
			return true;
		}
		else if (evt.target == btnPoprzednia)
		{
			lblWartosc.setText(Integer.toString(--m_nWartosc));
			return true;
		}
		else if (evt.target == btnZerowanie)
		{
			m_nWartosc = 0;
			lblWartosc.setText(Integer.toString(m_nWartosc));
			return true;
		}
		return false;
	}
}

Tradycyjny model obsługi zdarzeń opiera się na dziedziczeniu, tzn. po wystąpieniu zdarzenia dla obiektu danej klasy jest ono albo obsługiwane przez metodę action (zdarzenia generowane przez użytkownika) lub handleEvent (zdarzenia generowane przez komponenty graficzne) tej klasy lub przekazane do nadklasy w celu obsłużenia.

Osoby zainteresowane tradycyjnym modelem obsługi zdarzeń odsyłamy do Tutoriala (dokument HTML) firmy Sun, dostępnego w Internecie.

Delegacyjny model obsługi zdarzeń

Nowy model zdarzeń, nazywamy delegacyjnym ponieważ do obsługi zdarzeń generowanych przez źródło, mogą być delegowane dowolne obiekty, które implementują odpowiedni interfejs nasłuchujący (ang. listener interface). Jeden lub kilka obiektów klas nasłuchujących może zostać zarejestrowany do obsługi różnego rodzaju zdarzeń pochodzących z różnych źródeł. Delegacyjny model zdarzeń pozwala zarówno na obsługę jak i na generowanie zdarzeń.

Spójrzmy jak wygląda klasa wykonująca te same zadania co, klasa z poprzedniego przykładu ale implementująca model delegacyjny.

Nowe elementy lub zmiany zaznaczono pogrubioną czcionką a mniej istotny, powtarzający się kod wykropkowano: "...", natomiast metodę action usunięto:

Przykład 2.31 Obsługa zdarzeń w modelu delegacyjnym

import java.applet.*;
import java.awt.*;
import java.awt.event.*;
public class MojAplet extends Applet implements ActionListener  
{
	...
	Panel gridLayoutPanel()
	{
	...
	}
	
	public void init()
	{				
		...
		// nowe elementy metody init:
		btnPoprzednia.addActionListener(this);
		btnNastepna.addActionListener(this);
		btnZerowanie.addActionListener(this);
	}
	public void	actionPerformed(ActionEvent evt)
	{
		if (evt.getSource() == btnNastepna)
			lblWartosc.setText(Integer.toString(++m_nWartosc));
		else if (evt.getSource() == btnPoprzednia)
			lblWartosc.setText(Integer.toString(--m_nWartosc));
		else if (evt.getSource() == btnZerowanie)
		{
			m_nWartosc = 0;
			lblWartosc.setText(Integer.toString(m_nWartosc));
		}
	}
}

Ogólne zasady programowania obsługi zdarzeń w modelu delegacyjnym przedstawiają się następująco:

1. Deklaracja klasy (przeznaczonej do obsługi) implementującej odpowiedni interfejs nasłuchujący (lub dziedziczącą z klasy, która to implementuje).

Przykład :

public class MojAplet extends Applet implements ActionListener

2. Rejestracja dla danego komponentu obiektu klasy nasłuchującej:

Ogólnie, napis:

źródłoZdarzeń.addRodzajListener(obiektKlasyNasłuchującej);

oznacza, że dla obsługi zdarzeń generowanych przez obiekt źródłoZdarzeń, zarejestrowano obiekt obiektKlasyNasłuchującej implementujący interfejs nasłuchujący RodzajListener.

W szczególności wywołanie:

btnPoprzednia.addActionListener(this);

jako obiekt klasy nasłuchującej rejestruje sam siebie. Jest to możliwe dzięki implementacji interfejsu nasłuchującego ActionListener przez klasę MojAplet.

3. Implementacja metod z interfejsu nasłuchującego.

Interfejs ActionListener posiada tylko jedną metodę do implementacji:

public void actionPerformed(ActionEvent evt)
{
	// deklaracja reakcji na zdarzenia
}

W modelu delegacyjnym mamy bogaty zestaw interfejsów nasłuchujących, metody każdego z tych interfejsów umożliwiają reakcję na zdarzenie określonego typu.

Zdarzenia, jakie mogą być generowane przez komponenty AWT 1.1 zestawiono w poniższej tabeli. Natomiast interfejsy nasłuchujące i klasy adaptacyjne przedstawiono w następnej tabeli.

  Typy zdarzeń jakie mogą generować
Komponenty AWT
action
adjustment
component
container
focus
item
key
mouse
mousemotion
text
window
Button
X
 
X
 
X
 
X
X
X
   
Canvas
   
X
 
X
 
X
X
X
   
Checkbox
   
X
 
X
X
X
X
X
   
CheckboxMenuItem*
**
       
X
         
Choice
   
X
 
X
X
X
X
X
   
Component
   
X
 
X
 
X
X
X
   
Container
   
X
X
X
 
X
X
X
   
Dialog
   
X
X
X
 
X
X
X
 
X
Frame
   
X
X
X
 
X
X
X
 
X
Label
   
X
 
X
 
X
X
X
   
List
X
 
X
 
X
X
X
X
X
   
MenuItem*
X
                   
Panel
   
X
X
X
 
X
X
X
   
Scrollbar
 
X
X
 
X
 
X
X
X
   
ScrollPanel
   
X
X
X
 
X
X
X
   
TextArea
   
X
 
X
 
X
X
X
X
 
TextComponent
   
X
 
X
 
X
X
X
X
 
TextField
X
 
X
 
X
 
X
X
X
X
 
Window
   
X
X
X
 
X
X
X
 
X
* Uwaga: To nie jest podklasa klasy Component
** CheckboxMenuItem dziedziczy addActionListener z MenuItem, ale nie generuje zdarzeń typu action.

Tabela 2-6 Zdarzenia generowane przez komponenty AWT.

Interfejs nasłuchujący Klasa adaptacyjna
Nazwa Metody  
ActionListener
actionPerformed(ActionEvent)
brak
AdjustmentListener
adjustmentValueChanged(AdjustmentEvent)
brak
ComponentListener
componentHidden(ComponentEvent)
componentMoved(ComponentEvent)
componentResized(ComponentEvent)
componentShown(ComponentEvent)
ComponentAdapter
ContainerListener
componentAdded(ContainerEvent)
componentRemoved(ContainerEvent)
ContainerAdapter
FocusListener
focusGained(FocusEvent)
focusLost(FocusEvent)
FocusAdapter
ItemListener
itemStateChanged(ItemEvent)
brak
KeyListener
keyPressed(KeyEvent)
keyReleased(KeyEvent)
keyTyped(KeyEvent)
KeyAdapter
MouseListener
mouseClicked(MouseEvent)
mouseEntered(MouseEvent)
mouseExited(MouseEvent)
mousePressed(MouseEvent)
mouseReleased(MouseEvent)
MouseAdapter
MouseMotionListener
mouseDragged(MouseEvent)
mouseMoved(MouseEvent)
MouseMotionAdapter
TextListener
textValueChanged(TextEvent)
brak
WindowListener
windowActivated(WindowEvent)
windowClosed(WindowEvent)
windowClosing(WindowEvent)
windowDeactivated(WindowEvent)
windowDeiconified(WindowEvent)
windowIconified(WindowEvent)
windowOpened(WindowEvent)
WindowAdapter

Tabela 2-7 Zestawienie interfejsów nasłuchujących i klas adaptacyjnych.

Do rejestracji komponentów, jako przeznaczonych do obsługi wybranych zdarzeń używamy metod rejestrujących. W poniższej tabeli zestawiono metody rejestrujące oraz komponenty, których dotyczą. Należy pamiętać, że dana metoda rejestrująca może być użyta dla klas przedstawionych w tabeli oraz klas, które z nich dziedziczą (np. metoda addWindowListener może być użyta także dla obiektów klasy Dialog lub Frame ponieważ są one rozszerzeniem klasy Window).

Metoda rejestrująca Komponent, którego dotyczą
addActionListener(ActionListener)
Button, List, TextField, MenuItem
addAdjustmentListener(AdjustmentListener)
Scrollbar
addComponentListener(ComponentListener)
Component
addContainerListener(ContainerListener)
Container
addFocusListener(FocusListener)
Component
addItemListener(ItemListener)
Choice, List, Checkbox, CheckboxMenuItem
addKeyListener(KeyListener)
Component
addMouseListener(MouseListener)
Component
addMouseMotionListener(MouseMotionListener)
Component
addTextListener(TextListener)
TextComponent
addWindowListener(WindowListener)
Window

Tabela 2-8 Wykaz metod rejestrujących

Jak już wspomniano, model delegacyjny umożliwia obsługę różnego rodzaju zdarzeń pochodzących z różnych źródeł. W poniższym aplecie mamy przykład obsługi zdarzeń pochodzących z różnych źródeł przez jedną klasę nasłuchującą (tu jest to jednocześnie klasa generująca zdarzenia):

(Uwaga: kod powtarzający się w odniesieniu do wcześniejszego przykładu wykropkowano)

Przykład 2.32 Obsługa wielu zdarzeń, przykład nr 2

import java.applet.*;
import java.awt.*;
import java.awt.event.*;
public class MojAplet extends Applet implements ActionListener  
{
	...
	//nowy komponent, pole tekstowe w którym będzie wyświtlana 
	// historia wszystkich wykonanych przez nas w aplecie operacji
	TextArea txtHistoria = new TextArea(5,20);

	Panel gridLayoutPanel()
	{	...	}

	public void init()
	{				
		...
		// nowe elementy metody init
		// dodajemy na "zachodzie" apletu pole tekstowe
		add("West",txtHistoria);		
		txtHistoria.setFont(new Font("Arial", Font.BOLD, 10));		
	}
	public void	actionPerformed(ActionEvent evt)
	{
		...
		// w tej metodzie dodajemy jedno polecenie, 
		// dzieki temu mamy w polu tekstowym txtHistoria wyświetle
		// wszystkie operacje przez nas wykonane 
		// (metoda getActionCommand)
		txtHistoria.append(evt.getActionCommand()+"  \t" +.Integer.toString(m_nWartosc)+ "\n");
 	}
}

Poniższa ilustracja przedstawia efekt działania powyższego apletu po uruchomieniu i wykonaniu kilku operacji dodawania, odejmowania i zerowania. Każde naciśnięcie dowolnego przycisku powoduje zmianę wyświetlanej liczby i dopisanie nazwy wykonanej operacji (wraz z aktualną wartością pola m_nWartosc) do pola txtHistoria.

Ilustracja 2-16 Aplet: MojAplet w działaniu.

Obsługa zdarzeń w klasie zewnętrznej

Obsługę zdarzeń można zdefiniować w oddzielnej klasie. Jest to użyteczne, gdy np. definiujemy złożony graficzny interfejs użytkownika (ang. Graphic User Interface, GUI) i możemy odseparować reakcję na zdarzenia, od kodu odpowiadającego za wygląd apletu. Zwiększa to czytelność programu. Przykład takiego programowania pokazano na poniższym listingu. (Jest to przekształcona klasa z poprzedniego przykładu)

Przykład 2.33 Obsługa zdarzeń w klasie zewnętrznej

import java.applet.*;
import java.awt.*;
import java.awt.event.*;
public class MojAplet extends Applet
{
	...
	Panel gridLayoutPanel()
	{
		...
	}
	
	public void init()
	{				
		...
		
		ZdarzeniaTxt zdarzeniaTxt = new ZdarzeniaTxt(txtHistoria, 
							m_nWartosc,	lblWartosc, 
							btnPoprzednia, btnNastepna, btnZerowanie);
		// Rejestracja obsługi zdarzeń:
		btnNastepna.addActionListener(zdarzeniaTxt);
		btnPoprzednia.addActionListener(zdarzeniaTxt);
		btnZerowanie.addActionListener(zdarzeniaTxt);
	}
}
// klasa zewnętrzna w której zdefiniowana jest obsługa zdarzeń 
// klasy MojAplet
class ZdarzeniaTxt implements ActionListener 
{
	int m_nWartosc;
	Label  lblWartosc;
	Button 	btnPoprzednia, 
		btnNastepna, 
		btnZerowanie;
	TextArea txtHistoria;

	public ZdarzeniaTxt(TextArea historia, int wartosc, Label label,
			Button poprz, Button nast, Button zerow) 
	{	
		m_nWartosc = wartosc;
		lblWartosc = label;
		btnPoprzednia = poprz;
		btnNastepna = nast;
		btnZerowanie = zerow;
		txtHistoria = historia;
	}
	public void actionPerformed(ActionEvent evt) 
	{
		if (evt.getSource() == btnNastepna)
			lblWartosc.setText(Integer.toString(++m_nWartosc));
		else if (evt.getSource() == btnPoprzednia)
			lblWartosc.setText(Integer.toString(--m_nWartosc));
		else if (evt.getSource() == btnZerowanie)
		{
			m_nWartosc = 0;
			lblWartosc.setText(Integer.toString(m_nWartosc));
		}
		txtHistoria.append(evt.getActionCommand()+" \t" + Integer.toString(m_nWartosc) + "\n");
	}
}

Obsługa zdarzeń w klasie wewnętrznej

Jak widać obsługa zdarzeń w klasie zewnętrznej wymaga przekazania referencji do wszystkich obsługiwanych komponentów i wykorzystywanych zmiennych. Aby tego uniknąć można zdefiniować obsługę zdarzeń w klasie wewnętrznej (klasy wewnętrzne dodano do Javy w kwietniu 1997 i można je stosować dla programów zgodnych z JDK 1.1).

Przykład 2.34 Obsługa zdarzeń w klasie wewnętrznej

import java.applet.*;
import java.awt.*;
import java.awt.event.*;

public class MojAplet extends Applet implements ActionListener  
{
	...
	Panel gridLayoutPanel()
	{
		...
	}
	
	public void init()
	{				
		...
		// Rejestracja obsługi zdarzeń
		ZdarzeniaTxt zdarzeniaTxt = new ZdarzeniaTxt();
		btnNastepna.addActionListener(zdarzeniaTxt);
		btnPoprzednia.addActionListener(zdarzeniaTxt);
		btnZerowanie.addActionListener(zdarzeniaTxt);
	}
	// klasa wewnętrzna w której zdefiniowano 
	// reakcję na zdarzenia w aplecie
	class ZdarzeniaTxt implements ActionListener 
	{
		public void actionPerformed(ActionEvent evt) 
		{
			if (evt.getSource() == btnNastepna)
				lblWartosc.setText(Integer.toString(++m_nWartosc));
			else if (evt.getSource() == btnPoprzednia)
				lblWartosc.setText(Integer.toString(--m_nWartosc));
			else if (evt.getSource() == btnZerowanie)
			{
				m_nWartosc = 0;
				lblWartosc.setText(Integer.toString(m_nWartosc));
			}
			txtHistoria.append(evt.getActionCommand() + "  \t"+Integer.toString(m_nWartosc)+ "\n");
		}
	}
}

Użycie klasy adaptacyjnej

W dotychczas przedstawionych przykładach implementowaliśmy interfejs ActionListener, posiadający tylko jedną metodę: actionPerformed. Dla wszystkich interfejsów, które posiadają więcej niż jedną metodę zdefiniowano klasy adaptacyjne. W klasach adaptacyjnych zdefiniowano wszystkie metody związanych z nimi interfejsów nasłuchujących.

Przykładowo, dla interfejsu nasłuchującego MouseListener, musimy zadeklarować wszystkie metody interfejsu, a klasa implementująca interfejs może przybrać postać:

// W tym przykładzie interesuje nas tyko obsługa zdarzenia
// mouseClicked, musimy jednak zaimplementować, zgodnie z zasadami
// implementacji interfejsów, wszystkie metody zadeklarowane
// w interfejsie MouseListener.
MojaKlasa implements MouseListener 
{
	...
		jakisObiekt.addMouseListener(this);
	...
	public void mouseClicked(MouseEvent e) 
	{
		...//Tutaj obsługa zdarzenia mouseClicked
	}
	public void mousePressed(MouseEvent e) 
	{ /* Pusta definicja metody */	}
	public void mouseReleased(MouseEvent e) 
	{ /* Pusta definicja metody */	}
	public void mouseEntered(MouseEvent e) 
	{ /* Pusta definicja metody */	}
	public void mouseExited(MouseEvent e) 
	{ /* Pusta definicja metody */	}
}

Natomiast zastosowanie klasy adaptacyjnej pozwala nam tę nadmiarowość kodu (deklaracje metod z pustym ciałem) usunąć. Aby użyć klasy adaptacyjnej, deklarujemy klasę jako podklasę klasy adaptacyjnej, zamiast deklaracji jej jako klasy implementującej interfejs nasłuchujący.

W poniższym przykładzie klasa wewnętrzna ObslugaMyszy jest podklasą klasy MouseAdapter i dzięki temu nie musimy implementować wszystkich metod interfejsu MouseListener a tylko jedną, która nas interesuje: MousePressed.

Przykład 2.35 Użycie klasy adaptacyjnej

import java.applet.*;
import java.awt.*;
import java.awt.event.*;
public class MojAplet extends Applet
{
	TextArea txtHistoria = new TextArea(5,20);
	ObslugaMyszy obslugaMyszy;
	Panel panel = new Panel();
	Label status = new Label();

	// klasa wewnętrzna ObslugaMyszy klasy MojAplet
	class ObslugaMyszy extends MouseAdapter implements MouseMotionListener
	{
		int oldX,oldY;
		Graphics KGpanel;
		ObslugaMyszy()
		{	KGpanel = panel.getGraphics();		}	
		// redefinicja metody z klasy MouseAdapter
		public void mousePressed(MouseEvent evt)
		{
			oldX=evt.getX();
			oldY=evt.getY();
			txtHistoria.append(evt.toString()+"\n");
		}
		// implementacje metod interfejsu MouseMotionListener
		public void mouseDragged(MouseEvent evt)
		{
			txtHistoria.append(evt.toString()+"\n");
			KGpanel.drawLine(oldX,oldY,evt.getX(),evt.getY());
		}
		public void mouseMoved(MouseEvent evt)
		{	status.setText(evt.toString());	}
	} // koniec definicji klasy wewnętrznej ObslugaMyszy
	public void start()
	{
		panel.setBackground(Color.yellow);
		setLayout(new BorderLayout());
		add("North",txtHistoria);		
		add("Center",panel);
		add("South",status);
		
		obslugaMyszy = new ObslugaMyszy();
		panel.addMouseListener(obslugaMyszy);
		panel.addMouseMotionListener(obslugaMyszy);		
	}
}

Po uruchomieniu apletu, rysowane są linie o początku w miejscu gdzie naciśnięto klawisz myszy i końcu znajdującym się na drodze kursora myszki (przy wciąż wciśniętym klawiszu). Jednocześnie, w polu tekstowym wyświetlane są informacje o zdarzeniach, jakie generowane są przez myszkę.

Ilustracja 2-17 Działanie apletu MojAplet z klasą adaptacyjną

Użycie klas anonimowych

W przypadku, gdy jesteśmy zainteresowani obsługą przez komponent tylko jednej rodziny zdarzeń, możemy użyć klasy anonimowej.

Poniższy przykład pokazuje łączne użycie klas adaptacyjnych i anonimowych. W przykładzie tym, dla klasy tworzymy klasę anonimową typu KeyAdapter i redefiniujemy jej metodę keyReleased (podobnie postępujemy z metodą mousePressed, klasy MouseAdapter).

Przykład 2.36 Wykorzystanie klas anonimowych

import java.applet.*;
import java.awt.*;
import java.awt.event.*;
public class MojApletKAnonim extends Applet
{
	// kontekst graficzny	 na którym rysujemy
	Graphics KG; 
	// pozycja myszki, gdzie ostatnio naciśnięto przycisk myszki
	int mouseX,mouseY; 
	TextField txtZrodlo;
	String tekst = "BLUM";
	public void start()
	{			
		setLayout(new BorderLayout());
		txtZrodlo = new TextField("BLUM");
		add("South",txtZrodlo);
		KG = getGraphics();
		txtZrodlo.addKeyListener(
			new KeyAdapter() 
			{
				public void keyReleased(KeyEvent evt)
				{
					tekst = txtZrodlo.getText();	
					KG.drawString(tekst,mouseX,mouseY);	
				}
			}
		);
		addMouseListener(
			new MouseAdapter() 
			{
				public void mousePressed(MouseEvent evt)
				{
					mouseX = evt.getX();
					mouseY = evt.getY(); 
					if (tekst != "")
					{
						tekst = txtZrodlo.getText();
						KG.drawString(tekst,mouseX,mouseY);
					}
					txtZrodlo.requestFocus();
				}
			}
		);		
	}
}

Ilustracja 2-18 Wynik działania apletu MojApletKAnonim

Obsługa zdarzeń w podklasie

W modelu delegacyjnym nie musimy deklarować podklasy komponentu, tak jak w modelu tradycyjnym, aby umożliwić obsługę zdarzeń dla tego komponentu. Deklarujemy tylko odpowiednią klasę nasłuchującą i rejestrujemy obiekt tej klasy, jako obsługujący zdarzenia pochodzące od danego komponentu.

W sytuacji jednak, gdy zachodzi potrzeba deklaracji podklasy np. komponentu Button możemy umożliwić reakcję w tej podklasie na zachodzące zdarzenia. W tym celu używamy metody enableEvents. Wywołanie metody enableEvents przyjmuje następującą postać:

obiekt.enableEvents(AWTEvent.MaskaZdarzeń);

gdzie obiekt jest referencją do komponentu, a MaskaZdarzeń określa rodzaj zdarzeń, których obsługa ma być dostępna dla komponentu.

Do dyspozycji mamy następujące maski zdarzeń:

ACTION_EVENT_MASK
ADJUSTMENT_EVENT_MASK
COMPONENT_EVENT_MASK
CONTAINER_EVENT_MASK
FOCUS_EVENT_MASK
ITEM_EVENT_MASK
KEY_EVENT_MASK
MOUSE_EVENT_MASK
MOUSE_MOTION_EVENT_MASK
TEXT_EVENT_MASK
WINDOW_EVENT_MASK

Gdy chcemy np. zdefiniowanemu przez nas komponentowi będącemu podklasą Button umożliwić obsługę zdarzeń typu ActionEvent, deklaracja klasy komponentu przyjmuje następującą postać:

class MojButton extends Button
{
	MojButton(String etykieta)
	{
		super(etykieta);
		// umożliwiamy obsługę zdarzeń typu ActionEvent 
		enableEvents(AWTEvent.ACTION_EVENT_MASK);
	}
	protected void processActionEvent(ActionEvent evt)
	{
		// tu deklarujemy obsługę zdarzeń typu ActionEvent 
		// generowanych przez nasz komponent
		// możemy także dodać super.processActionEvent(evt);
		// aby umożliwić domyślną obsługę zdarzeń tego typu
	}
}

Poniżej podano zestawienie metod, które można wykorzystać przy obsłudze zdarzeń w podklasie.

Metoda Komponenty których dotyczy
processEvent processComponentEvent processContainerEvent processFocusEvent processKeyEvent processMouseEvent processMouseMotionEvent wszystkie obiekty będące podklasą klasy Component
processActionEvent Button, List, TextField, MenuItem
processItemEvent Choice, List, Checkbox, CheckboxMenuItem*
processTextEvent TextComponent, TextField, TextArea
processAdjustmentEvent Scrollbar
processWindowEvent Dialog, Frame, Window
* To nie jest podklasa klasy Component

Tabela 2-9 Metody obsługujące zdarzenia komponentowe

Metody przedstawione w powyższej tabeli nie mogą być wykonane do czasu, aż nie spełniony zostanie jeden z warunków:

a) obiekt nasłuchujący zostanie zarejestrowany dla odpowiedniego typu zdarzeń, (poprzez użycie metody addTypZdarzeńListener() )

b) zdarzenia obsługiwane przez metodę staną się dostępne dzięki użyciu metody enableEvents() z odpowiednią maską zdarzeń.

Ograniczenia i możliwości apletów

Jednym z założeń Javy jest to aby użytkownik uruchamiając aplet w przeglądarce miał pewność, że jego system nie zostanie zaatakowany (przez wirusy lub próba nieuprawnionego dostępu do zasobów systemu). W tym celu nałożono na działanie apletu pewne ograniczenia związane z bezpieczeństwem. W procesie projektowania apletu należy te ograniczenia wziąć pod uwagę.

Każda przeglądarka zdolna do uruchamiania apletów Javy posiada obiekt zarządcy bezpieczeństwa (ang. SecurityManager), który sprawdza, czy aplet nie narusza zasad bezpieczeństwa. Gdy aplet naruszy zasady bezpieczeństwa, generowany jest przez zarządcę bezpieczeństwa wyjątek typu SecurityException. Wyjątek ten może być przechwycony przez aplet i może zostać podjęta próba "bezpieczniejszego" wykonania zadania.

Aplety muszą spełniać następujące zasady bezpieczeństwa (spełnienie tych zasad jest kontrolowane przez zarządców bezpieczeństwa poszczególnych przeglądarek).

nazwa właściwości znaczenie
"file.separator" Separator plików np. "/"
"java.class.version" Liczba oznaczająca wersję klasy Javy
"java.vendor" nazwa dostarczyciela Javy
"java.vendor.url" URL dostarczyciela Javy
"java.version" Wersja maszyny wirtualnej Javy
"line.separator" Separator linii
"os.arch" Architektura systemu operacyjnego
"os.name" Nazwa systemu operacyjnego
"path.separator" Separator ścieżki np. ":"

Tabela 2-10 Zestawienie właściwości systemu dostępnych do odczytu dla apletów

Okna stworzone przez aplet wyglądają inaczej niż te, które stworzyła aplikacja. Okno apletu ma u dołu tekst ostrzegawczy (tu brzmi on: "Java Applet Window") lub innego rodzaju informacje pozwalające odróżnić użytkownikowi okno apletu od okna aplikacji, do której może mieć pełne zaufanie.

Ilustracja 2-19 Okno utworzone przez aplet uruchomiony w przeglądarce Netscape Communicator 4.0.

Powyższe okno utworzone przez aplet na u dołu informacje o tym, iż jest to okno apletu. Okna utworzone przez aplikacje Javy nie zawierają takiej informacji.

Więcej informacji o bezpieczeństwie w apletach można znaleźć w pracy "Frequently Asked Questions - Applet Security", która jest dostępna w Internecie pod adresem: http://java.sun.com/sfaq/

Prócz ograniczeń związanych z bezpieczeństwem, aplety mają także dodatkowe możliwości, których nie posiadają aplikacje. Dodatkowe możliwości apletów wynikają z wykorzystywania przez nie kodu przeglądarki, w której są uruchamiane. Aplet ma dostęp do możliwości przeglądarki poprzez pakiet java.applet, który zawiera klasę Applet oraz interfejsy: AppletContext, AppletStub, i AudioClip

Dodatkowe możliwości apletów to:

Przykład wywołania metody drugiego apletu znajdującego się na stronie:

Zmienna nazwaDrugiegoApletu określa nazwę apletu, którego metodę chcemy wołać.

Applet drugi =  getAppletContext().getApplet(nazwaDrugiegoApletu);

Załóżmy, że klasa DrugiAplet definiująca ten aplet posiada metodę jakasMetoda(), to wywołanie tej metody przybiera postać:

((DrugiAplet)drugi).jakasMetoda(); 

W celu wywołania metody jakasMetoda() musimy przeprowadzić konwersję referencji typu Applet do typu DrugiAplet.

Wielowątkowość w apletach

Każdy aplet wykonywany jest w kilku wątkach. Metody apletu odpowiedzialne za rysowanie (paint i update) wołane są z wątku AWT obsługującego rysowanie i obsługę zdarzeń (ang. event handling). Metody, które stanowią szkielet apletu (tj. init, start, stop, destroy) wołane są z wątku zależnego od aplikacji, w której uruchomiony jest aplet.

Wiele przeglądarek internetowych dla każdego apletu znajdującego się na stronie przydziela oddzielny wątek służący do wołania metod stanowiących szkielet apletu. Aby ułatwić zabicie wszystkich wątków danego apletu, niektóre przeglądarki dla każdego apletu przydzielają jedną grupę wątków.

Gdy aplet np. podczas inicjalizacji (metoda init) ma np. wczytać obrazki, to nie może wykonać żadnych innych czynności, zanim nie zakończy swojej inicjalizacji. W takim przypadku należy takie czasochłonne czynności umieścić w oddzielnym wątku. Nasz aplet zostanie uruchomiony i może wykonać jakieś inne zadania (np. inny wątek odczyta czas z serwera - usługa DTime) w czasie oczekiwania na dokończenie operacji ładowania obrazków.

Aplety zazwyczaj wykonują dwa rodzaje czasochłonnych zadań: zadania wykonywane tylko raz (np. ładowanie obrazków, nawiązanie połączenia sieciowego) i zadania wykonywane w pętli (np. odgrywanie dźwięków w tle).

Spójrzmy, jak wygląda użycie zadeklarowanych przez nas, niezależnych wątków w aplecie. Aplet Linie po uruchomieniu tworzy dwa wątki typu Rysuj, które rysują losowo na wspólnym obiekcie graficznym (ang. canvas) linie. Linie rysowane przez każdy wątek Rysuj mają początek w punkcie zadeklarowanym przy deklaracji obiektu Rysuj a koniec linii wybierany losowo.

Przykład 2.37 Prosty aplet wielowątkowy Linie:

import java.util.*;
import java.awt.*;
import java.applet.*;
public class Linie extends Applet 
{
	// Deklaracja obiektów typu Thread, które będą sterowały wątkami 
	// obiektów typu Rysuj
	Thread watek1 = null;
	Thread watek2 = null;	
	//W metodzie start inicjalizowane są wątki typu Rysuj 
	public void start() 
	{
		try
		{
			// sprawdzamy czy wątek na pewno nie istnieje
			if(watek1 == null);
			{
				// Deklaracja i inicjalizaja obiektu typu Rysuj
				Rysuj rysuj = new Rysuj( this, 50, 50);
				// Przypisanie do zmennej typu Thread nowego wątku
				watek1 = new Thread(rysuj);
				// Uruchomienie wątku watek1
				watek1.start();
			}
			// dla watek2 wykonane zostaja te same czynności 
			// co dla watek1
			if(watek2 == null);
			{
				Rysuj rysuj = new Rysuj( this, 150, 50);
				watek2 = new Thread(rysuj);
				watek2.start();
			}
		}
		catch (Error e)
		{	e.printStackTrace(); }
	}
	// Metoda stop wykonywana jest gdy opuszczamy stronę, usuwamy więc 
	// uruchomione wątki aby nie zajmowały miejsca w pamięci 
	public void stop() 
	{
		watek1.stop();
		watek2.stop();
		watek1 = null;
		watek2 = null;
	}
}

I klasa Rysuj rysująca linie o wspólnym początku i losowo dobranym końcu. Ponieważ wielowątkowość w tej klasie zaimplementowano poprzez implementacje interfejsu Runnable, to musimy zadeklarować metodę run odpowiedzialną za działanie wątku.

class Rysuj implements Runnable
{
	// Pole statyczne m_nZarodek zadeklarowano w aby każdy nowo 
	// tworzony obiekt m_r typu Random generował inne liczby 
	// pseudo-losowe
	static int m_nZarodek = 0; 
	// Deklaracja i inicjalizacja obiektu rand typu Random 
	Random rand = new Random(m_nZarodek++);	
	// W zmiennej currentApplet przechowywana jest referencja 
	// do apletu w którym uruchomiony będzie bieżący wątek i dzięki 
	// temu będzie dostępne do rysowania urządzenie graficzne. 
	Applet currentApplet = null;
	int m_x,m_y;
	
	// Konstruktor klasy Rysuj
	Rysuj(Applet a, int x, int y)
	{
		currentApplet = a;
		m_x = x;
		m_y = y;
	}
	public void run()
	{
		while (true) 
		{
			// Usypiamy bieżący wątek aby inne mogły wykonać swoją pracę
			try {Thread.sleep(1000);} 
			catch (InterruptedException e){}
			// Główne zadanie wątku, narysowanie linii na obiekcie 
			// graficznym apletu.
			linia(currentApplet.getGraphics());
		}       
	}   
	public void linia(Graphics g) 
	{
		g.setColor(Color.blue);
		g.drawLine(m_x, m_y, m_r.nextInt(), rand.nextInt());     
	}
}

Po uruchomieniu apletu efekt działania będzie podobny do przedstawionego na poniższej ilustracji:

Ilustracja 2-20 Aplet Linie w akcji.