Introduction à JavaFx

1. Introduction

JavaFX est une bibliothèque pour créer des applications clientes riches avec Java. Il fournit une API pour la conception d'applications GUI qui s'exécutent sur presque tous les appareils prenant en charge Java.

Dans ce didacticiel, nous allons nous concentrer et couvrir certaines de ses principales capacités et fonctionnalités.

2. API JavaFX

Dans Java 8, 9 et 10, aucune configuration supplémentaire n'est nécessaire pour commencer à travailler avec la bibliothèque JavaFX. Le projet sera supprimé du JDK à partir du JDK 11.

2.1. Architecture

JavaFX utilise un pipeline graphique accéléré par matériel pour le rendu, appelé Prism . De plus, pour accélérer pleinement l'utilisation des graphiques, il exploite le mécanisme de rendu logiciel ou matériel, en utilisant en interne DirectX et OpenGL .

JavaFX dispose d'une couche de boîte à outils de fenêtrage Glass dépendante de la plate-forme pour se connecter au système d'exploitation natif . Il utilise la file d'attente d'événements du système d'exploitation pour planifier l'utilisation des threads. En outre, il gère de manière asynchrone les fenêtres, les événements, les minuteries.

Les moteurs Media et Web permettent la lecture multimédia et la prise en charge HTML / CSS.

Voyons à quoi ressemble la structure principale d'une application JavaFX:

Ici, on remarque deux conteneurs principaux:

  • Stage est le conteneur principal et le point d'entrée de l'application . Il représente la fenêtre principale et est passé en argument de laméthode start () .
  • Scene est un conteneur pour contenir les éléments de l'interface utilisateur, tels que les vues d'image, les boutons, les grilles, les zones de texte.

La scène peut être remplacée ou basculée sur une autre scène . Cela représente un graphique d'objets hiérarchiques, appelé graphe de scène . Chaque élément de cette hiérarchie est appelé un nœud. Un seul nœud a son ID, son style, ses effets, ses gestionnaires d'événements, son état.

En outre, la scène contient également les conteneurs de mise en page, les images et les médias.

2.2. Fils

Au niveau du système, la JVM crée des threads séparés pour l'exécution et le rendu de l'application :

  • Fil de rendu de prisme - responsable du rendu du graphique de scène séparément.
  • Fil d'application - est le fil principal de toute application JavaFX. Tous les nœuds et composants actifs sont attachés à ce thread.

2.3. Cycle de la vie

La classe javafx.application.Application a les méthodes de cycle de vie suivantes:

  • init () - est appelé après la création de l'instance d'application . À ce stade, l'API JavaFX n'est pas encore prête, nous ne pouvons donc pas créer de composants graphiques ici.
  • start (Stage stage) - tous les composants graphiques sont créés ici. De plus, le fil principal des activités graphiques commence ici.
  • stop () - est appelé avant l'arrêt de l'application; par exemple, lorsqu'un utilisateur ferme la fenêtre principale. Il est utile de remplacer cette méthode pour un nettoyage avant l'arrêt de l'application.

La méthode static launch () démarre l'application JavaFX.

2.4. FXML

JavaFX utilise un langage de balisage FXML spécial pour créer les interfaces de vue.

Cela fournit une structure basée sur XML pour séparer la vue de la logique métier. XML est plus approprié ici, car il est capable de représenter assez naturellement une hiérarchie de Scene Graph .

Enfin, pour charger le fichier .fxml , nous utilisons la classe FXMLLoader , qui se traduit par le graphe d'objets de la hiérarchie de la scène.

3. Premiers pas

Pour être pratique, construisons une petite application qui permet de rechercher dans une liste de personnes.

Tout d'abord, ajoutons une classe de modèle Person - pour représenter notre domaine:

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

Remarquez comment, pour encapsuler les valeurs int, String et boolean , nous utilisons les classes SimpleIntegerProperty, SimpleStringProperty, SimpleBooleanProperty dans le package javafx.beans.property .

Ensuite, créons la classe Main qui étend la classe abstraite Application :

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); } }

Notre classe principale remplace la méthode start () , qui est le point d'entrée du programme.

Ensuite, FXMLLoader charge la hiérarchie du graphe d'objets de SearchController.fxml dans AnchorPane .

Après avoir démarré une nouvelle scène , nous la définissons sur la scène principale . Nous définissons également le titre de notre fenêtre et le montrons () .

Notez qu'il est utile d'inclure la méthode main () pour pouvoir exécuter le fichier JAR sans JavaFX Launcher .

3.1. Vue FXML

Plongeons maintenant plus profondément dans le fichier XML SearchController .

Pour notre application de recherche, nous ajouterons un champ de texte pour saisir le mot-clé et le bouton de recherche:

AnchorPane est ici le conteneur racine et le premier nœud de la hiérarchie du graphe. Lors du redimensionnement de la fenêtre, il repositionnera l'enfant sur son point d'ancrage. L' attribut fx: controller relie la classe Java avec le balisage.

Il existe d'autres mises en page intégrées disponibles:

  • BorderPane - divise la mise en page en cinq sections: haut, droite, bas, gauche, centre
  • HBox - organiser les composants enfants dans un panneau horizontal
  • VBox - les nœuds enfants sont disposés dans une colonne verticale
  • GridPane - utile pour créer une grille avec des lignes et des colonnes

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

Nous pouvons modifier l'interface utilisateur de l'application JavaFX en lui appliquant une conception personnalisée.

Par défaut, JavaFX utilise modena.css comme ressource CSS pour l'ensemble de l'application. Cela fait partie du fichier jfxrt.jar .

Pour remplacer le style par défaut, nous pouvons ajouter une feuille de style à la scène:

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

Nous pouvons également utiliser le style en ligne; par exemple, pour définir une propriété de style pour un nœud spécifique:

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

8. Conclusion

Cette brève description couvre les bases de l'API JavaFX. Nous avons parcouru la structure interne et introduit les fonctionnalités clés de son architecture, de son cycle de vie et de ses composants.

En conséquence, nous avons appris et sommes maintenant capables de créer une application GUI simple.

Et, comme toujours, le code source complet du didacticiel est disponible à l'adresse over sur GitHub.