Jan Bielecki
Java 3
Programowanie
współbieżne
obiektowe i zdarzeniowe
do Windows 95/98/NT
Profesorowi
Wojciechowi Cellaremu z wyrazami przyjaźni Spis ...
6 downloads
0 Views
Jan Bielecki
Java 3
Programowanie
współbieżne
obiektowe i zdarzeniowe
do Windows 95/98/NT
Profesorowi
Wojciechowi Cellaremu z wyrazami przyjaźni Spis treści Pierwsze kroki Projektowanie apletów Obsługiwanie zdarzeń Obsługiwanie myszki Obsługiwanie klawiatury Odtwarzanie pulpitu Dobieranie kolorów Wykreślanie napisów Wykreślanie obiektów Wykreślanie obrazów Odtwarzanie dźwięków Projektowanie oblicza Programowanie wątków Programowanie animacji Programowanie gier Grafika 2-wymiarowa Układ współrzędnych Definiowanie kształtu Wykreślanie linii Wypełnianie obszarów Przekształcanie obiektów Nakładanie kolorów Rozpoznawanie trafień Definiowanie obszarów Obcinanie wykreśleń Przekształcanie napisów Wykreślanie glifów Wykreślanie splajnów Dobieranie czcionek Lekkie komponenty Studium programowe Typy predefiniowane Deklaracje i instrukcje Wyrażenia i operatory Java po C++ Programowanie obiektowe Programowanie zdarzeniowe Projektowanie kolekcji Aplety i aplikacje Opisy apletów Klasy wewnętrzne Wykreślanie pulpitu Sytuacje wyjątkowe Obsługiwanie urządzeń Współrzędne pulpitu Wykreślanie figur Lokalizowanie zasobów Ładowanie obrazów Buforowanie wykreśleń Pakietowanie klas Udostępnianie zdarzeń Projektowanie zdarzeń Układanie komponentów Projektowanie oblicza Obsługiwanie okien Projektowanie menu Projektowanie dialogów Projektowanie przycisków Programowanie współbieżne Przetwarzanie plików Komponenty JavaBeans Programowanie wizualne Wykorzystywanie kostek Przechowywanie obiektów Czcionki i znaki narodowe Określanie daty i godziny Przezroczyste obrazy GIF Animowane obrazy GIF Rozbijanie plików GIF Przekształcanie obrazów Wyświetlanie komponentów Dekodowanie obrazów Używanie przyborników Wymiarowanie apletów Stosowanie modeli kolorów Interesujące przypadki Narzędzia pakietu JDK Studium programowe Dodatki A Priorytety operatorów B Definicje stałych C Klasa Debug D Symantec Visual Cafe E Borland JBuilder F Tek-Tools Kawa
Od Autora
Niepowstrzymany rozwój Javy powoduje, że wiele książek na jej temat było nieaktualnych już w chwili ich opublikowania. W minionym roku dezaktualizacja dosięgła także i moich tekstów: Java po C++ / Java od podstaw.
Obroniła się tylko Java 2, ponieważ w całości została oparta na delegacyjnym modelu obsługiwania zdarzeń. Ale i w niej znajdują się programy, które na skutek wyeliminowania takich metod jak stop, suspend, resume oraz kilkunastu innych, należałoby obecnie nieco zmodyfikować.
Niniejszą książkę napisałem w celu pokazania nowości, w tym nigdzie jeszcze nie opisanej grafiki 2D, dążąc do takiego wyłożenia współbieżności, aby mogła znaleźć zastosowanie w programowaniu wielowątkowych aplikacji animacyjnych. Pomogły mi w tym doświadczenia nabyte podczas nauczania Javy w Polsko-Japońskiej Wyższej Szkole Technik Komputerowych, w Instytucie Informatyki Politechniki Warszawskiej, w CITCOM oraz na szkoleniach organizowanych dla elit programistycznych wielkich firm. Jeśli podam, że w ubiegłym roku uczestniczyło w moich wykładach, projektach i laboratoriach ponad 400 informatyków, to zapewne lepiej niż słowa, ukaże to obecny stan zainteresowania Javą.
Aby uczynić książkę łatwiejszą od poprzednich, wyposażyłem ją w rozdział Pierwsze kroki. Metodą praktycznych przykładów pokazałem, jak niemal bez wiedzy podstawowej, można szybko przystąpić do układania całkiem niebanalnych programów, wykorzystujących techniki programowania współbieżnego, obiektowego, zdarzeniowego, graficznego i animacyjnego.
Nie mam wątpliwości, że każdy kto programował w języku obiektowym (np. w ANSI C++ albo w Delphi) oraz każdy kto poznał Javę w zakresie elementarnym, już po przeczytaniu kilku pierwszych rozdziałów nabierze umiejętności programowania profesjonalnego. Pozostałe rozdziały może już czytać w dowolnej kolejności, sięgając do nich po rozwiązania konkretnych problemów.
Życząc Czytelnikom pożytecznej lektury, z przyjemnością informuję, że uwzględniając apele o udostępnienie programów źródłowych, zamieszczam w Internecie, na stronie
www.ii.pw.edu.pl/~janb/java3/index.html
wszystkie zawarte w książce programy oraz wymagane przez nie pliki dźwiękowe i graficzne.
prof. Jan Bielecki Jan Bielecki
Pierwsze kroki
Programy dzielą się na aplikacje i aplety. Aplikacja jest programem wolnostojącym, a aplet jest programem wykonywanym pod nadzorem przeglądarki. W każdym z tych przypadków należy użyć Maszyny Wirtualnej, interpretującej B-kod programu powstałego po skompilowaniu programu źródłowego.
Uwaga: Maszyna Wirtualna może być implementowana w sprzęcie albo może być emulowana za pomocą rodzimego programu platformy.
Projektowanie apletów
Aplet jest programem zapisanym za pomocą publicznej klasy apletowej (np. Master). Klasa ta dziedziczy pola i metody z klasy Applet oraz uzupełnia je swoimi.
Jeśli w pewnym pliku występuje klasa publiczna Name, to nazwą pliku musi być Name.java. Stąd wynika, że w danym pliku może być zawarta co najwyżej jedna klasa publiczna. Jest nią zazwyczaj klasa apletowa.
W ciele klasy występują odwołania do klas bibliotecznych. Nazwa klasy bibliotecznej (np. Applet) jest poprzedzona nazwą pakietu, do którego należy ta klasa (np. java.applet).
Następujący program, pokazany na ekranie Pierwszy aplet, wykreśla koło o promieniu 30 pikseli, wpisane w domyślny prostokąt, którego lewy-górny wierzchołek znajduje się w punkcie (50, 50).
Uwaga: Współrzędne punktów są liczone względem lewego-górnego narożnika apletu.
Ekran Pierwszy aplet
### fapplet.gif
public
class Master extends java.applet.Applet { // klasa Master private int x = 50, // pola x, y, r, d y = 50, r = 30, d; public void init() // metoda init { d = 2 * r; } public void paint(java.awt.Graphics gDC) // metoda paint { // wybranie koloru gDC.setColor(java.awt.Color.red); // wykreślenie koła gDC.fillOval(x, y, d, d); // wybranie koloru gDC.setColor(java.awt.Color.black); // wykreślenie okręgu gDC.drawOval(x, y, d-1, d-1); }
}
Polecenia importu
Nazwy klas występujących w programie można uprościć do identyfikatorów (np. Applet, Graphics albo Color). Aby to umożliwić, należy użyć poleceń importu.
import java.applet.Applet;
import java.awt.*;
Dzięki poleceniu
import java.applet.Applet;
nazwę klasy java.applet.Applet można uprościć do Applet, a dzięki poleceniu
import java.awt.*;
odwołania do klas pakietu java.awt, których nazwy zaczynają się od java.awt można uprościć do identyfikatora kończącego taką nazwę (np. java.awt.Graphics można uprościć do Graphics).
Wykorzystano to w następującym aplecie, uproszczonym dzięki użyciu poleceń importu.
import java.applet.Applet;
import java.awt.*;
public
class Master extends Applet { private int x = 50, y = 50, r = 30, d; public void init() { d = 2 * r; } public void paint(Graphics gDC) { gDC.setColor(Color.red); gDC.fillOval(x, y, d, d); gDC.setColor(Color.black); gDC.drawOval(x, y, d-1, d-1); }
}
Opisy apletów
Pliki z rozszerzeniem .class, powstałe po po skompilowaniu klasy apletowej, są wykonywane przez Maszynę Wirtualną wbudowaną w przeglądarkę (np. Netscape 4.5) Aby Maszyna Wirtualna mogła wykonać B-kod zawarty w pliku *.class, należy przeglądarce dostarczyć opis apletu.
Opis apletu jest umieszczany w pliku z rozszerzeniem .html (np. Index.html). W opisie podaje się m.in. nazwę i rozmiary apletu: jego szerokość (width) i wysokość (height), wyrażone w pikselach.
Plik Index.htmlPo odnalezieniu B-kodu apletu przeglądarka tworzy obiekt apletowy, po czym na rzecz jego podobiektu klasy Applet, wywołuje metody init, start, paint, stop i destroy.
Jeśli w klasie apletowej dostarczy się metodę o takiej samej sygnaturze, jaką ma metoda klasy Applet, to zostanie wywołana metoda klasy apletowej, a nie metoda klasy Applet. Ta ważna właściwość języka umożliwia przedefiniowywanie w klasie pochodnej metod jej klasy bazowej.
Uwaga: Dwie metody mają taką samą sygnaturę, jeśli mają takie same nazwy, a ich nagłówki, pozbawione nazw parametrów i specyfikatorów (np. public, static, synchronized) są identyczne.
Metoda start jest wywoływana przed każdym pojawieniem się apletu, a metoda stop przed każdym jego zniknięciem. Przed pierwszym pojawieniem się apletu jest wywoływana metoda init, a po ostatnim metoda destroy. Metoda paint jest wywoływana wówczas, gdy należy odtworzyć pulpit apletu oraz po każdym wywołaniu metody start. Tuż przed tym, zniszczony fragment pulpitu jest czyszczony kolorem tła apletu.
Uwaga: Nawet gdy zniszczeniu ulega tylko fragment pulpitu, metodę paint należy zdefiniować w taki sposób, aby odtwarzała cały pulpit.
Następujący program, pokazany na ekranie Maskotka Javy, zawiera metodę init, przedefiniowującą metodę init klasy Applet. A zatem właśnie ona zostanie wywołana przez przeglądarkę. W taki sam sposób będzie potraktowane wywołanie przedefiniowanej metody paint.
Ekran Maskotka Javy
### mascot.gif
Uwaga: Ponieważ nie przedefiniowano metod start, stop i destroy, więc przeglądarka będzie wywoływać metody klasy Applet. Ponieważ ciała tych metod są puste, więc ich wykonanie nie będzie miało żadnego skutku.===============================================
import java.applet.Applet;
import java.awt.*;
public
class Master extends Applet { private Image img; public void init() { img = getImage(getDocumentBase(), "Duke.gif"); } public void paint(Graphics gDC) { gDC.drawImage(img, 50, 50, this); }
}
dla dociekliwych
Klasy programu można umieszczać w pakietach. Definicję klasy pakietowej należy poprzedzić deklaracją pakietu, a we frazie code opisu apletu należy uwzględnić nazwę pakietu.
Uwaga: Jeśli aplet nie jest wykonywany w środowisku uruchomieniowym, takim jak na przykład Kawa, to za pomocą parametru środowiska classpath albo parametru codebase, należy określić miejsce, skąd ma być ładowana jego klasa pakietowa.=====================================
package jbPackage; // pakiet jbPackage
import java.applet.Applet;
import java.awt.*;
public
class Master extends Applet { // klasa pakietowa public void paint(Graphics gDC) { // wykreślenie napisu gDC.rawString("Hello", 40, 40); }
}
Pulpit apletu
Prostokątny obszar zdefiniowany w opisie apletu jest jego pulpitem. Lewy-górny narożnik pulpitu ma współrzędne (0,0). Współrzędne x pulpitu rosną w prawo, a współrzędne y rosną do dołu. Aktualne rozmiary pulpitu otrzymuje się za pomocą metody getSize.
Dimension d = getSize();
int w = d.width, // szerokość h = d.height; // wysokość
Operacje na pulpicie wykonuje się za pomocą wykreślacza, wydając mu takie polecenia jak setColor (wybierz kolor), fillOval (wypełnij owal), drawOval (wykreśl owal), itp. Wykreślacz jest udostępniany przez parametr metody paint i update albo może być utworzony za pomocą metody getGraphics. Każde jej wywołanie dostarcza odnośnik do odrębnego wykreślacza.
Uwaga: Po zakończeniu korzystania z wykreślacza przydzielonego za pomocą metody getGraphics, należy go zwolnić za pomocą metody dispose. W przeciwnym razie, zwłaszcza gdy jest on przydzielany w pętli, może dojść do nadmiernej konsumpcji zasobów systemowych.
Graphics gDC = getGraphics(); // przydzielenie // wybierz kolor
gDC.setColor(Color.red); // wypełnij owal
gDC.fillOval(x, y, d, d); // wybierz kolor
gDC.setColor(Color.black); // wykreśl owal
gDC.drawOval(x, y, d-1, d-1);
gDC.dispose(); // zwolnienie
Obsługiwanie zdarzeń
Podczas wykonywania apletu mogą zachodzić zdarzenia. Obsługę zdarzenia deleguje się do obiektu nasłuchującego. Zrezygnowanie z delegowania powoduje, że zdarzenie jest ignorowane.
Delegowanie obsługi
Do delegowania obsługi zdarzeń służą metody addKindListener. Do obsługi przycisku służy metoda addActionListener, a do obsługi myszki i klawiatury metody addMouseListener, addMouseMotionListener i addKeyListener.
Argumentem metody addKindListener jest odnośnik do obiektu klasy implementującej interfejs KindListener. W takiej klasie, zgodnie z tabelą Metody obsługi, należy dostarczyć komplet metod zdefiniowanych w interfejsie KindListener.
Uwaga: Interfejsem jest klasa abstrakcyjna, która zawiera co najwyżej definicje stałych i deklaracje metod. Implementowanie interfejsu nie jest niczym innym jak wielo-dziedziczeniem takiej właśnie klasy. Jest to jedyna dozwolona forma wielodziedziczenia.
Tabela Metody obsługi
###
Delegacja: addActionListener
Interfejs: ActionListener Metoda Parametr actionPerformed ActionEvent
Delegacja: addMouseListener
Interfejs: MouseListener Metoda Parametr mousePressed MouseEvent mouseReleased MouseEvent mouseClicked MouseEvent mouseEntered MouseEvent mouseExited MouseEvent
Delegacja: addMouseMotionListener
Interfejs: MouseMotionListener Metoda Parametr mouseMoved MouseEvent mouseDragged MouseEvent
Delegacja: addKeyListener
Interfejs: KeyListener Metoda Parametr keyPressed KeyEvent keyReleased KeyEvent keyTyped KeyEvent
###
Uwaga: Zamiast implementować interfejs KindListener, można klasę obiektu nasłuchującego zdefiniować jako pochodną od pomocniczej klasy KindAdapter. W takiej klasie (nie ma jej dla interfejsu ActionListener) zdefiniowano wszystkie metody zadeklarowane w interfejsie KindListener.
Następujący program, pokazany na ekranie Obszar apletu, w zależności od tego czy kursor znajduje się nad pulpitem apletu czy poza nim, wykreśla czerwony okrąg na żółtym tle albo żółty okrąg na czerwonym tle.
Ekran Obszar apletu
### inside.gif===============================================
import java.applet.Applet;
import java.awt.*;
import java.awt.event.*;
public
class Master extends Applet { public void init() { // ustawienie koloru tła apletu setBackground(Color.white); // delegowanie obiektu nasłuchującego addMouseListener(new Watcher()); } class Watcher extends MouseAdapter { public void mouseEntered(MouseEvent evt) { setBackground(Color.yellow); } public void mouseExited(MouseEvent evt) { setBackground(Color.red); } } public void paint(Graphics gDC) { Color color = getBackground(); if(color.equals(Color.red)) gDC.setColor(Color.yellow); else gDC.setColor(Color.red); Dimension s = getSize(); int w = s.width, h = s.height; gDC.drawOval(0, 0, w-1, h-1); }
}
Obiekty zdarzeniowe
Po zajściu zdarzenia jest tworzony obiekt zdarzeniowy. W obiekcie zdarzeniowym jest zarejestrowany opis zdarzenia. Elementy opisu zależą od rodzaju zdarzenia.
Zdarzenie action
Zdarzenie zachodzi m.in. po kliknięciu przycisku (Button) oraz po naciśnięciu klawisza Enter podczas wprowadzania tekstu do klatki (TextField). Informacji o zdarzeniu dostarczają metody klasy ActionEvent.
Object getSource()
Dostarcza odnośnik do obiektu, w którym zaszło zdarzenie.
public void actionPerformed(ActionEvent evt)
{ Object obj = evt.getSource(); if(obj == startButton) // ... else if(obj == stopButton) // ...
}
String getActionCommand()
Dostarcza napis na przycisku albo zawartość klatki.
public void actionPerformed(ActionEvent evt)
{ String str = evt.getActionCommand(); // ...
}
Zdarzenie mouse, mouseMotion i key
Zdarzenia powstają po wykonaniu operacji za pomocą myszki albo klawiatury. Informacji o zdarzeniu dostarczają metody klasy InputEvent oraz dodatkowo: dla zdarzeń związanych z myszką - metody klasy MouseEvent, a dla zdarzeń związanych z klawiaturą - metody klasy KeyEvent.
long getWhen()
Dostarcza czas (w ms) jaki dzieli chwilę zajścia zdarzenia i początek epoki (1 stycznia 1970);
boolean isMetaDown() // prawy przycisk myszki
boolean isCtrlDown() // klawisz Ctrl
boolean isShiftDown() // klawisz Shift
boolean isAltDown() // klawisz Alt
Dostarcza informacji o naciśnięciu klawisza (lub ich kombinacji).
public void mousePressed(MouseEvent evt)
{ if(evt.isMetaDown() && evt.isShiftDown()) // ...
}
Tylko zdarzenia mouse i mouseMotion
Dodatkowych informacji dostarczają metody klasy MouseEvent.
int getX()
Dostarcza współrzędną x.
int getY()
Dostarcza współrzędną y.
Point getPoint()
Dostarcza punkt (x,y).
int getClickCount()
Dostarcza licznik wielo-kliknięcia (np. dla dwu-kliknięcia licznik 2).
public void mouseReleased(MouseEvent evt)
{ if(evt.isCtrlDown() && evt.getClickCount() == 2) // ...
}
Tylko zdarzenie key
Dodatkowych informacji dostarczają metody klasy KeyEvent.
Uwaga: Zdarzenie key może być obsłużone tylko przez komponent (aplet, przycisk, klatkę), na który jest nastawiony celownik. Do nastawienia celownika na komponent, w szczególności na aplet, służy metoda requestFocus.
int getKeyCode()
Dostarcza wirtualny kod klawisza. W metodzie keyTyped dostarcza VK_UNDEFINED.
char getKeyChar()
Dostarcza znak Unikodu. Jeśli w Unikodzie nie ma takiego znaku, to dostarcza VK_UNDEFINED.
Uwaga: Nazwy symboliczne klawiszy (np. VK_UNDEFINED, VK_SPACE) zdefiniowano w klasie KeyEvent.
public void keyReleased(KeyEvent evt)
{ int keyCode = evt.getKeyCode(); if(keyCode == KeyEvent.VK_F1) // ...
}
Obsługiwanie myszki
Następujący program, pokazany na ekranie Wykreślanie kół, ilustruje obsługę zdarzeń przez obiekty klasy wewnętrznej, zewnętrznej, anonimowej i apletowej.
Ekran Wykreślanie kół
### triplet.gif
Obsługa w klasie wewnętrznej===============================================
import java.applet.Applet;
import java.awt.*;
import java.awt.event.*;
public
class Master extends Applet { private int r = 30, d = 2*r; private Graphics gDC; // odnośnik do wykreślacza public void init() { // utworzenie wykreślacza gDC = getGraphics(); // utworzenie obiektu nasłuchującego Watcher mouseWatcher = new Watcher(); // oddelegowanie obiektu nasłuchującego // do obsłużenia zdarzenia mouse addMouseListener(mouseWatcher); } // klasa obiektów nasłuchujących class Watcher extends MouseAdapter { // metoda do obsłużenia zdarzenia mouse public void mouseReleased(MouseEvent evt) { // pobranie współrzędnych kursora myszki int x = evt.getX(), y = evt.getY(); // wykreślenie koła i okręgu gDC.setColor(Color.red); gDC.fillOval(x-r, y-r, d, d); gDC.setColor(Color.black); gDC.drawOval(x-r, y-r, d-1, d-1); } }
}
Obsługa w klasie zewnętrznej===============================================
import java.applet.Applet;
import java.awt.*;
import java.awt.event.*;
public
class Master extends Applet { private int r = 30; public void init() { // utworzenie obiektu nasłuchującego // dostarczenie mu wykreślacza i promienia Watcher mouseWatcher = ...