Aplety
Transkrypt
Aplety
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: • • CODE - określa nazwę pliku z kodem bajtowym apletu, WIDTH i HEIGHT- początkowa szerokość i wysokość okna na stronie WWW, w którym aplet będzie wykonywany, • CODEBASE - określa ścieŜkę do katalogu zawierającego plik z kodem bajtowym apletu, 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: • • • • init - wywoływana tylko raz, gdy strona WWW zawierająca aplet zostanie po raz pierwszy "załadowana", jeśli opuścimy stronę WWW zawierającą aplet i wrócimy na nią, metoda init nie będzie wykonana ponownie, start - metoda jest wykonywana za kaŜdym razem, gdy strona, na której znajduje się aplet, staje się stroną bieŜącą w przeglądarce, stop - metoda ta jest wykonywana za kaŜdym razem, gdy do przeglądarki ładowana jest następna strona WWW, destroy - wykonywana gdy aplet kończy swoje działanie. 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); olka(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: • w klasie MojRysownik definiujemy pola danych: // 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; • w ciele metody init() dodajemy: d = this.size(); // rozmiar na którym aplet moŜe rysować • główne zmiany dotyczą metody update, która teraz przybiera postać: 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. 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 tabeli (kliknij aby zobaczyć). Tabela 2-6 Zdarzenia generowane przez komponenty AWT. Natomiast interfejsy nasłuchujące i klasy adaptacyjne przedstawiono w następnej tabeli. 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) KeyAdapter keyTyped(KeyEvent) 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)+ " "); } } 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) + " "); } } 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)+ " "); } } } 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()+" "); } // implementacje metod interfejsu MouseMotionListener public void mouseDragged(MouseEvent evt) { txtHistoria.append(evt.toString()+" "); 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 wszystkie obiekty będące podklasą klasy processFocusEvent Component processKeyEvent processMouseEvent processMouseMotionEvent 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). • Aplety nie mogą ładować bibliotek lub definiować podprogramów (metod native). Aplety mogą uŜywać tylko swego własnego kody Javy oraz elementów Java API dostarczonych przez przeglądarkę. KaŜda przeglądarka apletów musi udostępnić Java API zdefiniowane w pakietach java.*. • • • • Aplety nie mogą w zwykły sposób czytać i zapisywać plików na komputerze, na którym są uruchomione. Aplety w przeglądarce mogą czytać pliki wyspecyfikowane przez URL, zamiast nazwy pliku. Aby zapisać dane na komputerze, z którego aplet załadowano (serwer) moŜna wysłać dane do aplikacji uruchomionej na serwerze, która moŜe swobodnie czytać i zapisywać dane na serwerze. Aplety nie mogą tworzyć połączeń sieciowych poza połączeniami z serwerem, z którego pochodzą. Nasz aplet moŜe jednak kontaktować się z innymi serwerami poprzez komunikację z aplikacją działającą na serwerze, z którego aplet załadowano. Aplikacja moŜe bowiem tworzyć swobodnie połączenia sieciowe. Aplet nie moŜe uruchamiać Ŝadnych programów na komputerze, na którym został uruchomiony. Tu takŜe rozwiązaniem moŜe być współpraca z aplikacją pracującą na serwerze. Aplet nie moŜe czytać wszystkich właściwości systemu (ang. system properities). Aplety mogą czytać tylko następujące właściwości systemu: 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: • • • aplety mogą odtwarzać dŹwięki, aplety uruchamiane w przeglądarce Internetowej mogą uŜyć jej do wyświetlenia dokumentów w HTML; jest to moŜliwe dzięki metodzie AppletContext.showDocument, aplety mogą takŜe wołać metody publiczne z innych apletów na tej samej stronie. 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. Źródło: Tutorial "Java - nowy standard programowania w Internecie" Artur Tyloch