Java FX - wprowadzenie
Chcąc tworzyć interfejsy graficzne w Javie byliśmy dotychczas skazani przede wszystkim na Swinga (lub ewentualnie SWT).
Od niedawna jednak, dzięki oficjalnemu włączeniu JavyFX do JDK w wersji 8, można ostatecznie zastąpić archaicznego i już nierozwijanego Swinga nowym narzędziem.
Wśród zalet trzeba zwrócić uwagę przede wszystkim na lepszą separację widoku od logiki aplikacji, co jest szczególnie widoczne, gdy zaczniemy korzystać z plików XFML
do definiowania kontrolek oraz plików CSS
do ich rozkładu.
Stworzenie nowej aplikacji opartej o bibliotekę JavaFX jest banalnie proste, odkąd znalazła się oficjalnie w JDK i przestało być wymagane podanie ścieżek systemowych do niej i używania specjalnych wtyczek do Mavena:
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>pl.wercia.example</groupId>
<artifactId>example-javafx</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<properties>
<maven.compiler.target>1.8</maven.compiler.target>
<maven.compiler.source>1.8</maven.compiler.source>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
</project>
Aplikacja sama w sobie to standardowa klasa rozszerzająca klasę abstrakcyjną javafx.application.Application
.
Najistotniejsza jest implementacja metody start(Stage primaryStage)
oraz uruchomienie w głównej metodzie main
aplikacji za pomocą statycznej metody launch(String… args)
.
import javafx.application.Application;
import javafx.stage.Stage;
public class MainApplication extends Application {
@Override
public void start(Stage primaryStage) throws Exception {
primaryStage.setTitle("Example JavaFX");
primaryStage.show();
}
public static void main(String[] args) {
Application.launch(args);
}
}
Zastanawiać może, czym jest obiekt typu javafx.stage.Stage
.
Jest to odpowiednik okna naszej aplikacji.
Okno aplikacji może zawierać w sobie instancję typu javafx.scene.Scene
, która definiuje rozkład poszczególnych komponentów.
Chcą przykładowo dodać do naszego okna prostą etykietę, należy stworzyć nowy obiekt javafx.scene.control.Label
, a następnie scenę z tym komponentem, którą z kolei możemy przypisać do okna naszego programu:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.stage.Stage;
public class MainApplication extends Application {
@Override
public void start(Stage primaryStage) throws Exception {
primaryStage.setTitle("Example JavaFX");
Label label = new Label("Example label");
Scene scene = new Scene(label);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
Application.launch(args);
}
}
Chcąc zobaczyć w czym JavaFX przewyższa swojego poprzednika, czyli bibliotekę Swing, koniecznie trzeba wykorzystać moc plików FXML
.
Same w sobie są to pliki XML
, które definiują rozkład komponentów.
Przykładowy tego typu plik, który definiuje pole tekstowe, przycisk i etykietę:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.ChoiceBox?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TextField?>
<VBox xmlns:fx="http://javafx.com/fxml/1">
<TextField fx:id="textField" />
<Button fx:id="button" text="Send" />
<Label fx:id="label" text="?" />
</VBox>
Plik ma bardzo prostą budowę: najpierw definiuje importy niezbędnych komponentów, a następnie położenie kontrolek na panelu głównym, w naszym wypadku jest to VBox
(układający komponenty jeden pod drugim).
Do wczytywania takich plików służy klasa javafx.fxml.FXMLLoader
.
Chcąc wczytać taki plik, wystarczy wskazać instancji tej klasy lokalizację pliku, a następnie wykorzystując metodę load()
załadować panel główny widoku.
Wczytany panel może zostać użyty do konstrukcji sceny dla okna głównego aplikacji.
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.stage.Stage;
public class MainApplication extends Application {
@Override
public void start(Stage primaryStage) throws Exception {
primaryStage.setTitle("Example JavaFX");
FXMLLoader loader = new FXMLLoader();
loader.setLocation(getClass().getResource("/Main.fxml"));
Pane mainPane = loader.<Pane>load();
Scene mainScene = new Scene(mainPane);
primaryStage.setScene(mainScene);
primaryStage.show();
}
public static void main(String[] args) {
Application.launch(args);
}
}
Chcąc zmienić standardowy wygląd komponentów, można bardzo łatwo podpiąć do sceny arkusz styli CSS
.
Należy pamiętać jedynie o tym, że własności komponentów JavyFX różnią się troszkę od tych znanych ze stron WWW
.
#textField {
-fx-padding: 20px;
-fx-border-insets: 10px;
-fx-background-insets: 10px;
}
#button {
-fx-padding: 20px;
-fx-border-insets: 10px;
-fx-background-insets: 10px;
}
#label {
-fx-padding: 10px;
-fx-border-insets: 10px;
-fx-background-insets: 10px;
-fx-text-fill: rgb(127,127,127);
-fx-font-size: 32;
-fx-min-width: 300px;
}
mainScene.getStylesheets().add(getClass().getResource("/main.css").toExternalForm());
Żeby aplikacja była użyteczna, warto dopisać jakąś logikę, która będzie sterować naszym widokiem.
Aby zachować zgodność ze wzorcem MVC
najlepiej skorzystać z kontrolera.
Kontroler to zwykła klasa Javy w której można w łatwy sposób wstrzyknąć komponenty zdefiniowane w naszym pliku widoku za pomocą adnotacji @FXML
:
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
public class MainController {
@FXML
private TextField textField;
@FXML
private Button button;
@FXML
private Label label;
}
Ponadto, aby widok wiedział, która klasa kontrolera jest przeznaczona dla niego, należy go wskazać w atrybucie fx:controller
dla naszego panelu.
Często po wczytaniu widoku niezbędna jest jego inicjalizacja, chociażby po to, aby przypiąć odpowiednie zdarzenia pod przyciski.
Mając stworzony kontroler jest to banalnie proste, dzięki interfejsowi javafx.fxml.Initializable
, który wymaga implementacji metody initialize(URL location, ResourceBundle resources)
:
import java.net.URL;
import java.util.ResourceBundle;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
public class MainController implements Initializable {
@FXML
private TextField textField;
@FXML
private Button button;
@FXML
private Label label;
@Override
public void initialize(URL location, ResourceBundle resources) {
button.setOnAction(e -> label.setText(String.format("Cześć %s", textField.getText())));
}
}
Oczywiście nic nie stoi na przeszkodzie, aby uzyskać dostęp do kontrolera z aplikacji.
Obiekt FXMLLoader
po załadowaniu widoku pozwala na pobranie referencji do kontrolera z pomocą metody getController()
.
Jeśli przykładowo nasz kontroler pozwala na ustawianie wartości pola tekstowego, można łatwo skorzystać z odpowiedniej metody, jeśli pobierzemy kontroler po załadowaniu widoku przez aplikację:
public void setTextFieldValue(String value) {
textField.setText(value);
}
MainController controller = loader.<MainController>getController();
controller.setTextFieldValue("Podaj imię");
Przykładowy projekt: