JavaFX i OpenJFX
JavaFX - historia i pozycja w ekosystemie
- JavaFX - zestaw bibliotek do tworzenia nowoczesnych aplikacji desktopowych w Javie
- Od JDK 8 wbudowany w Oracle JDK jako nowoczesna alternatywa dla AWT/Swing
- Od JDK 11 nie jest już częścią JDK, rozwijany osobno jako OpenJFX
- OpenJFX - otwartoźródłowy projekt rozwijający JavaFX przez społeczność, Oracle i firmę Gluon
- Aktualne wydania: Gluon OpenJFX
Cechy JavaFX
- architektura oparta o graf
Scene Graph - deklaratywne UI (FXML)
- stylowanie CSS
- multimedia, wsparcie dla animacji
- obsługa grafiki 2D i 3D oraz renderowania sprzętowego (akceleracja GPU)
- duża liczba wbudowanych kontrolek i możliwość rozbudowy
- WebView, silnik WebKit do osadzania treści webowej
- integracja z Swing (
SwingNode) - mechanizm danych reaktywnych (
Observable,Property,Binding)
Warstwy JavaFX
- Prism - akcelerowany potok renderowania (GPU: DirectX/OpenGL).
W przypadku braku wsparcia sprzętowego JavaFX przechodzi w tryb renderowania programowego. - Glass Windowing Toolkit - warstwa okien, zdarzeń i timerów; interfejs między JavaFX a systemem operacyjnym.
- WebView - kontrolka osadzająca HTML/JS (silnik WebKit) umożliwiająca renderowanie i interakcję z treścią webową.
- Media Engine - silnik multimediów (audio/video) obsługujący różne formaty i kodeki.
- Quantum Toolkit - łączy warstwy niskopoziomowe z API JavaFX.
- Scene Graph - drzewo węzłów UI i grafiki
- JavaFX API - publiczne klasy i interfejsy do budowy interfejsu graficznego
JavaFX SDK API
javafx.base- podstawowe klasy, kolekcje, propertiesjavafx.graphics- grafika 2D/3D, scena, renderowaniejavafx.controls- gotowe kontrolki UIjavafx.fxml- FXML, loader, kontroleryjavafx.media- multimediajavafx.web- WebView, silnik WebKitjavafx.swing- integracja z Swing (SwingNode)
Struktura aplikacji JavaFX
- Stage - okno aplikacji - scena teatralna, gdzie odbywa się przedstawienie
- Scene - zawartość okna - konkretna scenografia, rekwizyty i aktorzy ustawieni na scenie, zawiera hierarchię węzłów (
Scene Graph) - Node - elementy UI, grafiki
Scene Graph
- Każdy element UI (obiekty 2D/3D, kontrolki UI, kontenery, elementy multimedialne) jest węzłem grafu (
Node) - Renderowanie, transformacje i zdarzenia propagują się po drzewie
- Zmiana stanu węzła natychmiast widoczna w interfejsie
Elementy Scene Graph
Sceneto główny kontener, w którym umieszczamy hierarchię węzłów- Korzeń (
Root) - główny węzeł sceny, zwykle layout (np.VBox,BorderPane) - Węzły rodzicielskie (
Parent) - zawierają inne węzły (dzieci)- elementy grupujące (
Group), - układy (
Pane,HBox,StackPane,GridPane) - kontrolki (
Button,TextField,Label), też są rodzicami i mogą zawierać wewnętrzne węzły
- Liście - elementy graficzne 2D/3D (
Shape,ImageView,Text,SwingNode)
VBox root = new VBox(); Label title = new Label("Kurs JavaFX"); Button button = new Button("Kliknij"); root.getChildren().addAll(title, button); Scene scene = new Scene(root, 600, 400);
Cykl życia aplikacji
- Punkt wejścia: klasa dziedzicząca po
Application - Kluczowa metoda:
start(Stage stage)udostępniająca głównyStage.
W metodziestart():- przygotowujemy scenę (tworzymy węzły, układamy je w layoutach)
- ustawiamy
Stage(tytuł, rozmiar, scenę) - wywołujemy
show(), aby wyświetlić okno.
- Uruchomienie:
launch(args)wmain()
import javafx.application.Application; import javafx.scene.Scene; import javafx.scene.control.Label; import javafx.stage.Stage; public class HelloFxApp extends Application { @Override public void start(Stage stage) { Scene scene = new Scene(new Label("Witaj JavaFX"), 400, 200); stage.setTitle("Hello FX"); stage.setScene(scene); stage.show(); } public static void main(String[] args) { launch(args); } }
Konfiguracja JavaFX w IntelliJ
JavaFX nie jest już częścią JDK. Istnieje kilka sposobów na dodanie JavaFX do projektu:
- instalacja ręczna - pobierz OpenJFX, dodaj biblioteki do projektu, ustaw VM options
- z użyciem Maven lub Gradle - dodaj zależności do
pom.xmllubbuild.gradle - szablon projektu JavaFX w IntelliJ (plugin JavaFX, używa Maven) - brakujące zależności powinny zostać pobrane automatycznie
Dokumentacja OpenJFX zawiera szczegółowe instrukcje dla różnych IDE i systemów budowania.
Ręczna konfiguracja JavaFX
- Dodaj biblioteki JavaFX do projektu
File→Project Structure→Libraries - Ustaw ścieżkę do modułu w VM options przy uruchamianiu:
Build→Edit Configurations→VM options
--module-path "ścieżka/do/javafx-sdk-XX/lib" --add-modules javafx.controls,javafx.fxml
Konfiguracja JavaFX z użyciem Maven
Maven - system zarządzania projektem i budowania, który automatycznie pobiera zależności i konfiguruje projekt. Jak skonfigurować JavaFX z Maven:
- Utwórz projekt Maven (lub użyj archetypu JavaFX)
- Wyszukaj JavaFX w repozytorium Maven Maven Central lub https://mvnrepository.com/
- Dodaj zależności
javafx-controls(opcjonalniejavafx-fxml) do plikupom.xml
- Skonfiguruj plugin
javafx-maven-plugindo uruchamiania (zdarzeniejavafx:run) orazmaven-compiler-plugin - Uruchamiaj (z CLI) przez zadanie Maven, z okna narzędzi Maven IDE albo skonfiguruj IDE
mvn javafx:run
Przykładowa konfiguracja Maven dla JavaFX
<dependencies> <dependency> <groupId>org.openjfx</groupId> <artifactId>javafx-controls</artifactId> <version>26</version> </dependency> <dependency> <groupId>org.openjfx</groupId> <artifactId>javafx-fxml</artifactId> <version>26</version> <scope>compile</scope> </dependency> </dependencies>
Pluginy Maven dla JavaFX
<build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.15.0</version> <configuration> <source>25</source> <!-- Match your JDK version --> <target>25</target> </configuration> </plugin> <plugin> <groupId>org.openjfx</groupId> <artifactId>javafx-maven-plugin</artifactId> <version>0.0.8</version> <executions> <execution> <id>default-cli</id> <configuration> <mainClass>MyJavaFXApp</mainClass> <!-- Your main class --> </configuration> </execution> </executions> </plugin> </plugins> </build>
Uruchamianie aplikacji JavaFX z IDE
- Opcja 1: Okno narzędzi Maven
Lifecycle→cleanPlugins→javafx→javafx:run
- Opcja 2: Dodaj konfigurację uruchomienia w IntelliJ
Run→Edit Configurations- Dodaj
+→Maven - W polu
Command linewpisz:clean javafx:run
Wątki i JavaFX
- JavaFX ma własny wątek UI (
JavaFX Application Thread) uruchamiany automatycznie przy starcie aplikacji - Wszystkie operacje na węzłach muszą być wykonywane w tym wątku
- Aby zachować responsywność, długotrwałe zadania powinny być wykonywane w osobnych wątkach, np. przez
TasklubService, a aktualizacje UI przezPlatform.runLater()
Layouty
javafx.scene.layout- układy (layout) - specjalne kontenery, które automatycznie rozmieszczają swoje dzieci według określonych regułHBox,VBox,ButtonBar- układ poziomy/pionowy, rozmieszcza dzieci w liniiBorderPane- układ z pięcioma regionami (top, bottom, left, right, center)SplitPane- układ dzielący przestrzeń na panele o regulowanej wielkościGridPane- układ siatkowy, rozmieszcza dzieci w wierszach i kolumnachFlowPane- układ przepływowy, rozmieszcza dzieci w linii, zawija do następnego wierszaAnchorPane- układ kotwiczący, pozwala przypiąć dzieci do krawędziStackPane- nakłada dzieci jedno na drugie, centrowanie
StackPane stackPane = new StackPane(); Ellipse ellipse = new Ellipse(110, 70); ellipse.setFill(Color.LIGHTBLUE); Text text = new Text("Hello World"); text.setFont(new Font("Arial Bold", 24)); stackPane.getChildren().addAll(ellipse, text); Scene scene = new Scene(stackPane, 350, 230, Color.LIGHTYELLOW);
Efekty graficzne
- Effect - wbudowane efekty graficzne do stylizacji węzłów:
- DropShadow - efekt cienia, można dostosować kolor, rozmycie i przesunięcie
- Reflection - efekt odbicia, można dostosować wysokość i rozmycie
- Wypełnienie kształtu:
Color,ImagePattern,RadialGradient- LinearGradient - gradient liniowy, definiowany przez punkty początkowy i końcowy oraz zestaw
Stop(pozycja + kolor)
Stop[] stops = new Stop[] { new Stop(0, Color.DODGERBLUE), new Stop(0.5, Color.LIGHTBLUE), new Stop(1.0, Color.LIGHTGREEN) }; LinearGradient gradient = new LinearGradient(0, 0, 1, 1, true, CycleMethod.NO_CYCLE, stops); ellipse.setFill(gradient); ellipse.setEffect(new DropShadow(30, 10, 10, Color.GRAY));
Animacje
- System animacji oparty na klasie
TransitioniTimeline - Można animować właściwości węzłów (pozycja, rozmiar, kolor) oraz tworzyć sekwencje animacji
- Animacje są płynne dzięki renderowaniu sprzętowemu
FadeTransition,RotateTransition,ScaleTransition,TranslateTransition- gotowe klasy do animacji określonych właściwościTimeline- bardziej elastyczna klasa do animowania dowolnych właściwości w określonych punktach czasowychParallelTransitioniSequentialTransition- do łączenia wielu animacjiplay()- uruchamia animację,stop()- zatrzymuje,pause()- wstrzymuje,getStatus()- sprawdza status animacji
RotateTransition rotate = new RotateTransition(Duration.millis(2500), stackPane); rotate.setToAngle(360); rotate.setFromAngle(0); rotate.setInterpolator(Interpolator.LINEAR); rotate.play();
Obsługa zdarzeń
- Event - obsługa zdarzeń (myszy, klawiatury, akcji)
- Można rejestrować nasłuchiwacze zdarzeń na węzłach grafu
- Zdarzenia propagują się po drzewie, rodzic może przechwycić zdarzenie dziecka
setOnAction(EventHandler<ActionEvent>),setOnMouseClicked(EventHandler<MouseEvent>)dla kontrolek (węzłów) obsługujących konkretne zdarzenia,addEventHandler(EventType<T>, EventHandler<T>)dla ogólnych zdarzeń
stackPane.setOnMouseClicked(mouseEvent -> { if (rotate.getStatus().equals(Animation.Status.RUNNING)) { rotate.pause(); } else { rotate.play(); } });
Obserwowanie i wiązanie właściwości
- JavaFX posiada wbudowany mechanizm danych reaktywnych, możemy wiązać właściwości węzłów z innymi właściwościami lub źródłami danych, tak aby zmiany były automatycznie odzwierciedlane w interfejsie
- właściwości węzłów są obserwowalne np.
textProperty()dlaLabelczyTextField, możemy ustawić nasłuchiwacz reagujący na zmiany lub powiązać je z innymi właściwościami - Observable oraz ObservableValue - interfejsy reprezentujące obserwowalną wartość, posiadają metody do dodawania nasłuchiwaczy
addListener()i pobierania aktualnej wartości- InvalidationListener - reaguje na zmianę
- ChangeListener - reaguje na zmianę wartości, dostarcza starą i nową wartość
Przykład
rotate.statusProperty().addListener(new InvalidationListener() { @Override public void invalidated(Observable observable) { text.setText("Animation status: " + ((ObservableObjectValue<Animation.Status>)observable).getValue()); } });
lub krócej
rotate.statusProperty().addListener(observable -> { text.setText("Animation status: " + rotate.getStatus()); });
Binding
- Binding pozwala na tworzenie dynamicznych powiązań między właściwościami implementującymi interfejs Property<T> bez potrzeby dodawania ręcznych nasłuchiwaczy
bind()- tworzy jednokierunkowe powiązanie, gdzie jedna właściwość jest zależna od drugiej
text.rotateProperty().bind(stackPane.rotateProperty());
- przekształcanie wartości do tworzenia złożonych powiązań, np. matematycznych operacji, warunków logicznych, formatowania tekstu
text2.textProperty().bind(stackPane.rotateProperty().asString("%.1f"));
- łączenie wielu właściwości i operacji na nich, np. konkatenacja tekstu z dwóch
TextFielddoLabel
TextField firstName = new TextField(); TextField lastName = new TextField(); Label fullName = new Label(); fullName.textProperty().bind( firstName.textProperty().concat(" ").concat(lastName.textProperty()) );
Wiązanie dwukierunkowe
bindBidirectional()- dwukierunkowe powiązanie, zmiana jednej właściwości automatycznie aktualizuje drugą i odwrotnie- przydatne w formularzach, gdzie chcemy, aby zmiany w UI były odzwierciedlane w modelu danych i na odwrót
firstName.textProperty().bindBidirectional(model.firstNameProperty());
FXML - deklaratywny język UI
- FXML to XML opisujący hierarchię UI, pozwala na oddzielenie logiki od warstwy prezentacji
FXMLLoaderwczytuje i parsuje plik FXML, tworzy obiekty UI i ustawia je w hierarchii
<?xml version="1.0" encoding="UTF-8"?> <?import javafx.scene.control.*?> <?import javafx.scene.layout.*?> <VBox xmlns:fx="http://javafx.com/fxml"> <Button text="OK" /> <Label text="Witaj JavaFX" /> </VBox>
FXMLLoader loader = new FXMLLoader(getClass().getResource("main-view.fxml")); Parent root = loader.load(); Scene scene = new Scene(root); stage.setScene(scene);
Scene Builder
- Scene Builder: Gluon Scene Builder
niezależna aplikacja do projektowania interfejsu drag-and-drop - Integracja z IntelliJ: JavaFX w IntelliJ
Elementy FXML
- Instancje klas, np.
<Button>tworzy obiekt klasyjavafx.scene.control.Button - Własności obiektów (settery, kolekcje)
<Button> <text>Hello, World!</text> </Button>
- Własności statyczne, np.
GridPane.rowIndex
<Label text="My Label"> <GridPane.rowIndex>0</GridPane.rowIndex> <GridPane.columnIndex>0</GridPane.columnIndex> </Label>
- Element
<children>jest używany do definiowania listy dzieci
<VBox> <children> <Button text="OK" /> <Label text="Witaj JavaFX" /> </children> </VBox>
- Klasy mogą definiować domyślne własności, np.
<children>dla węzłow rodzicielskich (np.VBoxiHBox)
<VBox> <Button text="OK" /> <Label text="Witaj JavaFX" /> </VBox>
Atrybuty FXML
- Atrybuty elementów mapują się na właściwości obiektów, np.
text=„OK”dlaButtonustawia właściwośćtextobiektuButton
<Button text="OK" /> - Można używać atrybutów do ustawiania prostych wartości, takich jak tekst, kolory, rozmiary, ale bardziej złożone struktury (np. hierarchia węzłów) wymagają elementów zagnieżdżonych
- Ustawianie własności statycznych
<Label text="My Label" GridPane.rowIndex="0" GridPane.columnIndex="0"/>
- Specjalne atrybuty FXML:
fx:controller- określa klasę kontrolera powiązaną z tym widokiemfx:id- identyfikator węzła, pozwala m.in. na powiązanie z polem kontroleraonAction- przypisuje metodę obsługi zdarzenia (np. kliknięcia) z kontrolera
- Powiązanie z własnością kontrolera
<Label text="${controller.someProperty}" />
Atrybuttextjest ustawiany na wartość właściwościsomePropertyz kontrolera, a aktualizacje tej właściwości będą automatycznie odzwierciedlane w interfejsie.
FXML + Controller
- Wzorzec MVC: pliki FXML definiują strukturę interfejsu (widok), a kontrolery Java obsługują logikę i zdarzenia
// MainController.java public class MainController { @FXML private TextField input; @FXML private Label output; @FXML private void onGenerate() { output.setText("Witaj, " + input.getText().trim()); } }
<!-- main-view.fxml --> <VBox spacing="8" xmlns:fx="http://javafx.com/fxml" fx:controller="demo.MainController"> <TextField fx:id="input" promptText="Podaj imię" /> <Button text="Generuj" onAction="#onGenerate" /> <Label fx:id="output" /> </VBox>
Inicjalizacja kontrolera
- Metoda
initialize()interfejsuInitializablew kontrolerze jest wywoływana automatycznie po załadowaniu FXML i wstrzyknięciu zależności - Można jej użyć do dodatkowej konfiguracji elementów UI, ustawienia nasłuchiwaczy, inicjalizacji danych
public class MainController implements Initializable { @FXML private TextField input; @FXML private Label output; @Override public void initialize(URL location, ResourceBundle resources) { input.setText("Jan"); output.setText("Witaj, " + input.getText()); } }
FXMLLoader
FXMLLoader- klasa odpowiedzialna za wczytywanie i parsowanie plików FXML, tworzenie obiektów UI i hierarchiiload()- wczytuje FXML i zwraca korzeń hierarchii (Parent)getController()- zwraca instancję kontrolera powiązanego z tym FXMLsetLocation(URL)- ustawia lokalizację FXMLsetResources(ResourceBundle)- ustawia zasoby (dla internacjonalizacji)
URL location = getClass().getResource("example.fxml"); ResourceBundle resources = ResourceBundle.getBundle("com.foo.example"); FXMLLoader fxmlLoader = new FXMLLoader(location, resources); Pane root = (Pane)fxmlLoader.load(); MyController controller = (MyController)fxmlLoader.getController();
Metoda load()
- Odczyt pliku FXML, tworzenie obiektów i ustawienie właściwości węzłów
- Utworzenie instancji kontrolera (jeśli
fx:controllerjest określony) - Wstrzyknięcie zależności do kontrolera (pola publiczne lub oznaczone
@FXMLi odpowiadające imfx:idw FXML) - Rejestrowanie nasłuchiwaczy zdarzeń (np.
onAction=#methodName) - Wywołanie metody
initialize()kontrolera (jeśli implementujeInitializablelub posiada metodę oznaczoną@FXMLo nazwieinitialize)
CSS w JavaFX
- Stylowanie podobne do webowego CSS, ale z prefiksem
-fx- - Plik CSS podpinamy pod scenę lub dowolnego węzła rodzicielskiego:
scene.getStylesheets().add(getClass().getResource("Style.css").toExternalForm());
- Style można dodać w FXML
<stylesheets> <URL value="@demo.css" /> </stylesheets> <VBox stylesheet="@Style.css"> <Button text="Submit" id="primaryButton" styleClass="primaryButton"/> </VBox>
- Style propagują się w dół hierarchii
- domyslny plik CSS dla JavaFX:
modena.css- zawiera style dla wszystkich wbudowanych kontrolek, można go nadpisać lub rozszerzyć
Powiązanie stylów z węzłami
- Bezpośredni styl dla pojedynczego węzła (style inline)
button.setStyle("-fx-font-size: 16px; ");
- Selektory CSS w pliku CSS (klasy, identyfikatory) pozwalają na grupowanie i ponowne użycie stylów
- Style można określić w FXML (właściwości
styleClassiid) i konfigurować w Scene Builder
<Label text="Hello" styleClass="greeting"/> <Button text="Submit" id="primaryButton"/>
- Style można określić w kodzie Java, dodając klasy CSS do węzłów lub ustawiając identyfikatory
label.getStyleClass().add("greeting"); button.setId("primaryButton");
Selektry CSS JavaFX
/* Plik CSS dla JavaFX */ /* Selekcja po nazwie węzła */ Button { -fx-background-color: lightblue; -fx-text-fill: darkblue; } /* Selekcja po klasie CSS */ .primaryButton { -fx-background-color: linear-gradient(to right, #ff7e5f, #feb47b); -fx-text-fill: white; -fx-font-size: 16px; -fx-padding: 10px 20px; -fx-background-radius: 8px; } /* Selekcja po identyfikatorze */ #primaryButton { -fx-background-color: linear-gradient(to right, #ff7e5f, #feb47b); -fx-text-fill: white; -fx-font-size: 16px; -fx-padding: 10px 20px; -fx-background-radius: 8px; } /* Selekcja pseudoklasy */ Button:hover { -fx-background-color: linear-gradient(to right, #feb47b, #ff7e5f); }
Priorytety stylowania
- Styl inline
- Identyfikator CSS (
#id) - Klasa CSS (
.class) - Styl domyślny węzła
Materiały
Zadanie 8: Lista zakupów
Stwórz prostą aplikację JavaFX do zarządzania listą zakupów.
Wymagania:
- Aplikacja powinna umożliwiać dodawanie nowych pozycji do listy oraz usuwanie zaznaczonych pozycji.
- Użyj
ListViewdo wyświetlania listy,TextFielddo wprowadzania nowych pozycji - Przycisk
Dodajpowinien być aktywny tylko wtedy, gdy pole tekstowe nie jest puste (użyj wiązania) - Przycisk
Usuńpowinien usuwać aktualnie zaznaczoną pozycję z listy (lub wiele zaznaczonych pozycji) - Zadeklaruj widok w FXML. Wygląd aplikacji dowolny. Zaprojektuj samodzielnie, najlepiej z pomocą Scene Buildera
- Zdefiniuj klasę kontrolera i powiąż go z widokiem
- Zastosuj stylowanie CSS do poprawy wyglądu interfejsu
Wskazówki:
disableProperty().bind(…)dla przyciskugetSelectionModel().getSelectedIndex()dla usuwania- Puste wejście ignoruj przez
trim().isEmpty()





