Aplikacje w Javie – wykład 12

Transkrypt

Aplikacje w Javie – wykład 12
Aplikacje w Javie – wykład 12
Programowanie GUI
Treści prezentowane w wykładzie zostały oparte o:
● Barteczko, JAVA Programowanie praktyczne od podstaw, PWN, 2014
● http://docs.oracle.com/javase/8/docs/
● C. S. Horstmann, G. Cornell, Java. Podstawy, Helion, Gliwice 2008
1
GUI – komponenty wizualne i kontenery
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, menu, tablice itp.).
Komponenty wywodzą się z abstrakcyjnej klasy Component. Tworzymy je 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, czcionka 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 reprezentowana przez obiekty
odpowiednich klas (np. czcionka – 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 (rozkład jest jedną z właściwości
kontenera).
●
Zarządcy rozkładu są obiektami odpowiednich klas.
2
Komponenty wizualne i kontenery
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 Swing zawierają tzw. contentPane, który jest kontenerem do którego
domyślnie 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ń.
AWT (Abstract Windowing Toolkit) – obecny w Javie od samego początku - jest
zestawem klas, definiujących proste komponenty wizualnej interakcji. Są to
komponenty ciężkie tzn. realizowane poprzez użycie graficznych bibliotek GUI
systemu operacyjnego:
●
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
3
Komponenty wizualne i kontenery
Odpowiedzią na te problemy oraz ich rozwiązaniem jest pakiet Swing (JFC –
Java Foundation Classes) (javax.swing i podpakiety), który 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 Swinga oprócz kontenerów znajdujących się najwyżej w
hierarchii zawierania się komponentów (kontenerów najwyższego poziomu) są
komponentami lekkimi.
Komponenty lekkie:
●
są 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).
●
mogą być przezroczyste, a zatem mogą przybierać wizualnie dowolne
kształty
●
mają wygląd niezależny od platformy i modyfikowalny (Look&Feel)
Uwaga: możliwe jest umieszczanie w jednym kontenerze komponentów lekkich
(np. Swinga) i ciężkich (AWT), jednak jest to nie polecane i obarczone pewnymi
restrykcjami. Zalecane jest, aby dodawać komponenty Swinga do
contentPane okna ramowego Swingu (JFrame).
4
Hierarchia komponentów AWT i Swing
5
Hierarchia komponentów AWT i Swing
6
Hierarchia komponentów AWT i Swing
Wnioski, które wynikają z tej hierarchii :
●
podstawowe właściwości wszystkich komponentów (i AWT i Swinga) 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 Swinga, 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
Komponenty Swing
Przyciski
➢
JButton – odpowiednik Button z AWT,
➢
JToggleButton – przycisk dwustanowy o wyglądzie normalnego przycisku,
➢
JCheckBox – odpowiednik CheckBox z AWT,
➢
JRadioButton – odpowiednik RadioButton z AWT.
Podstawowe funkcje wszystkch klas przycisków określone są w klasie
AbstractButton. Przyciski przełącznikowe implementują JToggleButton.
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 mnemoników (metoda setMnemonic())
Etykieta: klasa JLabel
Możliwości:
➢
tekst i/lub ikona z dowolnym pozycjonowaniem
➢
tekst HTML
➢
ustalenie mnemonika i związanie go z innym komponentem (setLabelFor(...)),
np. polem edycyjnym (wciśnięcie alt+mnemonik powoduje przejście fokusu do
danego komponentu np. pola edycyjnego)
8
Komponenty Swing
9
Komponenty Swing
Menu rozwijalne: klasy JMenu, JMenuItem, JCheckBoxMenuItem,
JRadioMenuItem.
Ponieważ pochodzą od AbstractButton, to posiadają 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 komponenty
GUI. Inny dialog wyboru - JFileChooser - wybór plików.
Pole edycyjne: JTextField
➢
do wprowadzania hasła: JPasswordField
➢
W JDK 1.4 - nowa klasa JFormattedTextField - z wbudowaną
weryfikacją tekstu
10
Komponenty Swing
11
Komponenty Swing
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óżne elementy listy mogą mieć różny wygląd (kolor, czcionkę, 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)
12
Komponenty Swing
13
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 typu boolean) isNNN() i ustalane (jeśli to możliwe) za pomocą
metod setNNN(...)., gdzie NNN – nazwa właściwości.
Do najogólniejszych właściwości wszystkich komponentów należą:
rozmiar (Size), szerokość (Width), wysokość (Height), położenie
(Location), rozmiar i położenie (Bounds), minimalny rozmiar (MinimumSize),
preferowany rozmiar (PreferredSize), maksymalny rozmiar
(MaximumSize),wyrównanie po osi X (AlignmentX), wyrównanie po osi Y
(AlignmentY), czcionka (obiekt 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), nazwa (Name) (komponenty
otrzymują domyślne nazwy; można je zmieniać), widzialność (Visible),
lekkość (LigthWeight), przezroczystość (Opaque), dostępność (Enabled).
14
Właściwości komponentów
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 obszar kontenera, do
którego mogą być dodane komponenty). 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 lub dla komponentów-okien.
Aby ustalić rozmiar okna frame piszemy np.: frame.setSize(200, 200);
Metoda pack()w klasie Window (dziedziczonej przez Frame i JFrame) – 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();
Czcionka jest obiektem klasy Font, tworzonym za pomocą konstruktora
Font(nazwa_czcionki, styl, rozmiar)
gdzie:
●
●
●
nazwa_czcionki - jest łańcuchem znakowym, określającym rodzaj czcionki (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 czcionki w punktach.
15
Właściwości komponentów
Podstawowe, logiczne nazwy czcionek to: Serif, SansSerif, Dialog i
MonoSpaced.
Zatem, aby np. dla przycisku b ustalić czcionkę, 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 = 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
// zablokowanie; kliknięcia w przycisk nie będą "przyjmowane"
b.setEnabled(false);
// odblokowanie:
if (!b.isEnabled()) b.setEnabled(true);
16
Właściwości komponentów
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łania 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);
17
Kontenery Swing
Kontenery - to komponenty, które mogą zawierać inne komponenty (w tym inne
kontenery).
Podstawowe kontenery to:
●
Panel: klasa JPanel - służy do grupowania komponentów
W Swingu mamy też wyspecjalizowane kontenery (panele dzielone, zakładkowe,
przewijane), które mają specjalną konstrukcję i wobec nich używamy innych metod
ustalania zawartości:
●
●
●
●
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
Panele są umieszczane w innych kontenerach - np. oknach. Domyślnie panele są
widoczne, a okna - nie.
18
Kontenery Swing
19
Kontenery Swing – metody. Okna.
Podstawowe metody (oprócz odziedziczonych z klasy Component) to:
●
add(<nazwa komponentu>) – dodawanie komponentu do kontenera,
●
remove(<nazwa komponentu>) – usunięcie komponentu z kontenera.
A ponadto:
●
getCopmonentCount() – zwraca liczbę komponentów,
●
getComponent(int n) – zwraca referencję do n-tego komponentu,
●
getComponents() – zwraca tablicę wszystkich komponentów,
●
setLayout(...) – ustawia rozmieszczenie komponentów.
Okna to kontenery najwyższego poziomu, za pomocą których aplikacja komunikuje
się z użytkownikiem. Najważniejsze klasy, które tworzą okna to: JFrame, JDialog,
JWindow, JApplet, JInternalFrame.
●
●
●
●
Główne okno aplikacji jest obiektem klasy JFrame, np:
JFrame okno = new JFrame(”Okno główne”);
W AWT komponenty dodajemy bezpośrednio do okien.
W Swingu zwykle dodajemy komponenty (w tym kontenery) do contentPane
(specjalnego kontenera, stanowiącego standardową zawartość okna). Dostęp do
tego kontenera umożliwia metoda getContentPane(), np:
Container cp = okno.getContentPane();
Kolejne komponenty umieszcza się w kontenerze przy użyciu metody add(...)
20
Okna – podstawowe pojęcia
Podstawowe pojęcia, dotyczące okien
●
okno wtórne (secondary window, child window) = okno które ma właściciela - inne
okno
●
okno pierwotne, właściciel innych okien (owner) = okno, które jest właścicielem
innych okien
Skutki prawa własności:
●
●
●
zamknięcie okna pierwotnego powoduje zamknięcie okien wtórnych, które są jego
własnością,
minimalizacja okna pierwotnego powoduje minimalizację okien wtórnych,
przesunięcie okna pierwotnego powoduje przesunięcie okien wtórnych (nie na
wszystkich platformach).
Okno wtórne może być oknem modalnym lub nie.
Modalność oznacza, iż interakcja z oknem pierwotnym jest zablokowana do chwili
zamknięcia okna wtórnego.
Przy niemodalnym oknie wtórnym - możemy dalej działać na oknie pierwotnym.
Z-order = uporządkowanie okien "po osi Z" (czyli jak się okna na siebie nakładają).
Wszystkie okna w Javie, za wyjątkiem "okna" apletu (które tak naprawdę jest panelem)
oraz okien wewnętrznych Swingu, pochodzą od klasy Window.
21
Okna ramowe - JFrame
Ważnym rodzajem okna jest okno ramowe, mające ramki, pasek tytułowy, ikonki sterujące, oraz ew.
pasek menu. Realizowane jest ono przez klasy Frame (w AWT) i JFrame (w Swingu). Nie ma
właściciela i nie może być oknem modalnym.
Główne okno naszej aplikacji będzie zwykle obiektem klasy pochodnej od JFrame
Okno ramowe tworzymy za pomocą konstruktora bezparametrowego lub z argumentem String,
stanowiącym tytuł okna.
Niektóre metody klas Frame/JFrame
●
Image getIconImage() jaka ikonka przy minimalizacji?
●
MenuBar getMenuBar()
pasek menu (dla Frame)
●
JMenuBar getJMenuBar() pasek menu (dla JFrame)
●
String getTitle()
tytul
●
boolean isResizable()
czy możliwe zmiany rozmiarów?
●
remove(MenuComponent)
usunięcie paska menu
●
setIconImage(Image) ustala ikonkę używaną przy minimalizacji
●
setMenuBar(MenuBar) ustala pasek menu (dla Frame)
●
setJMenuBar(JMenuBar)
ustala pasek menu (dla JFrame)
●
setResizable(boolean)
ustalenie możliwosci zmiany rozmiarów
●
setTitle(String)
zmiana tytułu
●
setUndecorated(boolean) ustala, czy okno ma mieć "dekoracje" (tj. pasek tytułu, ramkę itp.)
●
setExtendedState(int stan) ustala stan okna, reprezentowany przez jedną ze stałych
statycznych z klasy Frame (podawane jako argument - stan): NORMAL, ICONIFIED,
MAXIMIZED_HORIZ, MAXIMIZED_VERT, MAXIMIZED_BOTH.
Uwaga: nie na wszystkich platformach wszystkie w/w stany są możliwe do osiągnięcia. Aby
stwierdzić, które tak, a które nie, używamy metody: Toolkit.isFrameStateSupported(int
stan)
22
Okna dialogowe, wewnętrzne.
Dialog (obiekty klas Dialog i JDialog ) - to okno z ramką i tytułem, które ma właściciela i nie
może mieć paska menu. Może natomiast być oknem modalnym lub nie, co zależy od naszych
ustaleń.
Podobnie jak Frame/JFrame klasa Dialog/JDialog zawiera metody pozwalające na
pobieranie - ustalanie tytułu oraz możliwości zmiany rozmiarów. Dodatkowo metody boolean
isModal() i setModal(boolean) pozwalają sprawdzać i ustalać właściwość modalności.
Okna wewnętrzne (JInternalFrame) dostępne w Swingu są lekkimi komponetami o
funkcjonalności okien ramowych. Podstawowe różnice wobec zwykłych okien ramowych
(JFrame):
●
niezależny od platformy wygląd
●
muszą być dodawane do innych kontenerów
●
dodatkowe możliwości programistyczne (np. dynamicznych zmian właściwości takich jak
możliwość zmiany rozmiaru, możliwość maksymalizacji itp.)
Operacja zamknięcia okna. Wszystkie okna Swinga (JFrame, JDialog,
JInternalFrame...) pozwalają na ustalenia co ma się stać w przypadku zamknięcia okna.
Służy temu metoda setDefaultCloseOperation(int), której argument może przyjmować
następujące wartości (są to nazwy odpowiednich statycznych stałych):
●
DO_NOTHING_ON_CLOSE
●
HIDE_ON_CLOSE - operacja domyślna (chowa okno, ale nie likwiduje go)
●
DISPOSE_ON_CLOSE - usunięcie okna, ale bez zamknięcia aplikacji
●
EXIT_ON_CLOSE - powoduje zamknięcie aplikacji
W AWT - dla wywołania określonych efektów przy zamknięciu okna (np. zakończenia aplikacji)
musimy obsługiwać zdarzenie zamykania okna.
23
Umieszczanie komponentów w oknie - przykład
import java.awt.*;
import javax.swing.*;
public class Ramka extends JFrame{
public Ramka(){
super("Ramka 2"); //Wywołanie konstruktora nadklasy
setSize(250,70); //Ustawienie rozmiaru okna ramki
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//Umieszczanie komponentów w oknie
JPanel p = new JPanel();
p.add(new JLabel("Etykieta") );
p.add(new JButton("Przycisk") );
p.add(new JTextField("Pole textowe"));
Container cp = getContentPane();
cp.add(p);
setVisible(true);//Wyświetlenie ramki na ekranie
}
}
public static void main(String []args){
new Ramka();
}
24
Zarządcy rozkładów
Z każdym kontenerem jest skojarzony tzw. zarządca rozkładu, który określa rozmiary i
położenie komponentów przy wykreślaniu kontenera (a więc gdy jest on uwidaczniany lub
zmieniają się jego rozmiary).
W klasie każdego z komponentów znajdują się metody: getPreferredSize(),
getMinimumSize() i getMaximumSize(), zwracające - różne w zależności od typu
komponentu - rozmiary preferowane, minimalne i maksymalne (obiekty typu Dimension z
publicznymi polami width i height)
Rozmiary te - w różny sposób przez różnych zarządców rozkładów - są brane pod uwagę
przy układaniu komponentów.
Dla lekkich komponentów Swinga rozmiary te możemy ustalać za pomocą odpowiednich
metod set...
Zdarza się czasem, że zmiany rozmiarów jakiegoś komponentu, umieszczonego w
kontenerze (np. na skutek jakichś pośrednich odwołań z programu) nie skutkują w
rozkładzie komponentów w kontenerze. Należy wtedy wywołać metodę revalidate()
na rzecz komponentu, którego rozmiary uległy zmianie, co spowoduje ponowne ułożenie
komponentów w kontenerze przez zarządcę rozkładu. Jeśli zmiany ułożenie nie będą
uwidocznione na ekranie - należy dodatkowo wywołać metodę repaint(), również na
rzecz zmienionego komponentu.
Użycie pack() wobec okna zawsze powoduje wywołanie metod układania komponentów
przez zarządców rozkładu dla wszystkich kontenerów w hierarchii zawierania się
komponentów w oknie.
25
Zarządcy rozkładów
●
●
●
Jak prawie wszystko w Javie - zarządcy rozkładu są obiektami odpowiednich klas.
Klasy te implementują interfejs LayoutManager lub rozszerzający go
LayoutManager2.
Np. rozkłady Flow, Border i Grid są zarządzane przez obiekty klas FlowLayout,
BorderLayout i GridLayout.
●
Aby ustalić rozkład komponentów musimy:
– stworzyć obiekt-zarządcę rozkładu,
– ustalić ten rozkład dla kontenera.
●
Ustalenie zarządcy rozkładu w AWT dla kontenera odbywa się za pomocą metody
setLayout np.
FlowLayout flow = new FlowLayout();
Frame f = new Frame();
f.setLayout(flow);
Okna Swinga mają złożoną architekturę i zwykle rozkład komponentów ustalamy dla
contentPane okna np.:
JFrame frame = new JFrame();
frame.getContentPane().setLayout(new FlowLayout());
Samo odwołanie: frame.setLayout(...) domyślnie ustala rozkład w
contentPane (a nie w oknie).
●
●
26
Rozkład - FlowLayout
FlowLayout (rozkład domyślny dla Panel, JPanel)
●
Komponenty ułożone są w wierszu.
●
Przy zmianie rozmiarów kontenera rozmiary komponentów nie zmieniają się
●
Jeśli szerokość kontenera jest za mała, pojawiają się dodatkowe wiersze.
●
Można ustalić, jak mają być wyrównane komponenty (do lewej, w centrum, do
prawej): służą temu stałe FlowLayout.LEFT, FlowLayout.CENTER,
FlowLayout.RIGHT podawane jako argument konstruktora
●
Można ustalić odstępy (w pionie i poziomie) pomiędzy komponentami.
Wersje konstruktorów
FlowLayout()
FlowLayout(int)
// (center, odstępy 5)
//
podane wyrównanie
FlowLayot(int, int, int)
// podane wyrównanie oraz odstępy
//
poziom, pion
27
Rozkład - BorderLayot
BorderLayout (układ domyślny dla Frame, Window i Dialog oraz
contentPane okien Swingu)
●
●
●
●
●
Komponenty ułożone są "geograficznie": "North", "East", "South", "West",
"Center"
Używa się metody cont.add(comp, loc), gdzie loc - napis oznaczający
miejsce lub stałą całkowitoliczbową BorderLayout.NORTH,
BorderLayout.CENTER, etc.
Komponent dodany w miejscu "Center" wypełnia całe pozostawiane przez
inne komponenty miejsce.
Komponenty zmieniają rozmiary wraz ze zmianami rozmiaru kontenera:
–
"North" i "South" - w poziomie, ale nie w pionie
–
"West" i "East" - w pionie, ale nie w poziomie
–
"Center" - w obu kierunkach
Można podać odstępy między komponentami (pion, poziom)
new BorderLayout(odst_poz, odst_pion)
28
Rozkład - GridLayout
GridLayout
●
Siatka (tablica) komponentów
●
Rozmiary wszystkich komponentów będą takie same
●
Zmieniają się wraz ze zmianami rozmiaru kontenera
Konstruktory:
GridLayout(n, m)
// tablica n x m komponentów,
Jeśli n=0 lub m=0, to dany wymiar tablicy zostanie ustalony dynamicznie na
podstawie drugiego wymiaru i liczby komponentów w kontenerze.
GridLayout(n, m, hgap, vgap) // z podanymi odstępami w
poziomie i pionie
29
Rozkład BoxLayout
BoxLayout to bardzo elastyczny rozkład, który układa komponenty w jednym wierszu lub w
jednej kolumnie (poziomo lub pionowo).
W odróżnieniu od rozkładu GridLayout brane są przy tym pod uwagę preferowane,
mnimalne i maksymalne rozmiary komponentów (dla komponentów Swingu
setMinimumSize() i setMaximumSize()) oraz ich wyrównanie w kontenerze (w Swingu:
metody setAlignmentX(...) i setAlignmentY(...) z argumentami - stałymi
statycznymi z klasy Component: LEFT_ALIGNMENT, CENTER_ALIGNMENT,
RIGHT_ALIGNMENT, BOTTOM_ALIGNMENT, TOP_ALIGNMENT).
Aby istniejącemu kontenerowi cont nadać rozkład BoxLayout:
pionowy: cont.setLayout(new BoxLayout(cont, BoxLayout.Y_AXIS));
poziomy: cont.setLayout(new BoxLayout(cont, BoxLayout.X_AXIS));
W przypadku tworzenia nowego kontenera można użyć klasy Box, która definiuje kontener
lekki o rozkładzie BoxLayout (uwaga: Box nie jest J-komponentem)
Tworzenie obiektu Box:
●
za pomocą konstruktora: Box(orient), gdzie orient - BoxLayout.X_AXIS lub Y_AXIS
●
za pomocą statycznych metod zwracających referencję do utworzonego obiektu Box:
Box.createHorizontalBox() lub Box.createVerticalBox().
W klasie Box zdefiniowano też wygodne metody statyczne do wprowadzania "wypełniaczy"
przestrzeni w rozkładzie BoxLayout. Te wypełniacze to: klej (glue) (Box.createGlue()),
sztywny obszar (rigid area) (Box.createRigidArea(Dimension)) oraz wypełniacz (boxfiller) (new Box.Filler(min, pref, max)).
30
Rozkłady
●
●
●
●
●
Do bardziej zaawansowanych rozkładów należą: GridBagLayout, CardLayout oraz
GroupLayout(przeznaczony raczej do stosowania w środowiskach wizualnych
edytorów GUI).
Wszystkie te klasy implementują interfejs LayoutManager2, który rozszerza interfejs
LayoutManager. Implementując te interfejsy we własnych klasach możemy również
tworzyć własnych zarządców rozkładu. Dostępne są także gotowe, przygotowane przez
różnych programistów, ciekawe rozkłady, które nie są w "standardzie" Javy, ale mogą
być łatwo doinstalowane. Np. bardzo elastyczne i łatwe w użyciu są rozkłady
TableLayout i MigLayout.
Możliwe jest także, by kontener nie miał żadnego rozkładu. Ponieważ wszystkie
kontenery (oprócz ScrollPane w AWT i wyspecjalizowanych kontenerów Swingu) mają
jakiś domyślny rozkład, to brak rozkładu musimy zaordynować sami za pomocą
odwołania:
kontener.setLayout(null);
W takim kontenerze posługujemy się "absolutnym" pozycjonowaniem i wymiarowaniem
komponentów, np. za pomocą metody setBounds(x,y,w,h), gdzie x, y współrzędne położenia komponentu, w,h - szerokość i wysokość.
Podsumowując: użycie rozkładów pozwala programiście unikać oprogramowania zmian
rozmiarów i położenia komponentów przy zmianie rozmiarów kontenera. Wyszukane
układy komponentów GUI zwykle uzyskamy łatwiej poprzez umiejętne łączenie paneli z
różnymi prostszymi rozkładami.
31
Rozkłady
32
Rozkłady
import java.awt.*;
import javax.swing.*;
public class LayoutTest {
public static void main(String[] args) {
// liczba komponentów dodawanych do każdego rozkładu
final int CNUM = 5;
// liczba komponentów dodawanych do każdego rozkładu
// nazwy rozkładów (opisy)
String lmNames[] = {"Flow Layout", "Flow (left aligned)",
"Border Layout", "Grid Layout(1,num)",
"Grid Layout(num, 1)", "Grid Layout(n,m)",};
// odpowiedni zarządcy
LayoutManager lm[] = {new FlowLayout(),
new FlowLayout(FlowLayout.LEFT),
new BorderLayout(), new GridLayout(1, 0),
new GridLayout(0, 1), new GridLayout(2, 0),};
String gborders[] = {"West", "North", "East", "South", "Center"};
// kolory paneli dla rozkładów
Color colors[] = {new Color(191, 225, 255), new Color(255, 255, 200),
new Color(201, 245, 245), new Color(255, 255, 140),
new Color(161, 224, 224), new Color(255, 255, 200)};
33
Rozkłady
// główne okno
JFrame frame = new JFrame("Layouts show");
Container cp = frame.getContentPane(); // jego contentPane
cp.setLayout(new GridLayout(0, 2)); // rozkład cp = siatka
for (int i = 0; i < lmNames.length; i++) {
// kolejne panele z kolejnymi rozkładami
JPanel p = new JPanel();
p.setBackground(colors[i]);
// ramka z opisem
p.setBorder(BorderFactory.createTitledBorder(lmNames[i]));
p.setLayout(lm[i]);
// ustalenie rozkładu
for (int j = 0; j < CNUM; j++) { // dodanie komponentów
JButton b = new JButton("Przycisk " + (j + 1), null);
p.add(b, gborders[j]);
}
cp.add(p);
}
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);// domyślna
//op. zamkn. okna
frame.pack();
frame.setVisible(true);
}
}
34
Schemat aplikacji Swing
●
●
●
●
Interakcja użytkownika z aplikacją graficzną odbywa się poprzez obsługę zdarzeń (takich
jak np. kliknięcie w przycisk). Obsługą zdarzeń zajmuje się specjalny, uruchamiany przez
JVM równolegle z głównym wątkiem aplikacji, wątek obsługi zdarzeń.
Zanim dojdzie do interakcji z użytkownikiem, okno aplikacji musi być zbudowane i
uwidocznione, muszą być skonstruowane komponenty, muszą uzyskać one jakieś
właściwości (np. kolor, czcionkę) i być dodane do kontenerów, a w końcu i do okien.
Komponenty Swinga zostały zbudowane w taki sposób, że wszelkie manipulacje na nich
(tworzenie, odczytywanie i zmiany ich właściwości) muszą się odbywać w jednym wątku najwygodniej w wątku obsługi zdarzeń.
Zatem kod, który działa na komponentach Swinga, umieszczamy w metodach
obsługujących zdarzenia albo w wątku obsługi zdarzeń za pomocą statycznych metod
invokeLater oraz invokeAndWait z klasy java.awt.EventQueue. Tak samo
nazwane metody znajdują się również w klasie SwingUtilities (delegują one
odwołania do odpowiednich metod z klasy EventQueue). Argumentem tych metod jest
referencja do obiektu klasy implementującej interfejs Runnable. Kod operujący na
komponentach Swinga umieszczamy w zdefiniowanej w tej klasie metodzie run().
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
obsługi 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 działanie
bieżącego wątku zostanie wstrzymane do chwili wykonania przekazanego kodu przez
wątek obsługi zdarzeń. Zatem nie należy wywoływać tej metody z wątku obsługi zdarzeń,
bowiem może to zablokować GUI.
35
Schemat aplikacji Swing
●
●
●
Schemat wywołania metody invokeLater(..)
SwingUtilities.invokeLater(new Runnable() {
public void run() {
// tu działania na komponentach Swingu
}
});
Generalnie wszelkie działania na GUI (tworzenie komponentów, dodawanie
komponentów do kontenerów, ustalanie właściwości komponentów
wizualnych) winny być wykonywane w wątku obsługi zdarzeń.
Od tej reguły są wyjątki. Mianowicie, niektóre metody działające na
komponentach Swinga są wielowątkowo bezpieczne (mogą być
wywoływane z wielu wątków, nie tylko z wątku obsługi zdarzeń). Należy do
nich np. metoda repaint(). W dokumentacji metod zawsze zaznaczono,
czy są one wielowątkowo bezpieczne (thread-safe).
36
Szablon aplikacji Swing
import java.awt.*;
import javax.swing.*;
public class GuiSwing extends JFrame {
public GuiSwing() {
//działania inicjacyjne, nie związane z komponentami Swing
// …
// budujemy okno;
// ponieważ jest to działanie na komponentach wizualnych
// - zrobimy to w wątku obsługi zdarzeń
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
createGui();
}
});
}
37
Szablon aplikacji Swing
protected void createGui() {
// ustalenie tytułu okna
setTitle("Okno aplikacji");
Container cp = getContentPane();
// ustalenie (jeśli trzeba) rozkładu
cp.setLayout(new FlowLayout());
// tworzenie komponentów np.
JLabel lab = new JLabel("Etykieta");
JButton b = new JButton("Przycisk");
// Ustalenie własciwości komponentów,
lab.setForeground(Color.red);
b.setForeground(Color.blue);
// Dodanie komponentów do okna np.
cp.add(lab);
cp.add(b);
// Ustalenie domyślnej operacji zamknięcia okna
setDefaultCloseOperation(EXIT_ON_CLOSE);
// ustalenie rozmiarów okna, np.:
pack();
38
Szablon aplikacji Swing
// ustalenie położenia okna np. wycentrowanie
setLocationRelativeTo(null);
}
}
// pokazanie okna
setVisible(true);
public static void main(String[] args) {
new GuiSwing();
}
Uwaga. Wszelkie długotrwałe działania (np. wyszukiwanie w systemie plików,
ściąganie danych z Internetu) winny być wykonywane poza wątkiem obsługi
zdarzeń. W przeciwnym razie będą blokować interakcję z GUI (GUI staje się
"zamrożone"). Do uruchamiania takich działań z kodów wykonujących się w
wątku obsługi zdarzeń służy klasa SwingWorker.
39
Delegacyjny model obsługi zdarzeń
●
Interakcja użytkownika z GUI naszej aplikacji polega na wywoływaniu zdarzeń (np.
kliknięcie w przycisk, wciśnięcie klawisza na klawiaturze etc).
●
Zatem programowanie GUI jest programowaniem zdarzeniowym. Jest ono oparte na
koncepcji tzw. funkcji callback. Funkcja typu callback zawarta w naszym programie jest
wywoływana (zazwyczaj) nie przez nasz program, ale przez sam system na przykład w
reakcji na zdarzenia takie jak wciśnięcie klawisza czy kliknięcie myszką. Funkcja ta
obsługuje zdarzenie.
●
W Javie dodatkowo zastosowano koncepcję delegacyjnego modelu obsługi zdarzeń,
która umożliwia przekazanie obsługi zdarzenia, które przytrafia się jakiemuś obiektowi
(źródło zdarzenia (source)) do innego obiektu (słuchacza zdarzenia (listener)).
●
Zdarzenia (event) są obiektami odpowiednich klas, określających rodzaj zdarzeń.
(Obsługa zdarzeń jest zawarta w pakiecie java.awt.event. )
●
Słuchacze są obiektami klas implementujących interfejsy nasłuchu. Interfejsy nasłuchu
określają zestaw metod obsługi danego rodzaju zdarzeń. W klasach słuchaczy definiuje
się metody odpowiedniego interfejsu nasłuchu zdarzeń, które określają co ma się dziać
w wyniku zajścia określonego zdarzenia (metody obsługi odpowiednich zdarzeń)
●
Zdarzenie (obiekt odpowiedniej klasy zdarzeniowej) jest przekazywane do obsługi
obiektowi-słuchaczowi tylko wtedy, gdy Słuchacz ten jest przyłączony do Źródła
zdarzenia (przyłączenie za pomocą odwołania z.addNNNListener(h), gdzie: z –
Źródło zdarzenia, NNN - rodzaj zdarzenia, h – Słuchacz danego rodzaju zdarzenia)
40
Delegacyjny model obsługi zdarzeń
●
●
●
Przekazanie zdarzenia do obsługi polega na wywołaniu odpowiedniej dla danego
zdarzenia metody obsługi zdarzenia (zdefiniowanej w klasie Słuchacza) z argumentem
obiekt-zdarzenie. Argument (obiekt klasy zdarzeniowej) zawiera informacje o
okolicznościach zajścia zdarzenia (np. komu się przytrafiło? kiedy? jakie ma inne
właściwości?).
W Javie standardowo zdefiniowano bardzo dużo różnych rodzajów zdarzeń i
interfejsów ich nasłuchu. Rozpatrzmy ogólne zasady na przykładzie zdarzenia "akcja"
(obiekt klasy ActionEvent), które powstaje:
– po kliknięciu w przycisk lub naciśnięciu spacji, gdy przycisk ma fokus (zdolność do
przyjmowania zdarzeń z klawiatury),
– po naciśnięciu ENTER w polu edycyjnym,
– po wyborze opcji menu,
– po podwójnym kliknięciu myszką / wciśnięciu ENTER na liście AWT (ale tylko
AWT, nie dotyczy to listy Swingu - JList) lub na liście rozwijalnej Swingu JComboBox
(powstaje - o ile do Źródła przyłączono odpowiedniego Słuchacza akcji – czyli obiekt
klasy implementującej interfejs nasłuchu akcji - ActionListener)
Uwaga: zdarzenie "akcja" jest zdarzeniem semantycznym - niezależnym od
fizycznego kontekstu (zauważmy jak w różnych fizycznych okolicznościach ono
powstaje – kliknięcie, naciśnięcie spacji lub ENTER, w różnych "fizycznych"
kontekstach – na przycisku, w polu edycyjnym, w menu). Nie należy go mylić ze
zdarzeniem kliknięcia myszką (czysto fizyczne zdarzenie).
41
Delegacyjny model obsługi zdarzeń
Przykład. Wyobraźmy sobie teraz, że w naszym GUI mamy jeden przycisk.
Klikając w ten przycisk (w tym programie) nie uzyskamy żadnych efektów (oprócz
widocznej – zagwarantowanej przez domyślny look&feel – zmiany stanu
przycisku: wyciśnięty – wciśnięty – wyciśnięty).
Chcemy by po kliknięciu w przycisk (lub naciśnięciu SPACJI, gdy przycisk ma
fokus) wyprowadzony został na konsolę napis "Wystąpiło zdarzenie!".
Kliknięcie lub naciśnięcie spacji na przycisku wywoła zdarzenie "akcja", jeśli do
przycisku przyłączymy słuchacza akcji. Musimy zatem stworzyć obiekt –
słuchacza akcji i przyłączyć go do przycisku. Klasa słuchacza akcji musi
implementować interfejs nasłuchu akcji (ActionListener), i w konsekwencji zdefiniować jego jedyną metodę – actionPerformed. W tej metodzie zawrzemy
kod, który zostanie uruchomiony po kliknięciu w przycisk, np. wyprowadzenie na
konsolę napisu.
public class Handler implements ActionListener {
public void actionPerformed(ActionEvent e) {
// instrukcje obsługujące zdarzenie
}
}
Klasa-słuchacz może też implementować większą liczbę interfejsów nasłuchu
(określamy w ten sposób zestaw obsługiwanych zdarzeń).
42
Delegacyjny model obsługi zdarzeń
Przyłączanie słuchacza do komponentu. Wszystkie komponenty Swing umożliwiają
przyłączanie/odłączanie określonych typów słuchaczy. Służą do tego metody:
addXXXListener() oraz removeXXXListener(),
gdzie XXX jest typem słuchacza.
Np.: zdarzenie ActionEvent może być obsłużone przez słuchacza implementującego
interfejs ActionListener. Słuchacz taki może być przyłączony do komponentów,
które mają dostęp do metody addActionListener(). Są to: Button, List,
TextField, MenuItem oraz klasy pochodne. Np.:
Handler sluchacz = new Handler();
przycisk.addActionListener(sluchacz);
Przedstawimy teraz trzy sposoby obsługi powyższego zdarzenia:
●
definiując osobną klasę słuchacza
●
implementując interfejs ActionListener w klasie, definiującej okno naszej
aplikacji
●
używając anonimowych klas wewnętrznych
W przypadku interfejsów z wieloma metodami obsługi, spośród których interesuje nas
tylko jedna lub dwie, lepszym rozwiązaniem może okazać się odziedziczenie
odpowiedniego adaptera w anonimowej klasie wewnętrznej niż implementacja
interfejsu w klasie GUI, gdyż obciąża nas ona koniecznością zdefiniowania pustych
metod obsługi (metod obsługi tych zdarzeń, którymi nie jesteśmy zainteresowani)
43
Delegacyjny model obsługi zdarzeń - przykład
import java.awt.event.*; // konieczny dla obsługi zdarzeń
import javax.swing.*;
//procedura obsługi
class Handler implements ActionListener {
public void actionPerformed(ActionEvent e) {
System.out.println("Wystąpiło zdarzenie!");
}
}
public class GUI extends JFrame {
public GUI() {
JButton b = new JButton(
"<html><center>Przycisk<br>reagujący na wciśnięcie</center></html>");
Handler h = new Handler();
b.addActionListener(h);//dodanie słuchacza
getContentPane().add(b);
setDefaultCloseOperation(DISPOSE_ON_CLOSE);
pack();
setLocationRelativeTo(null);
setVisible(true);
}
public static void main(String[] a) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
new GUI();
}
});
}
}
44
Delegacyjny model obsługi zdarzeń – przykład 2
Możemy implementować interfejs ActionListener w klasie, definiującej okno
naszej aplikacji:
class GUI extends JFrame implements ActionListener {
GUI() {
JButton b = new JButton("Przycisk");
b.addActionListener(this); //TEN obiekt będzie słuchaczem
getContentPane().add(b);
pack();
show();
}
public void actionPerformed(ActionEvent e)
{
System.out.println("Wystąpiło zdarzenie!");
}
}
public static void main(String[] a) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
new GUI();
}
});
}
45
Delegacyjny model obsługi zdarzeń – przykład 3
Możemy też implementację interfejsu nasłuchu umieścić w anonimowej klasie wewnętrznej:
public class InnerHandler extends JFrame {
ActionListener al = new ActionListener(){
public void actionPerformed(ActionEvent e){
System.out.println("Wystąpiło zdarzenie!");
}
};
public InnerHandler(){
JButton b = new JButton(
"<html><center>Przycisk<br>reagujący na wciśnięcie</center></html>");
b.addActionListener(al);//dodanie słuchacza
getContentPane().add(b);
setDefaultCloseOperation(DISPOSE_ON_CLOSE);
pack();
setLocationRelativeTo(null);
setVisible(true);
}
public static void main(String[] a) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
new InnerHandler();
}
});
}
}
46