7. Programowanie GUI. Komponenty wizualne
Ten wykład rozpoczyna cykl poświęcony programowaniu graficznych interfejsów
użytkownika. Jest to nie tylko ważna i ogólna dziedzina (niewiele jest współcześnie
aplikacji, które nie mają graficznych interfejsów użytkownika), ale również
jest to jedna z najważniejszych składowych środowiska Javy, a przy tym doskonały
przykład zastosowania koncepcji programowania obiektowego (od polimorfizmu
poczynając, poprzez delegowanie uprawnień, po architekturę "Model-View-Controller").
7.1. Ogólne reguły działania z komponentami GUI.
Standardowe
pakiety java.awt (AWT) oraz javax.swing (Swing) zawierają klasy
definiujące wiele różnorodnych komponentów wizualnej
interakcji programu z użytkownikiem (okna, przyciski, listy, menus,
tablice itp.). Są gotowe do wykorzystania w naszych programach.
Można sformułowac następujące reguły dzialania z komponentami GUI.
Komponenty
tworzymy za pomocą wyrażenia new
wywołującego odpowiedni konstruktor klasy komponentu, któremu
podajemy argumenty, określające niektóre właściwości
komponentu (np. tekst/ikonę na przycisku).
Komponenty
mają właściwości (np. kolory, pismo tekstu na przycisku),
które możemy ustalać (lub pobierać) za pomocą metod z
odpowiednich klas komponentów (metody setNNN(...),
getNNN(...), isNNN(...), gdzie NNN nazwa właściwości).
Większość
właściwości komponentów jest reprezentowane przez obiekty
odpowiednich klas (np. pismo – klasa Font, kolor – klasa
Color)
Komponenty,
które mogą zawierać inne komponenty nazywają się kontenerami.
Do kontenerów
dodajemy inne komponenty (w tym inne kontenery)
Z
każdym kontenerem związany jest określony zarządca rozkładu,
który określa układ komponentów w kontenerze i ich
zachowanie (zmiany rozmiarów i położenia) przy zmianie
rozmiarów kontenera. Inaczej: rozkład jest jedną z
właściwości kontenera.
Zarządcy
rozkładu są
obiektami odpowiednich klas
Dla
każdego kontenera możemy ustalić wybranego zarządcę
rozkładu
Aplikacja
komunikuje się z użytkownikiem za pomocą okna lub
wielu okien
Okna AWT
są kontenerami, do których dodajemy komponenty wizualnej
interakcji z użytkownikiem (w tym inne kontenery)
Okna Swingu
zawierają tzw. contentPane, który jest
kontenerem do którego zwykle dodajemy komponenty wizualnej
interakcji (w tym inne kontenery).
Okna (w tym
"okno" apletu) są zawsze kontenerami najwyższego
poziomu w hierarchii zawierania się komponentów
Współdziałanie
użytkownika z aplikacją odbywa się na zasadzie obsługi zdarzeń
(np. zdarzenia kliknięcia w przycisk). Obsługą zdarzeń zarządza
specjalny, równolegle z naszym programem wykonujący się kod w
JVM – wątek obsługi zdarzeń. O obsłudze zdarzeń – w
następnym wykładzie.
Przykładowa aplikacja Swing (pokazuje trzy przyciski z obrazkami i tekstem).
import java.awt.*;
import javax.swing.*;
class Przyklad {
public static void main(String[] args) {
Icon[] icon = { new ImageIcon("ocean.jpg"), // ikony z plików JPEG
new ImageIcon("pool.jpg"),
new ImageIcon("town.jpg"),
};
String[] opis = { "Ocean", "Pool", "Town" }; // tekst na przyciskach
JFrame f = new JFrame(); // utworzenie okna ramowego
Container cp = f.getContentPane(); // ... i pobranie jego contentPane
cp.setLayout(new FlowLayout()); // ustalenie rozkładu FlowLayout
for (int i=0; i<icon.length; i++) {
// tworzenie kolejnych przycisków
JButton b = new JButton(opis[i], icon[i]);
// Ustalenie pisma i koloru napisu na przyciskach
b.setFont( new Font("Dialog", Font.BOLD | Font.ITALIC, 18));
b.setForeground(Color.blue);
// Ustalenie pozycji tekstu na przycisku względem ikony
b.setVerticalTextPosition(SwingConstants.BOTTOM);
b.setHorizontalTextPosition(SwingConstants.CENTER);
cp.add(b); // dodanie przycisku do contentPane
}
f.pack(); // spakowanie okna
// (wymiary okna takie by dokładnie zmieścić komponenty)
f.show(); // pokazanie okna
}
}
7.2. Komponenty AWT a komponenty Swingu
AWT (Abstract Windowing
Toolkit) – obecny w Javie od samego początku - jest zestawem klas, definiujących
proste komponenty wizualnej interakcji.
Problemy AWT:
- ubogie możliwości graficzne i interakcyjne komponentów,
- brak komponentów istotnych dla oprogramowania nowoczesnych GUI (np. tabel)
- zależny od platformy systemowej wygląd komponentów
Odpowiedzią na te problemy oraz ich rozwiązaniem był projekt Swing (ogólniej:
Java Foundation Classes - JFC), początkowo występujący jako dodatek do JDK
1.1.8, a później włączony w sklad Java 2 Platform.
Pakiet Swing (javax.swing i podpakiety) zawiera dużo więcej niż AWT komponentów
- nowych wobec AWT oraz mających rozbudowane właściwości odpowiedników AWT.
Wszystkie komponenty Swingu oprócz kontenerów znajdujących się najwyżej w
hierarchii zawierania się komponentów (kontenerów najwyższego poziomu) są
komponentami lekkimi. W przeciwieństwie – gotowe komponenty AWT są komponentami ciężkimi.
Komponenty ciężkie są realizowane poprzez użycie graficznych bibliotek GUI systemu operacyjnego.
Komponenty lekkie są natomiast rysowane za pomocą kodu Javy w obszarze
jakiegoś komponentu ciężkiego znajdującego się wyżej w hierarchii zawierania
się komponentów (zwykle jest to kontener najwyższego poziomu).
Z tego wynika, że – w przeciwieństwie do komponentów ciężkich:
- komponenty lekkie mogą być przezroczyste, a zatem mogą przybierać wizualnie dowolne kształty
- komponenty lekkie mają wygląd niezależny od platformy.
Lekkie komponenty Swingu spełniają oba te warunki, a architektura klas Swingu pozwala wybierać wygląd jego lekkich komponentów (pluggable look and feel).
Uwaga: możliwe jest umieszczanie w jednym kontenerze komponentów lekkich
(np. Swingu) i ciężkich (AWT), jednak jest to nie polecane i obarczone pewnymi
restrykcjami, dotyczącymi m.in. porządku nakładania się komponentów po osi
Z (Z-order). M.in. z tego ostatniego względu architektura okien Swingowych
jest złożona (o czym dalej) i standardowo powinniśmy dodawać komponenty Swingu
do contentPane okna ramowego Swingu (JFrame).
Podstawową hierarchię klas komponentów GUI przedstawia rysunek.
Wnioski, które wynikają z tej hierarchii :
podstawowe
właściwości wszystkich komponentów (i AWT i Swingu, i
komponentów terminalnych i kontenerów) określa klasa
Component z pakietu java.awt; w klasie tej znajdziemy mnóstwo
użytecznych metod do ustalania i pobierania właściwości dla każdego
z możliwych komponentów,
podstawowe
właściwości i użyteczne metody dla wszystkich kontenerów
określa klasa Container,
szczególne
dla lekkich komponentów Swingu, ale wspólne dla nich
wszystkich, właściwości określa klasa JComponent
specyficzne
właściwości i funkcjonalność poszczególnych rodzajów
komponentów zdefiniowane są w klasach tych komponentów
7.3. Podstawowe komponenty AWT i hierarchia klas komponentów AWT
Poniższe rysunki pokazują komponenty AWT oraz hierarchię ich klas.
Źródło: Java Tutorial.
Bardzo krótko omówimy wybrane komponenty AWT, bowien dalej skupimy się raczej na bardziej bogatych komponentach Swingu.
Przycisk (klasa Button)
może być tworziny za
pomocą konstruktora Button() lub – z podaniem tekstu na
przycisku – Button(String txt). Tekst można pobierać
(getLabel()) i zmieniac (setLabel(String)). Z przyciskiem można
kojarzyć napis (setActionCommand(String)), który łatwo można
pobrać w metodzie obsługującej akcję na przycisku (zdarzenie kliknięcia w przycisk).
Etykieta (klasa Label)
jest w zasadzie komponentem opisowym. Przedstawia tekst.
Tekst może być w obszarze etykiety wyrównywane do lewej, do
prawej bądź środkowany (odpowiednie argumenty konstruktora lub metody
setAlignment(...) – stałe o nazwach Label.LEFT, Label.RIGHT,
Label.CENTER).
Oczywiście
możemy pobierać (getText()) i ustalać(setText(String)) tekst
etykiety.
Lista
(klasa List ) jest
komponentem prezentującym listę elementów do wyboru.
Może
działać w trybie selekcji pojedyńczej (tylko jeden element na liście
może być zaznaczony) lub wieloselekcji (zaznaczonych może być wiele
elementów listy).
Podstawowe
konstruktory to:
List() // tworzy listę
List(int n)
// inicjalnie lista ma n wierszy
List(int n, boolean b) //
jesli b==true możliwa wieloselekcja
Podstawowe
metody klasy List
|
add(String s)
|
dodanie elementu s na koniec listy
|
add(String s, int i)
|
dodanie elementu s na pozycji i
|
int getRows()
|
na ile wierszy widoczna
|
int getItemCount()
|
ile ma elementów
|
String[] getItems()
|
pobierz wszystkie elementy
|
String getItem(int i)
|
co na pozycji i
|
int getSelectedIndex()
|
pozycja zaznaczonego
|
int[] getSelectedIndexes()
|
pozycje zaznaczonych
|
String getSelectedItem()
|
zaznaczony element
|
String[] getSelectedItems()
|
zaznaczone elementy
|
boolean isIndexSelected(int i)
|
czy element o indeksie i zaznaczony
|
boolean isMultipleMode()
|
czy w trybie wielozaznaczania
|
select(int i)
|
zaznacz element o indeksie i
|
deselect(int i)
|
usunąć
zaznaczenie elementu o indeksie i
|
remove(int i)
|
usunąć
element na pozycji i
|
removeAll()
|
usunąć
wszystkie elementy
|
setMultipleMode(boolean)
|
wielozaznaczanie czy nie
|
Najprostszy
przykład tworzenia listy i dodawania elementów może wyglądac
tak:
List l = new List();
for (int i = 1; i <= 10; i++)
l.add("Element listy " + i);
Podobna
do zwykłej listy jest lista rozwijalna. Składa się ona z tekstu
opatrzonego z prawej strony przyciskiem oraz listy, która
ukazuje się (rozwija) pod spodem pola tekstowego po kliknięciu w
przycisk..
Wybrany
z listy element pojawia się w polu tekstowym.
Lista
rozwijalna (niezbyt szczęśliwie) nazywa się w AWT Choice.
Metody
klasy Choice są podobne do metod klasy List: musimy tylko pamiętać,
że może być zaznaczony tylko jeden element i że pojawia się on jako
napis w polu tekstowym.
Lista
rozwijalna pozwala oszczędzać miejsce w GUI.
Kolejnym
komponentem jest przycisk-znacznik (klasa Checkbox).
Pozwala on zaznaczać opcje: zaznaczenie obrazowane jest "ptaszkiem"
z lewej strony tekstu przycisku. Kolejne kliknięcie w przycisk
zmienia jego stan na "niezazaczony" i "ptaszek"
znika.
Właściwości
znacznika: napis na przycisku, stan: zaznaczony-niezaznaczony mogą
być ustalane w konstruktorze oraz w metodach setLabel(String) i
setState(boolean) oraz pobierane odpowiednimi metodami getLabel() i getState().
Stany
przycisków-znaczników dodanych do GUI są niezależne od
siebie. Jeśli chcemy opisywać wyłączające się opcje (symulować
działanie tzw. radio-przycisków) to znaczniki powinny być
dodane do obiektu klasy CheckboxGroup.
Wtedy graficzne zaznaczenie zmieni się na kropeczkę i automatycznie
będzie spełniony warunek, że tylko jeden ze znaczników w
grupie może być zaznaczony.
Dwie
klasy reprezentujące w AWT komponenty tekstowe.
Klasa
TextField oznacza tekstowe pole edycyjne (jeden wiersz), a
klasa TextArea wielowierszowe pole edycyjne (niczym prosty
edytor tekstów).
Obie
te klasy pochodzą od klasy TextComponent, w której
zawarto większość metod
manipulowania na komponentach tekstowych. Nie możemy tworzyć obiektów
tej klasy, ale za to możemy korzystać z jej metod wobec obiektów
klas TextField i TextArea.
Podstawowe
metody klasy TextComponent
|
int getCaretPosition()
|
pozycja kursora
|
String getSelectedText()
|
zaznaczony tekst
|
int getSelectionEnd()
|
gdzie koniec zaznaczenia
|
getSelectionStart()
|
gdzie
początek zaznaczenia
|
getText()
|
cały tekst
|
isEditable()
|
czy edytowalny
|
setCaretPosition(int)
|
ustawia kursor na podanej pozycji
|
setEditable(boolean)
|
czy ma być
edytowalny
|
setSelectionEnd(int)
|
ustawia koniec zaznaczenia
|
setSelectionStart(int)
|
ustawia
początek zaznaczenia
|
setText(String)
|
ustla tekst |
Klas
TextField umożliwia
tworzenie pól edycyjnych o podanej liczbie widocznych kolumn
tekstu (konstruktor TextField(int)) i/lub zawiearających podany tekst
(TextField(String)).
Obsługuje
również właściwośc Columns:
int
getColumns() // ile widocznych kolumn tekstu
setColumns(int)
// ustal liczbę widocznych kolumn
W klasie TextArea
oprócz liczby kolumn możemy kontrolować liczbę wierszy, Poza
tym dostępna jest metoda dopisująca podany tekst na końcu tekstu już
zawartego w wielopolu edycyjnym :
append(String tekst) // dodaje
tekst na końcu .
7.4. Hierarchia klas komponentów Swingu. Krótkie omówienie wybranych komponentów.
Swing dostarcza wiele nowych komponentów, których nie było w AWT. Natomaist
te komponenty, które były w AT obecne (np. przyciski, czy listy) zyskały
nowe, rozbudowane możliwości, czasem bardzo różniące je od "starych" komponentów.
Dla efektywmnego posługiwania się komponentami wizualnymi użyteczna jest
znajomość ich hierarchii dziedziczenia. Poniższe rysunki przedstawiając te
hierarchie, stanowią informację, która być może w tej chwili nie będzie bezpośrednio
użyteczna, ale do której warto wracać przy analizowaniu działania konkretnych
komponentów.
Rys. Komponenty Swingu o rozbudowanych możliwosciach w stosunku do AWT
Żródło: Magellan Institute Swing Short Course.
Rysunek . Hierarchia klas komponentów Swingu, nie mających odpowiedników w AWT
Żródło: Magellan Institute Swing Short Course.
W poniższej tabeli przedstawiono przegląd komponentow Swingu z krótkimi
komentarzami co od ich właściwości. Konkretne kompoenenty będziemy poznawać
dokladnie w toku dalszego wykladu.
Komponenty terminalne Swingu |
|
Przyciski: klasy JButton, JToggleButton, JCheckBox, JRadioButton
Możliwości:
- tekst i/lub ikona na przycisku z dowolnym pozycjonowaniem
- różne ikony dla różnych stanów (wciśnięty, kursor myszki nad przyciskiem etc)
- ustalanie tekstu w HTML
- programistyczne symulowanie kliknięć (metoda doClick())
- ustalanie mnemoniki (metoda setMnemonic())
|
|
Etykieta: klasa JLabel
Możliwości:
- tekst i/lub ikona z dowolnym pozycjonowaniem
- tekst HTML
- ustalenie mnemoniki i związanie jej z innym komponentem np. polem edycyjnym
(wciśnięcie alt-mnemonika powoduje przejście fokusu do danego komponentu
np. pola edycyjnego)
|
|
Menu rozwijalne: klasy JMenu, JMenuItem, JCheckBoxMenuItem, JRadioMenuItem.
Ponieważ pochodzą od AbstractButton - wszystkie właściwości przycisków!
Menu kontekstowe: klasa JPopupMenu |
|
Suwak: klasa JSlider
Ustalanie wartości za pomocą suwaka. W pełni konfigurowalny, jako etykiety może zawierać ikony. |
|
Dialog wyboru koloru: JColorChooser
łatwy w użyciu w wersji standardowej. W pełni konfigurowalny - możliwość
tworzenia wersji niestandardowych i wbudowywania ich w inne kompoennetu GUI.
Inny dialog wyboru - JFilaChooser - wybór plików. |
|
Pole edycyjne: JTextField
do wprowadzania hasła: JPasswordField
weryfikacja tekstu: za pomocą dziedziczenia klasy abstrakcyjnej InputVerifier i zdefiniowania metody verify(Component).
W JDK 1.4 - nowa klasa JFormattedTextField - z wbudowaną weryfikacją tekstu: |
|
Wielowierszowe pole edycyjne: JTextArea
Uwaga: wszystkie komponenty tekstowe pochodzą od klasy JTextComponent,
która zapewnia bardzo elastyczne możliwości tworzenia różnych edytorów. Komponenty
tekstowe są bardzo rozbudowane, jesli chodzi o architekturę. Procesory dokumentów:. JEditorPane i JTextPane |
|
Lista: klasa JList
- oparta na współpracy modelu danych listy z widokiem tych danych
- elementy: teksty i/lub obrazki, a nawet inne komponenty GUI (wygląd)
- rózne elementy listy mogą mieć różny wygląd (kolor, pismo, obrazek lub nie etc).
|
|
Lista rozwijalna: JComboBox
oszczędność miejsca w GUI
te same właściwości co lista + możliwość przechodzenia do elementu tekstowego
po wciśnięciu pierwszej litery napisu, który on reprezentuje |
|
Tabela: klasa JTable
Ogromne możliwości konfiguracyjne, przestawianie kolumn (myszką i programistycznie),
różny wygląd kolumn (teksty, obrazki, komponenty interakcyjne), sortowanie
wierszy, wielozaznaczanie (po wierszach i po kolumnach) |
|
Drzewo: klasa JTree
Reprezentowanie hierarchii. Węzły drzewa mają te same właściwości co
elementy tabeli (tzn. mogą być reprezentowane w postaci napisów i/lub ikon
oraz innych komponentów) |
Lekkie i wyspecjalizowane kontenery Swingu
|
|
Panel: klasa JPanel
służy do grupowania komponentów |
|
Panel dzielony: klasa JSplitPane
podział kontenera na dwie części (poziomo lub pionowo) z możliwością
przesuwania belki podziału dla ustalenia rozmiarów widoków części |
|
Panel zakładkowy: JTabbedPane
zakładki służą do wybierania komponentów, które mają być uwidocznione w panelu |
|
Panel przewijany: JScrollPane
służy do pokazywania komponentów, które nie mieszczą się w polu widoku;
suwaki umożliwiają "przewijanie" widoku komponentu, tak, by odsłaniać kolejne
jego fragmenty.
JTextArea i JList powinny być umieszczane w JScrollPane, jeśli ich zawartość
(wiersze tekstu, elementy listy) może się powiększać. |
|
Pasek narzędzi: JToolBar |
Źródło rysunków: Swing Connection, Sun.
Uwaga: okna (w tym wewnętrzne) zostaną omówione oddzielnie.
7.5. Wspólne właściwości komponentów (AWT i Swing)
Wszystkie
komponenty wywodzą się z abstrakcyjnej klasy Component, która
definiuje metody, m.in. ustalające właściwości komponentów.
Mamy dwa rodzaje komponentów: komponenty-kontenery (takie,
które mogą zawierać inne komponenty) oraz komponenty
terminalne (nie mogą zawierać innych komponentów).
Właściwości
mogą być pobierane za pomocą metod getNNN()
lub (dla właściwości zero-jedynkowych, typu boolean) isNNN()
i ustalane (jeśli to możliwe) za pomocą metod setNNN(...).,
gdzie NNN – nazwa
własciwości.
Do
najogólniejszych właściwosci wszystkich komponentów
należą.
Właściwość | Komentarz |
rozmiar (Size)
|
ustalanie i
pobieranie tych właściwości ma ograniczone zastosowanie (zob.
dalej)
|
szerokość
(Width) |
wysokość
(Height) |
położenie
(Location) |
rozmiar i
położenie (Bounds) |
minimalny rozmiar (MinimumSize) |
Właściwości
ważne dla niektórych zarządców rozkładu (zob.
dalej). Mogą być ustalane tylko dla komponentów Swingu. W
AWT – ustalanie poprzez zdefiniowanie metod get... w klasie
dziedziczącej klasę komponentu standardowego.
|
preferowany rozmiar (PreferredSize) |
mksymalny rozmiar (MaximumSize) |
wyrównanie po osi X (AlignmentX) | określa
położenie komponentów wobec innych. Uwagi j.w.
|
wyrównanie po osi Y (AlignmentY) |
pismo (Font) | pismo jest obiektem klasy Font |
kolor tła
(Background) | kolor jest obiektem
klasy Color kolor tła
ma znaczenie tylko dla nieprzezroczystych komponentow
|
kolor pierwszego planu (Foreground) |
rodzic (Parent) | kontener, który
zawiera dany komponent. Właściwość
tylko do odczytu. |
nazwa (Name) | Komponenty
otrzymują domyślne nazwy. Można je zmieniać. |
Właściwości
typu 0-1 |
widzialność
(Visible) | czy
widoczny? można zmieniać |
lekkość
(LigthWeight) | czy komponent lekki? |
przezrosczystość
(Opaque) | zmiany tylko dla komponentów Swingu |
dostępność
(Enabled). | czy możliwa
interakcja z komponentem? |
Rozmiary
i położenie komponentów nie są znane dopóki
komponenty nie zostaną zrealizowane (uwidocznione lub okno w którym
się znajdują nie zostanie spakowane).
Położenie
komponentu określane jest przez współrzędne (x,y), a punkt
(0,0) oznacza lewy górny róg obszaru w którym
znajduje się komponent (jest to ten obszaru kontenera, do którego
mogą być dodane komponenty, a więc np. za wyłączeniem paska menu w
oknie). Zmiana położenia i rozmiarów za pomocą metod set
ma w zasadzie sens tylko dla komponentów znajdujących się w
kontenerach bez zarządcy rozkładu (o zarządcach rozkładu powiemy za
chwilę) lub dla komponentów-okien.
Aby
ustalić rozmiar okna frame piszemy np.:
frame.setSize(200,
200);
Szczególna
metoda w klasie Window (dziedziczonej przez Frame i JFrame) –
pack() pozwala ustalić rozmiary okna, tak by były dokladnie takie (i
nie większe) żeby zmieścić wszystkie znajdujące się w nim komponenty:
frame.pack();
|
Pismo
jest obiektem klasy Font, tworzonym za pomocą konstruktora
Font(nazwa_pisma, styl, rozmiar)
gdzie:
nazwa_pisma - jest łańcuchem znakowym, określającym rodzaj pisma
(np. "Dialog")
styl
- jest jedną ze stałych statycznych typu int z klasy Font:
Font.BOLD
Font.ITALIC
Font.PLAIN
(kombinacje uzyskujemy poprzez sumę logiczną np. Font.BOLD |
Font.ITALIC)
rozmiar
- liczba całkowita określająca rozmiar pisma w punktach.
Podstawowe, logiczne, nazwy pisma to: Serif, SansSerif, Dialog i MonoSpaced.
Zatem,
aby np. dla przycisku b ustalić pismo, piszemy
JButton b = new
JButton("Tekst na przycisku");
b.setFont(new
Font("Dialog", Font.PLAIN, 14);
Kolor
jest obiektem klasy Color, która ma kilka konstruktorów
oraz udostępnia stałe statyczne typu Color z predefiniowanymi
kolorami, np. Color.red, Color.blue, Color.white...
Kolory
przycisku możemy więc ustalić za pomocą takich konstrukcji:
b.setBackground(Color.blue);
b.setForeground(Color.white);
albo:
int r, g,
b;
r = 200; g = 200; b = 255;
b.setBackground(new
Color(r,g,b));
|
Zablokowanie/odblokowanie
komponentu
Komponenty
gotowe do interakcji są odblokowane (enabled). Zablokowanie
komponentu uniemożliwia interakcję z nim.
Np. jeśli b jest
przyciskiem
b.setEnabled(false);
// zablokowanie; kliknięcia w przycisk nie będą "przyjmowane"
//
odblokowanie:
if
(!b.isEnabled()) b.setEnabled(true);
Uwidacznianie
komponentów:
Wszystkie
komponenty, oprócz tych wywodzących się z klasy Window, są
domyślnie widoczne. Pojawiają się one na ekranie, gdy zostały dodane
do jakiegoś kontenera i kontener jest/został uwidoczniony.
W
trakcie działanie programu można czynić komponenty niewidocznymi i
przywracać ich widzialność, np:
JButton
b = new JButton(...);
....
b.setVisible(false); // stanie się
niewidoczny
...
if (!b.isVisible()) b.setVisible(true);
// gdy niewidoczny, uwidaczniamy
Przezroczystość
komponentów
Wszystkie
komponenty AWT (jako ciężkie) są nieprzezroczyste (isOpaque() zwróci
true).
Komponenty
lekkie mogą być przezroczyste lub nie.
Domyślnie
większość lekkich komponentów Swingu jest
nieprzezroczysta.
Wyjątkiem
jest etykieta JLabel.
Zatem
aby ustalić tło etykiety Swingu musimy napisać:
JLabel
l = new JLabel("Jakaś etykieta");
l.setOpaque(true);
l.setBackground(Color.yellow);
7.6 Własne komponenty i rysowanie
Warto zastanowić się nad tym, w jaki sposób komponenty pojawiają się na ekranie?
Otóż odpowiada za to metoda paint. To ona "maluje" komponenty na ekranie,
Klasa każdego komponentu zawiera definicję metody public void paint(Graphics).
Metoda ta jest zdefiniowana w klasie Component (od której pochodzą wszystkie
komponenty). W klasach konkretnych komponentów AWT jest ona przedefiniowana.
W przypadku komponentów Swingu - metoda jest przedefiniowana w klasie JComponent,
a z jej wnętrza wywoływane są polimorficznie inne metody, odpowiedzialne
za rysowanie
Metoda paint(..) jest wywoływana przez JVM (na zasadzie callback, czyli "jestem i czekam, aż ktoś mnie wywoła" ) zawsze wtedy, gdy graficzny kontekst komponentu wymaga odświeżenia tj:
- komponent staje sie widoczny na ekranie,
- zmieniają się rozmiary komponentu,
- coś innego zasłoniło komponent, a potem został odsłonięty.
W metodzie paint(...) dostarczany jest kod, który powoduje wyrysowanie komponentu.
Ten kod będzie wywołany przez system - gdy trzeba odświeżyć komponent.
Wykreślanie ciężkich i lekkich komponentów różni się nie tylko pod względem
odwołań do natywnego systemu graficznego (ciężkie się odwołują, lekkie –
rysują bezpośrednio w obszarze "pożyczonym" od ciężkiego kontenera z wyższego
poziomu hierarchii ), ale również gdy chodzi o wewnętrzne mechanizmy wykreślania
(o czym więcej w przyszłym semestrze w wykładzie o zaawansowanej grafice).
Lekkie komponenty Swingu definiują, oprócz metody paint(...), trzy inne metody:
protected void paintComponent(Graphics g) // wykreśla sam komponent
protected void paintBorder(Graphics g) // wykreśla ramkę komponentu (jeśli jest)
protected void paintChildren(Graphics g) // wykreśla hierarchię zawartych komponentów
Są one wywoływane "z wnętrza" metody paint().
Ostatnia z trzech metod wymaga komentarz: jak można zauważyć ze schematu
dziedziczenia klas, wszystkie lekkie komponenty Swingu są kontenerami. Dlatego
dla każdego definiowana jest metoda paintChildren(...), wykreślająca komponenty
zawarte w odrysowywanym komponencie.
Metody paint(...) nie wolno wołać z poziomu aplikacji.
Jeśli istnieje konieczność odrysowania komponentu przez aplikację, to należy użyć metody repaint(...).
Opisany skrótowo mechanizm pozwala na wykonywanie własnych rysunków na gotowych komponentach.
Aby przedefiniować sposób wykreślania komponentów dziedziczymy ich klasy i przedefiniowujemy:
- w AWT metodę public void paint(Graphics),
- a w Swingu – metodę public void paintComponent(Graphics).
Ten sposób jest użyteczny szczególnie w odniesieniu do "upiększania" gotowych
komponentów AWT. Chociaż możemy go zastosować wobec komponentów Swingu, to
istnieją lepsze podejścia związane z wykorzystaniem mechanizmów pluggable
lookk & feel (o czym parę słów powiemy w wykładzie 11).
W istocie, dziedziczac klasy komponentów i przedefiniowując metody paint... tworzymy własne komponenty.
Te własne komponenty możemy budować całkiem od podstaw (wykorzystując "minimalne"
komponenty, np. JComponent lub JPanel), albo też możemy skorzystać z gotowej
funkcjonalności bardziej rozbudowanych komponentów (np. JButton), dziedzicząc
ich klasy.
Niewątpliwie najczęściej będziemy wykorzystywać opisany sposób do prezentowania
jakichś rysunków. Dlatego musimy wiedzieć w jaki sposób rysować podstawowe
kszałty, napisy oraz wykreślać obrazy z plików graficznych.
Kluczem jest tu klasa Graphics. Referencja do obiektu tej klasy stanowi parametr metod paint...
Obiekt ten określa kontekst graficzny komponentu. Cóż to takiego?
Kontekst graficzny jest swoistym logicznym "urządzeniem
wyjściowym". Zwykle jest to ekran komputera, ale może to być np. wydruk
lub bufor w pamięci. Logiczny kontekst graficzny może więc być związany z
różnymi "urządzeniami wyjściowymi" na których "rysowany" jest komponent.
W klasie Graphics zdefiniowano wiele metod umożliwiających m.in.:
- uzyskiwanie i ustalanie właściwości kontekstu graficznego,
- rysowanie linii i figur,
- wypisywanie tekstów,
- rysowanie obrazów (obiektów klasy Image).
I właśnie dzięki temu mamy pełną kontrolę nad tym co ma być rysowane w obszarze naszego komponentu.
M.in. dostępne są następujące metody (proszę przejrzeć zestaw metod klasy Graphics w dokumentacji):
void | drawLine(int x1,
int y1,
int x2,
int y2)
Rysuje linię prostą pomiędzy punktami
(x1, y1) i (x2, y2) |
void | drawOval(int x,
int y,
int width,
int height)
Rysuje okrąg (elipsę). |
void | drawRect(int x,
int y,
int width,
int height)
Rysuje prostokąt |
Analogiczne metody fill... pozwalają na rysowanie wypełnionych kształtów (figur).
Rysowanie i wypełnianie odbywa się bieżącym kolorem kontekstu graficznego,
który jest domyślnie kolorem pierwszego planu (Foreground) komponentu, ale
może być dla potrzeb każdego rysunku zmieniany za pomocą metody setColor(Color)
z klasy Graphics. Metoda getColor() zwraca bieżący (ustalony) kolor.
W metodach rysowania, wypisywania, malowania obrazów posługujemy się współrzędnymi.
Przy rysowaniu figur należy pamiętać o tym, że górny lewy róg komponentu
ma współrzędne (0, 0). Zwiększanie wspólrzędnych następuje w prawo i w dół.
Współrzędne określają punkty POMIĘDZY odpowiednimi pikselami "urządzenia wyjścia".
Przy rysowaniu kształtów trzeba rozumować tak:
- rysowanie jest realizowane przez pióro, które zostawia ślad o szerokości i wysokości jednego piksela,
- pióro zostawia ślad PONIŻEJ i Z PRAWEJ strony ścieżki współrzędnych określonych w danej operacji rysowania.
Zatem zrobienie ramki wokół komponentu wygląda tak:
public void paintComponent(Graphics g) {
g.drawRect(0, 0, getWidth()-1, getHeight()-1);
}
Nieco inaczej wygląda sytuacja przy wypełnianiu figur:
public void paint(Graphics g) {
g.fillRect(0,0,getWidth(),getHeight());
}
Tutaj wypełniane jest WNĘTRZE ścieżki po której idzie pióro wyimaginowanego plotera.
Zobaczmy pierwszy przykład - proste "upiększenie" przycisku małymi czerwonymi
kwadracikami umieszczonymi w jego rogach (zob. rysunek)
Aby to osiągnąć, odziedziczymy klasę JButton i przedfiniujemy w niej metodę
paintComponent. W tej metodzie wyrysujemy narożne kwadraciki. Uzyskamy w
ten sposób w pełni funkcjonalny przycisk (mający wszystkie cechy JButton)
z dodatkiem (raczej ilustracyjnym, bo jego celowość jest mało sensowna) w
postaci czerwonych "narożników".
import javax.swing.*;
import java.awt.*;
class MyButton extends JButton {
public MyButton(String txt) {
super(txt);
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
int w = getWidth(); // aktualna szerokość ...
int h = getHeight(); // i wysokość komponentu
g.setColor(Color.red); // ustalenie kolru rysunku
// rysowanie kwadracików
g.fillRect(0, 0, 10, 10);
g.fillRect(w-10, 0, 10, 10);
g.fillRect(0, h-10, 10, 10);
g.fillRect(w-10, h-10, 10, 10);
}
}
class MyButtonTest extends JFrame {
Container cp = getContentPane();
public MyButtonTest() {
MyButton mb = new MyButton("To jest przycisk");
cp.add(mb);
pack();
show();
}
public static void main(String args[]) {
new MyButtonTest();
}
}
Zwróćmy uwagę na ważną kwestię.
Aby zagwarantować odpowiedni wygląd i funkcjonalność gotowych komponentów,
których klasy dziedziczymy, w przedfiniowywanej metodzie paintComponent należy
na początku wywołać metodę paintComponent z nadklasy
Budując własne komponenty calkowicie od podstaw (np. obszary do rysowania), wykorzystujemy zwykle możliwie "minimalne" klasy.
Budowanie komponentów wizualnych od podstaw
|
Komponenty terminalne
|
Komponenty-kontenery
|
Ciężkie
komponenty
AWT
|
class NewComp extends Canvas {
...
}
|
class NewComp extends Panel {
...
}
|
Lekkie
komponenty
AWT
|
class NewComp extends Component {
...
}
|
class NewComp extends Container {
...
}
|
Lekkie
komponenty Swingu
|
class NewComp extends JComponent {
...
}
|
class NewComp extends JPanel {
...
}
|
Przy budowaniu "od podstaw" ważne jest nie tylko przedefiniowanie metody
paint, ale również metod określających minimalne, maksymalne i preferowane
rozmiary komponentów. W przeciwnym razie nasze komponenty mogą być niewidoczne
(klasy "minimalne", takie jak Canvas czy JComponent, dają komponenty o zerowych
rozmiarach).
Przykład: stworzyć rysunek siatki niebieskich linii (zob. rysunek). Obszarem
rysowania będzie własny, zbudowany od podstaw, komponent dziedziczący klasę
JComponent.
Musieliśmy zatem przedefiniwoać metody:
public Dimension getMinimumSize();
public Dimension getPrefferedSize();
public Dimension getMaximumSize();
Uwaga: zwracają one referencję do obiektu klasy Dimension, który opisuje
wymiary - szerokość i wysokość. Również w metodach set.. dla preferowanych,
minimalnych i maksymalnych rozmiarów jako argument podajemy referencje do
obiektu tej klasy. Obiekt taki możemy stworzyć za pomocą konstruktora Dimension(szerokośc,
wysokośc).
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
class ObszarRysunku extends JComponent {
Dimension d;
public ObszarRysunku(int w, int h) {
d = new Dimension(w, h);
}
public Dimension getMinimumSize() { return d; }
public Dimension getPreferredSize() { return d; }
public Dimension getMaximumSize() { return new Dimension(1000, 1000); }
public void paintComponent(Graphics g) {
super.paintComponent(g);
int w = getWidth();
int h = getHeight();
g.setColor(Color.blue);
g.drawRect(0,0,w-1,h-1);
int hor = 10, vert = 10;
while (hor < h) {
g.drawLine(1, hor, w-1, hor);
hor += 10;
}
while (vert < w) {
g.drawLine(vert, 1 , vert, h-1);
vert += 10;
}
}
}
class Rysunek extends JFrame {
Container cp = getContentPane();
public Rysunek() {
ObszarRysunku or = new ObszarRysunku(100, 100);
cp.add(or);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
pack();
show();
}
public static void main(String args[]) {
new Rysunek();
}
}
Czasami na rysunkach będziemy potrzebowali napisów.
Zwykle uzyskujemy je za pomocą metody void drawString(String s, int x, int y)
Lokalizacja tekstu (x, y) - INACZEJ NIŻ PRZY RYSOWANIU KSZTAŁTÓW i FIGUR
- oznacza POŁOŻENIE LINII BAZOWEJ TEKSTU (base line). Różnicę obrazuje rysunek.
Aby dobrze rozplanować położenie tekstu należy uzyskać charakterystyki pisma
i wykorzystać je przy układaniu tekstu. Służy temu klasa FontMetrics np.
FontMetrics fm;
fm = g.getFontMetrics() // zwraca metrykę dla bieżącego pisma kontekstu g
W klasie FontMetrics mamy np. metodę
stringWidth(String s)
która zwraca szerokość zajmowaną przez napis s wyrażony w konkretnym piśmie (tej metryce).
Można ją wykorzystać np. do wycentrowania napisu w poziomie:
public void paintComponent(Graphics g) {
String s = "jakis tekst";
int y = ... ; // położenie w pionie
int w = getWidth();
int h = getHeight();
g.drawString(s, (w - g.getFontMetrics().stringWidth(s))/2, y);
...
}
Inne metody klasy FontMetrics pozwalają na rozmieszczanie napisów w pionie.
Mamy tu takie metody jak:
- getHeight(),
- getAscent(),
- getDescent(),
- getLeading()
Ich znaczenie pokazuje rysunek (źródło: Java Tutorial).
Za pomocą metod klasy Graphics możemy także wykreślać obrazy z plików graficznych (typów JPEG, GIF i PNG).
Mówiąc ściślej, dostępne są metody wykreślania obrazów, które są obiektami klasy Image.
Aby uzyskać obiekt Image, reprezentujący obraz z pliku, możemy zastosować
dwa podejścia: użyć klasy ImageIcon (o czym w następnym wykładzie, przy okazji
omawiania interfejsu Icon) albo użyć metody getImage() z klasy Toolkit.
Głównym zadaniem klasy Toolkit jest zapewnienie współpracy pomiędzy komponentami
AWT, a środkami platformy systemowej, szczególnie komponantami realizowanymi
przez graficzne API systemu. Niewiele z metod tej klasy można i należy wykorzystywać
w programach użytkowych. Do tych niewielu należy np. metoda pobierania infrmacji
o rozdzielczości ekranu oraz - właśnie - metoda getImage(), stosowana do
uzsyakania obrazu z pliku graficznego.
Aby użyć metody getImage() musimy najpierw uzyskać obiekt klasy Toolkit.
Można to zrobić m.in. za pomocą statycznej metody getDefaultToolkit() z klasy
Toolkit.
Dostęp do obrazu z pliku możemy uzyskać za pomocą odwołania:
Image img = Toolkit.getDefaultToolkit().getImage(nazwa_pliku);
Wykreślaniem obrazu w obszarze komponentu wizualnego zajmuje się metoda drawImage z klasy Graphics
Uzyskanie odpowiedniej referencji do obrazu z pliku za pomocą metody getImage() nie powoduje załadowania obrazka z pliku
Ładowanie następuje w trakcie wyświetlania przez metodę drawImage. Zwraca
ona sterowanie, gdy tylko część obrazu zostanie zaladowana i może być wyświetlona.
Metoda paint wołana jest przez system ponownie i ponownie, dopóki caly obraz
nie zostanie załadowany i wyświetlony. Takie ładowanie (i wyświetlanie)
etapami - przy dużych obrazach - może trwać długo (za każdym razem odrysowywany
jest od nowa cały komponent wizualny).
Dlatego w Javie zapewniono dwa sposoby, umożliwiające załadowanie obrazu przed wyświetleniem.
Pierwszy polega na dostarczeniu obiektu typu ImageObsrever. Referencja
do tego obiektu jest ostatnim argumentem przekazywanym metodzie drawImage,
a sam obiekt może zajmować się szczegółowym śledzeniem postępów ladowania
obrazu, co pozwala na jednokrotne wykreślenie komponentu zawierającego obraz
wtedy, gdy cały obraz jest załadowany. Nie będziemy (na razie) korzystać
z tego sposobu, wspomnimy tylko, że ImageObserver jest interfejsem i że klasa
Component implememntuje ten interfejs, zatem jako ostatni argument wywołania
metody drawImage możemy podać referencję do dowolnego komponentu (w szczególności
tego, w obszarze którego obraz jest wyświetlany).
Drugim sposobem na "zaczekanie na załadowanie obrazka" jest użycie klasy MediaTracker.
Nie daje on tak precyzyjnych informacji jak ImageObserver, ale za to jest
łatwiejszy w użyciu i umożliwia śledzenie ładowania wielu obrazków (co jest
użyteczne np. przy animacji).
Konstruktor klasy MediaTracker ma jeden argument - komponent na którym ewentualnie (ale niekoniecznie) będzie malowany obrazek
W zasadzie możemy tu użyć dowolnego komponentu np. aplikacji dziedziczącej okno
Po stworzeniu obiektu MediaTracker dodaajemy do nego obrazy (do śledzenia).
MediaTracker mt = new MediaTracker(this);
Toolkit tk = Toolkit.getDefaultToolkit();
Image img = tk.getImage(nazwaPliku);
mt.addImage(img, 1); // drugi argument - identyfikaor, pozwalający dzielić
// obrazy na grupy z różnymi priorytetami ładowania
Następnie żądamy od MediaTrackera, by rozpoczął ładowanie i zaczekał na załadowanie wszystkich dodanych obrazków:
try {
mt.waitForAll();
} catch(InterruptedException e) { System.exit(1); }
Albo – by ładował obrazy konkretnej grupy (waitForID(...))
Wywołanie metod waitForAll lub waitFor... wstrzymuje dzialanie aplikacji,
dopóki wszystkie obrazy dodane do MediaTrackera (lub obrazy podanej grupy)
nie zostaną załadowane.
To oczekiwanie na załadowanie może przerwane z zewnątrz - stąd potrzeba obsługi wyjątku InterruptedException.
Gdy obrazek jest już zaladowany możemy go wykreślić za pomocą metody drawImage
np. w metodzie paintComponent klasy komponentu, który ma stanowić obszar
prezentacji.
Jedna z przeciążonych wersji tej metody wygląda następująco.
boolean drawImage(Image img,
// obraz do wykreślenia
int x,
int y,
// górny lewy róg w obszarze komponentu
int width, int height,
// szerokość i wysokość (skalowanie)
ImageObserver observer)
Jako przyklad zbudujmy klasę ImagePanel, której obiekty będą stanowić kontenery
z obrazkiem z podanego pliku jako tłem. Do takiego kontenera możemy dodawać
inne komponenty i będą prezentowane na tle obrazu. Inicjalne rozmiary kontenera
będą równe rozmiarom obrazka, a przy zmianach rozmiarów kontenera obrazek
stanowiący jego tło będzie reskalowany. Rysunek obok pokazuje taki kontener
z dodanym przyciskiem "Jakiś przycisk".
Kod programu przedstawiono poniżej.
import javax.swing.*;
import java.awt.*;
class ImagePanel extends JPanel {
Image img;
boolean loaded = false; // czy obrazek został załadowany?
public ImagePanel(String imgFileName) {
loadImage(imgFileName);
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
if (img != null && loaded)
g.drawImage(img, 0, 0, getWidth(), getHeight(), this);
else
g.drawString("Bez obrazka", 10, getHeight() - 10);
}
private void loadImage(String imgFileName) {
img = Toolkit.getDefaultToolkit().getImage(imgFileName);
MediaTracker mt = new MediaTracker(this);
mt.addImage(img, 1);
try {
mt.waitForID(1);
} catch (InterruptedException exc) { }
int w = img.getWidth(this); // szerokość obrazka
int h = img. getHeight(this); // wysokość obrazka
if (w != -1 && w != 0 && h != -1 && h != 0) {
loaded = true;
setPreferredSize(new Dimension(w, h));
}
else setPreferredSize(new Dimension(200,200));
}
}
class ImagePanelTest extends JFrame {
Container cp = getContentPane();
public ImagePanelTest(String fname) {
ImagePanel p = new ImagePanel(fname);
p.add(new JButton("Jakiś przycisk"));
cp.add(p);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
pack();
show();
}
public static void main(String args[]) {
new ImagePanelTest(args[0]); // argument - nazwa pliku graficznego
}
}
Komentarze i uwagi.
- W metodzie loadImage ładujemy obrazek i uzyskujemy jego wymiary. Służą
temu metody getWidth(...) i getHeight(...) z klasy Image. Ich argumentem
jest ImageObserver, możemy podać this, bo klasy wszystkich komponentów implementują
interfejs ImageObserver. Zgodnie z tymi wymiarami ustalamy preferowane rozmiary
kontenera, Jeśli obrazek (z jakichś przyczyn) nie jest gotowy do wyświetlenia
(-1 jako wynik metod getWidth lub getHeight) - ustalamy domyślne rozmiary
kontenera, a w metodzie paintComponent - wykreślimy u jego dołu napis "Bez
obrazka".
- Zauważmy też, że wykreślając obrazek metodą drawImage jako jego szerokośc
i wysokość podajemy aktualne rozmiary kontenera. Rozmiary te mogą się zmieniać
i zgodnie z tymi zmianami obrazek będzie reskalowany.
- Nieco prostszy program uzyskamy używając klasy ImageIcon, która przy
konstrukcji obiektów automatycznie używa MediaTrackera i synchronicznie
ładuje obraz. Nie będziemy więc musieli zapisywać kodu związanego z MediaTrackerem,
ale za to będziemy musieli od obiektu klasy ImageIcon uzyskać obraz klasy
Image (więcej o tym w następnym wykładzie).
Kończąc to syntetyczne "wprowadzenie do rysowania" trzeba powiedzieć, że
w Javie istnieją dużo bardziej rozbudowane możliwości graficzne. W zakresie
grafiki dwuwymiarowej dostarcza ich klasa Graphics2D oraz szereg innych związanych
z nią klas. Z zaawansowanymi możliwościami graficznymi, również w obszarze
przetwarzania obrazów i multimediów, zapoznamy się w przyszłym semestrze.
7.8. Komponenty Swingu a wielowątkowość
Przypomnijmy, że interakcja użytkownika z aplikacją graficzną odbywa się
poprzez obsługę zdarzeń (takich jak np. kliknięcie w przycisk). Obslugą zdarzeń
zajmuje się specjalny, uruchamiany przez JVM równolegle z głównym wątkiem
aplikacji graficznej, wątek obslugi zdarzeń.
Oczywiście, zanim dojdzie do interakcji z użytkownikiem, okno aplikacji musi
być uwidocznione. Uwidoczneinie okna uwidacznia wszystkie zawarte w nim komponenty
wizualne. Powiada się, że wtedy komponenty są realizowane.
Komponent uznaje się za zrealizowany wtedy, gdy zostaje uwidoczniony lub gdy okno, w którym się znajduje zostaje spakowane
Komponenty Swingu zostały zbudowane w taki sposób, że wywołanie metod na ich rzecz po realizacji powinny być dokonywane wyłącznie w wątku obsługi zdarzeń.
Niewątpliwie łatwo i naturalnie można spełnić ten warunek w trakcie obsługi
zdarzeń - w metodach obsługujących zdarzenia, bowiem metody te wywoływane
są w wątku obsługi zdarzeń.
We wszystkich innych przypadkach kod, który działa na komponentach Swingu,
powinniśmy umieścić "do wykonania" w wątku obsługi zdarzeń. Służą temu statyczne
metody invokeLater oraz invokeAndWait z klasy SwingUtilities.
Ich argumentem jest referencja do obiektu klasy implementującej interfejs Runnable.
W zdefiniowanej w tej klasie metodzie run() umieszczamy kod operujący na
komponentach Swingu. Metoda invokeLater przekaże ten kod do wykonania przez
wątek obsługi zdarzeń (umieści kod w kolejce zdarzeń), wykonanie bieżącego
wątku będzie kontynuowane, natomiast nasz kod umieszczony w kolejce zdarzeń
zostanie wykonany wtedy, gdy wątek obslugi zdarzeń obsłuży zdarzenia znajdujące
się w kolejce przed nim.
Tak samo działa metoda invokeAndWait, z tą różnicą, że po jej wywołaniu dzialanie
bieżącego watku zostanie wstrzymane do chwili wykonania przekazanego kodu
przez wątek obsługi zdarzeń.
Prezentuje to ilsutracyjny schemat:
SwingUtilities.invokeLater( new Runnable() {
public void run() {
// tu działania na komponentach Swingu
}
});
Od tej reguły są wyjątki. Mianowicie, niektóre metody działające na komponentach
Swingu są wielowątkowo bezpieczne (mogą być wywoływane z wielu watków, nie
tylko z wątku obsługi zdarzeń). Należy do nich np. metoda repaint().
7.9. Podsumowanie
Wykład stanowił przegląd komponentów wizualnej interakcji użytkownika z programem.
Niejako z lotu ptaka poznaliśmy dostępne w Javie komponenty. Przyjrzeliśmy
się także wspólnym, dla wszystkich bez wyjątku komponentów, właciwościom
i sposobom ich pobierania i ustalania. Ostatni punkt ktrótko wprowadził nas
w elementy grafiki, co umożliwia wykonywamnie prostych rysunków i umieszcanie
obrazów "na komponentach".
7.10. Zadania i ćwiczenia
Zad 1. Właściwości komponentów
Stworzyć okno ramowe JFrame z tytułem "Prosty edytor", zawierające komponent
JTextArea (wielowierszowe pole edycyjne).
Zapewnić możliwość ustawiania z wiersza poleceń (przy uruchamianiu
aplikacji) rodzaju i rozmiaru pisma oraz kolorów tła i pisma.
Argumenty wywołania:
- typ pisma (np. "Dialog", "Serif", "Monospaced")
- rozmiar pisma
- trójka RGB, określająca kolor tła
- trójka RGB, określająca kolor pisma
Domyślne wartości
Jeśli w wywołaniu nie podano argumentów, domyślne wartości winny być
takie:
typ pisma = "Dialog"
rozmiar pisma = 14
kolor tła = niebieski
kolor pisma = biały
Wywołanie aplikacji powinno być możliwe:
- bez argumentów
- tylko rodzaj pisma
- rodzaj pisma i rozmiar pisma
- jw. + kolor tła (trzy liczby)
- jw. + kolor pisma (trzy liczby)
Np.
java Zad1 -> niebieski edytor z pismem Dialog 14 w kolorze białym
java Zad1 Monospaced -> niebieski edytor z pismem Monospaced 14 w kolorze
białym
java Zad1 Dialog 18 -> niebieski edytor z pismem Dialog 18 w
kolorze białym
java Zad1 Dialog 18 0 0 0 -> czarny edytor z pismem Dialog 18 w kolorze
białym
java Zad1 Dialog 18 255 255 255 0 0 0 -> biały edytor z pismem jw.
Zad. 2. Wykres
Napisać aplikację, która wczytuje plik tekstowy.
Aplikacja ma zliczać częstotliwość wystąpienia poszczególnych znaków w pliku
i podać wynik graficznie - w postaci wykresu słupkowego, na którym szerokość
słupków jest proporcjonalna do częstości występowania znaków, przy czym minimalna
częstość jest oznaczana kolorem sarym, maksymalna - czerwonym, a posrednie
- niebieskim. Za słupkami na wykresie pokazać liczby, oznaczające odpowiednie
częstości. Obrazuje to poniższy rysunek.
Zad. 3. Obrazki
Napisać program, który wyświetla kolejno co 5 sekund pliki graficzne znajdujące się w podanym katalogu.
Np. jeśli w podanym katalogu znajdują się pliki pool1.jpg, pool2.jpg i bview2.jpg,
to w oknie aplikacji mają pojawiać się co 5 sekund kolejne obrazki z tych
plików: