Nadszedł czas na pierwsze spotkanie z biblioteką JavaFX, którą wykorzystamy do stworzenia bardzo prostego programu z interfejsem graficznym (GUI). Będzie też trochę teorii.
Czym jest JavaFX?
Przez lata w Javie do tworzenia graficznych interfejsów użytkownika (ang. Graphical User Interface w skrócie GUI) służyła biblioteka graficzna Swing. W poprzedniej wersji kursu zamieściłem kilka wpisów na jej temat, możesz je znaleźć tutaj. W 2006 roku zaprezentowano nową platformę do tworzenia aplikacji z interfejsem graficznym w Javie, nazwaną JavaFX, wykorzystująca język JavaFX Script. Nie wchodząc w historyczne zawiłości, po kilku latach burzliwego rozwoju i zastąpieniu języka JavaFX Script przez Javę znalazła swoje miejsce, jako JavaFX 8 w standardowym wydaniu Javy 8. JavaFX została opracowana jako następca Swinga, ale zapewne jeszcze przez wiele lat obie biblioteki będą równolegle wykorzystywane przez programistów, szczególnie, że mogą ze sobą współpracować.
JavaFX pozwala na stosunkowo łatwe tworzenie wizualnie efektownych aplikacji, które mogą być uruchamiane jako samodzielne aplikacje na komputerach, jako aplety na stronach internetowych, aplikacje uruchamiane z przeglądarek a także jako programy na urządzeniach mobilnych. Wprowadzono też szereg udogodnień dla programistów i nowych możliwości w tworzeniu GUI, między innymi dotyczących wsparcia materiałów multimedialnych, animacji itd.
JavaFX – dwa podstawowe sposoby tworzenia aplikacji
Istnieją dwa podstawowe sposoby tworzenia GUI wykorzystującego JavaFX:
- z użyciem „czystej” Javy
- z użyciem plików FXML
Istnieją także narzędzia ułatwiające projektowanie interfejsu graficznego, na razie jednak zaczniemy od tworzenia programu stosując samą Javę, bez „ułatwiaczy”.
Pierwsza aplikacja
W NetBeans wybierz „New Project” a następnie „JavaFX | JavaFX Application”.
Następnie wybieramy nazwę projektu, niech się nazywa „JavaFXPierwszaAplikacja” i klikamy „Finish”.
Pokazuje się okienko z kodem, który za chwilę omówimy.
Naszą nową aplikację możemy uruchomić tradycyjnie klikając w ikonę z zieloną strzałką lub wciskając klawisz F6. Pokazuje się okienko:
Jak widać nie jest to zbyt skomplikowany GUI. Po kliknięciu w jedyny przycisk w okienku „Output” w NetBeans zostanie wypisany komunikat (cóż na niespodzianka!): Hello World!
.
Przyjrzyjmy się teraz kodowi, który został wygenerowany przez NetBeans:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
package javafxpierwszaaplikacja; import javafx.application.Application; import javafx.event.ActionEvent; import javafx.event.EventHandler; import javafx.scene.Scene; import javafx.scene.control.Button; import javafx.scene.layout.StackPane; import javafx.stage.Stage; public class JavaFXPierwszaAplikacja extends Application { @Override public void start(Stage primaryStage) { Button btn = new Button(); btn.setText("Say 'Hello World'"); btn.setOnAction(new EventHandler<ActionEvent>() { @Override public void handle(ActionEvent event) { System.out.println("Hello World!"); } }); StackPane root = new StackPane(); root.getChildren().add(btn); Scene scene = new Scene(root, 300, 250); primaryStage.setTitle("Hello World!"); primaryStage.setScene(scene); primaryStage.show(); } public static void main(String[] args) { launch(args); } } |
Po linii z informacją na temat nazwy pakietów, mamy serię poleceń importu, który jak zwykle pozwala używać poszczególnych klas. Nazwy wszystkich z nich rozpoczynają się of javafx.
.
Deklaracja klasy wygląda tak:
public class JavaFXPierwszaAplikacja extends Application
,
co oznacza, że jest ona pochodną klasy Application
.
Następnie znajduje się deklaracja metody poprzedzona adnotacją:
@Override
public void start(Stage primaryStage)
Adnotacja @Override
, oznacza, że następująca po nim metoda nadpisuje metodę z klasy bazowej. Sama nazwa metody: start
sugeruje, że odpowiada ona za uruchomienie aplikacji. A co z metodą main
? Zauważ, że zawiera ona tylko jedno polecenie: launch(args);
, które też jest dość jednoznaczne. Wróćmy zatem do metody start
.
Kolejny fragment kodu wygląda tak:
Button btn = new Button();
btn.setText("Say ‘Hello World’");
btn.setOnAction(new EventHandler
@Override
public void handle(ActionEvent event) {
System.out.println("Hello World!");
}
});
Najpierw tworzony jest obiekt typu Button
, o nazwie btn
. Łatwo się domyślić, że obiekt ten bedzie odpowiadał przyciskowi, który widzieliśmy w oknie naszej aplikacji.
Obiekt typu Button
jest jedną z wielu kontrolek (ang. control) używanych w tworzeniu aplikacji graficznych. Kontrolkami są także inne elementy, które znasz z tego typu aplikacji, typu okienka do wpisywania tekstu, ikony, etykiety z wyświetlającym się tekstem, okienka dialogowe, rozwijane menu etc. o czym będzie jeszcze mowa.
Wróćmy jednak na razie do kodu. Kolejna linia odpowiada za ustawienie tekstu, który wyświetla się na przycisku. Możesz go zmienić na przykład na „Witaj Świecie” i sprawdzić efekt. Zwróć uwagę, że metoda odpowiedzialna za ustawianie tekstu nazywa się setTekst
co dość jasno wskazuje na jej funkcje a ponadto, że jest ona zgodna z zasadami nazywania modyfikatorów (setterów).
Kolejne linie kodu mogą być mniej zrozumiałe. Wywołujemy tam na obiekcie btn
metodę setOnAction
. Nazwa sugeruje, że odpowiada ona za działanie, które jest wykonywane w przypadku „Akcji” czyli wydarzenia związanego z obiektem btn
. W nawiasie przekazujemy argument który wygląda jak coś pomiędzy deklaracją klasy a tworzeniem obiektu. Jest to tzw. klasa anonimowa o czym powiem więcej przy innej okazji. Zauważ, że nie deklarujemy obiektu i nie przypisujemy mu żadnej nazwy. Tu tworzymy klasę anonimową bezpośrednio w miejscu, gdzie zwykle wpisuje się argument metody, ale można ten fragment rozpisać też w ten sposób:
EventHandler
@Override
public void handle(ActionEvent event) {
System.out.println("Hello World!");
}
};
btn.setOnAction(ae);
Nowością jest także to, że zamiast zakończyć linię tworzącą obiekt średnikiem, otwieramy nawias klamrowy a następnie piszemy kod, który w dodatku zawiera nadpisywaną metodę. Nie wnikając w szczegóły, przyjmij, że tak po prostu też można.
Niejasne też jest znaczenie fragmentu: <ActionEvent>
. Wyjaśnię to dokładniej w przyszłości, na razie powiem, że oznacza to w uproszczeniu, że obiekt będzie przyjmował obiekty typu ActionEvent
.
Obiekt typu EventHandler
odpowiedzialny jest, jak sama nazwa wskazuje, za obsługę wydarzeń. Metoda handle
właśnie je obsługuje, czyli wykonuje dany kod w przypadku, gdy wydarzenie zajdzie. W naszym przypadku wydarzeniem będzie kliknięcie przycisku btn
. Wtedy, jak wynika z kodu zostanie wypisany w terminalu tekst „Hello World!”. Oczywiście można tam wpisać dowolny inny kod (spróbuj).
Teraz mamy utworzony i skonfigurowany obiekt typu Button
ale nie oznacza to, że będzie on wyświetlany na ekranie, właściwie póki co, nie ma gdzie się wyświetlać. Przyjrzymy się zatem kolejnym częściom kodu:
StackPane root = new StackPane();
– tu tworzymy obiekt typu StackPane
o nazwie root
. Jest to element odpowiedzialny za rozmieszczenie kontrolek, które będziemy w nim umieszczać.
Dodajemy kontrolkę przycisku btn
:
root.getChildren().add(btn);
Następnie tworzony jest obiekt typu Scene
:
Scene scene = new Scene(root, 300, 250);
Określa on wielkość wyświetlanego okna oraz element, który się w nim znajdzie. Jak widać, będzie to utworzony wcześniej obiekt root
, zawierający z kolei przycisk (btn
). Pierwsza z liczb oznacza szerokość okna, druga jego wysokość, wyrażone w liczbie pikseli. Spróbuj pozmieniać te wartości i zaobserwować jak zmienia się wielkość okna.
Teraz trzy kolejne linie:
primaryStage.setTitle("Hello World!");
primaryStage.setScene(scene);
primaryStage.show();
Odpowiadają one kolejno za: ustawienie tytułu okna, umieszczenie obiektu scene
oraz (w końcu!) wyświetlenie okna na ekranie. O ich znaczeniu możesz się przekonać poprzez zakomentowanie po kolei każdej z nich i uruchamianiu za każdym razem aplikacji. W przypadku ostatniego polecenia nie będziesz mógł zamknąć okna (które się nie pokaże), możesz natomiast zakończyć działanie aplikacji poprzez kliknięcie ikony ze znakiem „x” na dole okna:
Trochę teorii
Jak można się było przekonać na podstawie przykładu powyżej, aplikacje w JavaFX mają charakter hierarchiczny. Obiekt typu Button
umieszczaliśmy w obiekcie typu StackPane
, ten z kolei w obiekcie typu Scene
, który był umieszczony w obiekcie primaryStage
. Wszystkie elementy, które umieszczamy w Scene
są węzłami (ang. node).
Taki obraz aplikacji może na pierwszy rzut oka przypominać zależność liniową. Obiekt, który znajduje się wyżej w hierarchii jest „rodzicem” (ang. parent) a ten, który znajduje się niżej jest „dzieckiem” (ang. child). Na przykład obiekt btn
jest dzieckiem obiektu root
.
Rzeczywisty obraz zależności w aplikacji jest jednak bardziej złożony, relacje między obiektami przypominają raczej drzewo (do góry korzeniem) niż drabinę. Węzeł może być rodzicem dla wielu innych węzłów. Jeśli nie posiada węzłów potomnych, nazywany jest liściem (ang. leaf).
Podstawowym, „szczytowym” elementen aplikacji graficznej w JavaFX jest stage
, która określa okno jest „kontenerem” dla wszystkich elementów znajdujących się w oknie aplikacji. Stage
zawiera scene
(nie będę tych terminów tłumaczył na język Polski, obie można rozumieć jako „scenę”), która z kolei zawiera całe drzewo składające się z węzłów. To w obiekcie typu Scene
definiowaliśmy rozmiary okna i w niej umieszczaliśmy obiekt root
, który znajdował się stopień niżej w hierarchii.
Obiekt root
jest przykładem panelu – obiektu służącego do rozmieszczania innych obiektów w aplikacji. W naszym programie zawierał jeden obiekt, przycisk, ale może zawierać ich więcej. Powyżej linii, w której tworzyliśmy obiekt btn
dopisz linię:
Circle kolko = new Circle(0,0,30, Color.RED);
Nie trzeba wielkiej inteligencji aby się domyśleć, że właśnie tworzymy obiekt, który będzie czerwonym kółkiem.
Następnie poniżej polecenia, w którym dodaliśmy do niego obiekt btn
dopisz:
root.getChildren().add(kolko);
Pamiętaj o zaimportowaniu odpowiednich bibliotek.
W aplikacji pojawiło się czerwone kółko:
Kółko zasłania przycisk. Jak widać, kontener typu StackPane
umieszcza elementy jeden nad drugim. Możemy zamienić miejscami dwie linie kodu:
root.getChildren().add(kolko);
root.getChildren().add(btn);
Teraz przycisk jest powyżej, ponieważ został dodany później:
Ponieważ zarówno obiekt btn
jak i kolko
nie mają dzieci, są liśćmi (tak, to brzmi trochę dziwnie).
Zależności opisanych obiektów schematycznie można przedstawić tak:
Rodzaje węzłów
Jak powyżej wspomniałem obiekty typu Stage
zawierają węzły, są one rozszerzeniem klasy Node
. Można wyróżnić trzy podstawowe rodzaje węzłów:
- figury geometryczne (węzły prymitywne) i tekst – np.
Circle
,Text
,Rectangle
,Line
,Polygon
. - panele pozwalające na kontrolę rozmieszczenia elementów interfejsu graficznego (layoutu) – np.
StackPane
,GridPane
,FlowPane
. - kontrolki czyli wszelkiego rodzaju elementy typu przyciski, okienka, suwaki czy menu – np.
Button
,ColorPicker
,Label
,Menu
Oczywiście w kolejnych lekcjach zapoznamy się z niektórymi z nich ale bardzo wygodny przegląd dostępnych elementów można znaleźć w aplikacji „Ensemble”, którą można pobrać tutaj (Java SE Development Kit 8u77 Demos and Samples Downloads). Znajdziesz tam także przykłady ich zastosowania, wraz z kodami źródłowymi.
Uwaga: Obecnie nie jest dostępna na stronie Oracle wykonywalny plik z aplikacją Ensemble. Aby ją uruchomić należy ją najpierw otworzyć w NetBeans jako projekt. Po rozpakowaniu pobranego archiwum, należy:
- Otworzyć NetBeans
- Wybrać z menu File->Open Project… (lub użyć odpowiedniej ikony/skrótu klawiszowego),
- Z katalogu, który uzyskaliśmy wybrać:
demo/javafx_samples/src/Ensemble8
W okienku z otwartymi projektami powinien pojawić się projekt Ensemble8. Może się jednak okazać, że otworzy się okienko z komunikatem „One or more project resources could not be found…”.
Problem może być spowodowany tym, że NetBeans nie może odnaleźć JDK 8. W takim wypadku należy:
- Zamknąć okno z komunikatem
- Na ikonie projektu wybrać „Properties”
- W kategoriach wybrać „Libraries”
- Po prawej, na górze pojawi się, przy etykiecie „Java Platform:” rozwijaną listę a w niej zaznaczona na czerwono opcja „Missing platform: JDK_1.8 (Custom)”
- Wybieramy z listy istniejącą platformę, u mnie jest to „JDK 1.8 (Default)”
- Po chwili z ikon projektu powinny zniknąć wykrzykniki oznaczające błędy
Projekt uruchamiamy jak zwykle. Powinno pojawić się okno, które będzie wyglądało tak:
Oczywiście nie należy także zapominać o dokumentacji.
Na koniec…
Na koniec, we fragmencie kodu odpowiadającą za akcję wykonywaną przy kliknięciu w przycisk, dopisz linię kodu:
kolko.setFill(Color.GREEN);
Sprawdź jaki będzie efekt.
[…] http://ggoralski.pl/?p=1859 […]
Fajny tutorial. Tym fajniejszy, że dotyczy dość mało upowszechnionej i tym samym słabo udokumentowanej technologii. Dzięki niemu usiłuję przypomnieć sobie technologię Javy aby napisać prostą aplikacyjkę użytkową.
Tytułem uzupełnienia – aby wyświetlić czerwone kółko w postaci opisanej na tej stronie należy zaimportować odpowiednie klasy:
import javafx.scene.shape.Circle;
import javafx.scene.paint.Color;
Nie zostało to opisane explicite a kompletny nowicjusz może na tym się „zatknąć”.
Dziękuję i życzę wytrwałości na niwie tutorialowej:)