Einführung in JavaFx

1. Einleitung

JavaFX ist eine Bibliothek zum Erstellen von Rich Client-Anwendungen mit Java. Es bietet eine API zum Entwerfen von GUI-Anwendungen , die auf fast jedem Gerät mit Java-Unterstützung ausgeführt werden.

In diesem Tutorial konzentrieren wir uns auf einige seiner wichtigsten Fähigkeiten und Funktionen.

2. JavaFX-API

In Java 8, 9 und 10 ist kein zusätzliches Setup erforderlich, um mit der JavaFX-Bibliothek zu arbeiten. Das Projekt wird ab JDK 11 aus dem JDK entfernt.

2.1. Die Architektur

JavaFX verwendet für das Rendern eine hardwarebeschleunigte Grafikpipeline, die als Prism bezeichnet wird . Um die Grafiknutzung vollständig zu beschleunigen, werden außerdem Software- oder Hardware-Rendering-Mechanismen genutzt, indem DirectX und OpenGL intern verwendet werden .

JavaFX verfügt über eine plattformabhängige Glass Windowing Toolkit-Schicht, mit der eine Verbindung zum nativen Betriebssystem hergestellt werden kann . Es verwendet die Ereigniswarteschlange des Betriebssystems, um die Thread-Nutzung zu planen. Außerdem werden Fenster, Ereignisse und Timer asynchron verarbeitet.

Die Medien- und Web- Engines ermöglichen die Medienwiedergabe und die HTML / CSS-Unterstützung.

Mal sehen, wie die Hauptstruktur einer JavaFX-Anwendung aussieht:

Hier sehen wir zwei Hauptcontainer:

  • Stage ist der Hauptcontainer und der Einstiegspunkt der Anwendung . Es stellt das Hauptfenster dar und wird als Argument der start () -Methode übergeben.
  • Scene ist ein Container zum Speichern der UI-Elemente wie Bildansichten, Schaltflächen, Raster und Textfelder.

Die Szene kann ersetzt oder zu einer anderen Szene gewechselt werden . Dies stellt eine grafische Darstellung der hierarchischen Objekte, die als bekannt ist Scene Graph. Jedes Element in dieser Hierarchie wird als Knoten bezeichnet. Ein einzelner Knoten hat seine ID, seinen Stil, seine Effekte, seine Ereignishandler und seinen Status.

Darüber hinaus enthält die Szene auch die Layout-Container, Bilder und Medien.

2.2. Themen

Auf Systemebene erstellt die JVM separate Threads zum Ausführen und Rendern der Anwendung :

  • Prisma- Rendering-Thread - verantwortlich für das separate Rendern des Szenendiagramms .
  • Anwendungsthread - ist der Hauptthread jeder JavaFX-Anwendung. Alle aktiven Knoten und Komponenten sind an diesen Thread angehängt.

2.3. Lebenszyklus

Die Klasse javafx.application.Application verfügt über die folgenden Lebenszyklusmethoden:

  • init () - wird aufgerufen, nachdem die Anwendungsinstanz erstellt wurde . Zu diesem Zeitpunkt ist die JavaFX-API noch nicht fertig, daher können wir hier keine grafischen Komponenten erstellen.
  • start (Stage stage) - hier werden alle grafischen Komponenten erstellt. Hier beginnt auch der Haupt-Thread für die grafischen Aktivitäten.
  • stop () - wird vor dem Herunterfahren der Anwendung aufgerufen; Zum Beispiel, wenn ein Benutzer das Hauptfenster schließt. Es ist nützlich, diese Methode für einige Bereinigungen vor dem Beenden der Anwendung zu überschreiben.

Die statische Methode launch () startet die JavaFX-Anwendung.

2.4. FXML

JavaFX verwendet eine spezielle FXML-Markup-Sprache, um die Ansichtsschnittstellen zu erstellen.

Dies bietet eine XML-basierte Struktur zum Trennen der Ansicht von der Geschäftslogik. XML ist hier besser geeignet, da es ganz natürlich eine Szenendiagrammhierarchie darstellen kann .

Zum Laden der .fxml- Datei verwenden wir schließlich die FXMLLoader- Klasse, die zum Objektdiagramm der Szenenhierarchie führt.

3. Erste Schritte

Um praktisch zu werden, erstellen wir eine kleine Anwendung, mit der Sie eine Liste von Personen durchsuchen können.

Fügen wir zunächst eine Personenmodellklasse hinzu , um unsere Domäne darzustellen:

public class Person { private SimpleIntegerProperty id; private SimpleStringProperty name; private SimpleBooleanProperty isEmployed; // getters, setters }

Beachten Sie , dass wir zum Abschluss der Werte int, String und boolean die Klassen SimpleIntegerProperty, SimpleStringProperty und SimpleBooleanProperty im Paket javafx.beans.property verwenden .

Als nächstes wollen wir die erstellen Hauptklasse, die die erweitert Anwendungs abstrakte Klasse:

public class Main extends Application { @Override public void start(Stage primaryStage) throws Exception { FXMLLoader loader = new FXMLLoader( Main.class.getResource("/SearchController.fxml")); AnchorPane page = (AnchorPane) loader.load(); Scene scene = new Scene(page); primaryStage.setTitle("Title goes here"); primaryStage.setScene(scene); primaryStage.show(); } public static void main(String[] args) { launch(args); } }

Unsere Hauptklasse überschreibt die start () -Methode, die der Einstiegspunkt für das Programm ist.

Anschließend lädt der FXMLLoader die Objektdiagrammhierarchie aus SearchController.fxml in das AnchorPane .

Nach einer neuen Start- Szene , setzen wir es auf die Primärstufe . Wir setzen auch den Titel für unser Fenster und zeigen ihn () .

Beachten Sie, dass es nützlich ist, die main () -Methode einzuschließen, um die JAR-Datei ohne den JavaFX Launcher ausführen zu können .

3.1. FXML-Ansicht

Lassen Sie uns nun tiefer in die SearchController- XML-Datei eintauchen .

Für unsere Suchanwendung fügen wir ein Textfeld hinzu, um das Schlüsselwort und die Suchschaltfläche einzugeben:

AnchorPane ist hier der Stammcontainer und der erste Knoten der Diagrammhierarchie. Beim Ändern der Fenstergröße wird das untergeordnete Element an seinem Ankerpunkt neu positioniert. Das Attribut fx: controller verbindet die Java-Klasse mit dem Markup.

Es stehen einige andere integrierte Layouts zur Verfügung:

  • BorderPane - unterteilt das Layout in fünf Abschnitte: oben, rechts, unten, links, Mitte
  • HBox - Ordnen Sie die untergeordneten Komponenten in einem horizontalen Feld an
  • VBox - Die untergeordneten Knoten sind in einer vertikalen Spalte angeordnet
  • GridPane - nützlich zum Erstellen eines Rasters mit Zeilen und Spalten

In our example, inside of the horizontal HBox panel, we used a Label to place text, TextField for the input, and a Button. With fx: id we mark the elements so that we can use them later in the Java code.

The VBox panel is where we'll display the search results.

Then, to map them to the Java fields – we use the @FXML annotation:

public class SearchController { @FXML private TextField searchField; @FXML private Button searchButton; @FXML private VBox dataContainer; @FXML private TableView tableView; @FXML private void initialize() { // search panel searchButton.setText("Search"); searchButton.setOnAction(event -> loadData()); searchButton.setStyle("-fx-background-color: #457ecd; -fx-text-fill: #ffffff;"); initTable(); } }

After populating the @FXML annotated fields, initialize() will be called automatically. Here, we're able to perform further actions over the UI components – like registering event listeners, adding style or changing the text property.

In the initTable() method we'll create the table that will contain the results, with 3 columns, and add it to the dataContainer VBox:

private void initTable() { tableView = new TableView(); TableColumn id = new TableColumn("ID"); TableColumn name = new TableColumn("NAME"); TableColumn employed = new TableColumn("EMPLOYED"); tableView.getColumns().addAll(id, name, employed); dataContainer.getChildren().add(tableView); }

Finally, all of this logic described here will produce the following window:

4. Binding API

Now that the visual aspects are handled, let's start looking at binding data.

The binding API provides some interfaces that notify objects when a value change of another object occurs.

We can bind a value using the bind() method or by adding listeners.

Unidirectional binding provides a binding for one direction only:

searchLabel.textProperty().bind(searchField.textProperty());

Here, any change in the search field will update the text value of the label.

By comparison, bidirectional binding synchronizes the values of two properties in both directions.

The alternative way of binding the fields are ChangeListeners:

searchField.textProperty().addListener((observable, oldValue, newValue) -> { searchLabel.setText(newValue); });

The Observable interface allows observing the value of the object for changes.

To exemplify this, the most commonly used implementation is the javafx.collections.ObservableList interface:

ObservableList masterData = FXCollections.observableArrayList(); ObservableList results = FXCollections.observableList(masterData);

Here, any model change like insertion, update or removal of the elements, will notify the UI controls immediately.

The masterData list will contain the initial list of Person objects, and the results list will be the list we display upon searching.

We also have to update the initTable() method to bind the data in the table to the initial list, and to connect each column to the Person class fields:

private void initTable() { tableView = new TableView(FXCollections.observableList(masterData)); TableColumn id = new TableColumn("ID"); id.setCellValueFactory(new PropertyValueFactory("id")); TableColumn name = new TableColumn("NAME"); name.setCellValueFactory(new PropertyValueFactory("name")); TableColumn employed = new TableColumn("EMPLOYED"); employed.setCellValueFactory(new PropertyValueFactory("isEmployed")); tableView.getColumns().addAll(id, name, employed); dataContainer.getChildren().add(tableView); }

5. Concurrency

Working with the UI components in a scene graph isn't thread-safe, as it's accessed only from the Application thread. The javafx.concurrent package is here to help with multithreading.

Let's see how we can perform the data search in the background thread:

private void loadData() { String searchText = searchField.getText(); Task
    
      task = new Task
     
      () { @Override protected ObservableList call() throws Exception { updateMessage("Loading data"); return FXCollections.observableArrayList(masterData .stream() .filter(value -> value.getName().toLowerCase().contains(searchText)) .collect(Collectors.toList())); } }; }
     
    

Here, we create a one-time task javafx.concurrent.Task object and override the call() method.

The call() method runs entirely on the background thread and returns the result to the Application thread. This means any manipulation of the UI components within this method, will throw a runtime exception.

However, updateProgress(), updateMessage() can be called to update Application thread items. When the task state transitions to SUCCEEDED state, the onSucceeded() event handler is called from the Application thread:

task.setOnSucceeded(event -> { results = task.getValue(); tableView.setItems(FXCollections.observableList(results)); }); 

In the same callback, we've updated the tableView data to the new list of results.

The Task is Runnable, so to start it we need just to start a new Thread with the task parameter:

Thread th = new Thread(task); th.setDaemon(true); th.start();

The setDaemon(true) flag indicates that the thread will terminate after finishing the work.

6. Event Handling

We can describe an event as an action that might be interesting to the application.

For example, user actions like mouse clicks, key presses, window resize are handled or notified by javafx.event.Event class or any of its subclasses.

Also, we distinguish three types of events:

  • InputEvent – all the types of key and mouse actions like KEY_PRESSED, KEY_TYPED, KEY_RELEASED or MOUSE_PRESSES, MOUSE_RELEASED
  • ActionEvent – represents a variety of actions like firing a Button or finishing a KeyFrame
  • WindowEventWINDOW_SHOWING, WINDOW_SHOWN

To demonstrate, the code fragment below catches the event of pressing the Enter key over the searchField:

searchField.setOnKeyPressed(event -> { if (event.getCode().equals(KeyCode.ENTER)) { loadData(); } });

7. Style

Wir können die Benutzeroberfläche der JavaFX-Anwendung ändern, indem wir ein benutzerdefiniertes Design darauf anwenden.

Standardmäßig verwendet JavaFX modena.css als CSS-Ressource für die gesamte Anwendung. Dies ist ein Teil der Datei jfxrt.jar .

Um den Standardstil zu überschreiben, können wir der Szene ein Stylesheet hinzufügen:

scene.getStylesheets().add("/search.css");

Wir können auch den Inline-Stil verwenden. So legen Sie beispielsweise eine Stileigenschaft für einen bestimmten Knoten fest:

searchButton.setStyle("-fx-background-color: slateblue; -fx-text-fill: white;");

8. Fazit

Diese kurze Beschreibung behandelt die Grundlagen der JavaFX-API. Wir gingen die interne Struktur durch und stellten die wichtigsten Funktionen der Architektur, des Lebenszyklus und der Komponenten vor.

Als Ergebnis haben wir gelernt und sind nun in der Lage, eine einfache GUI-Anwendung zu erstellen.

Und wie immer ist der vollständige Quellcode des Tutorials auf GitHub verfügbar.