Cours 7 : Programmation Graphique
Programmation graphique et événementielle
JavaFX
JavaFX est une librairie graphique pour Java un peu plus moderne que sa prédécesseuse Swing, l'usage de cette dernière est tout de même resté assez répandu.
Ajouter JavaFX à votre projet Gradle
Pour commencer, ajouter le plugin Gradle JavaFX à la section plugins
de script de build (build.gradle
) de votre application :
Ensuite, ajoutez à votre script de build la configuration de ce plugin :
Première Application JavaFX
Ensuite, le point d'entrée d'une application JavaFX est une classe qui étend la classe abstraite Application
, et en redéfinit la méthode start
:
Ensuite, dans cette méthode, nous allons décrire notre interface. Le type Scene
permet de créer une fenêtre graphique, dans laquelle nous pouvons passer des contrôles d'interface graphique :
1280
et720
sont les dimensions en largeur et hauteur de notre fenêtreStackPanel
est un contrôle d'interface graphique qui permet de positionner des éléments en ligne ou en colonne.Text
est un contrôle d'interface graphique qui permet d'affiche du texte.
Ensuite, il ne nous reste plus qu'à demander l'affichage de notre fenêtre, via le Stage
fourni par la méthode start
:
Enfin, on peut instancier la classe, et lancer l'application via sa méthode launch
dans le point d'entrée (la méthode main
) de notre application :
On obtient une fenêtre avec notre "Hello World" :
Contrôle d'application
Pour toute application graphique, on a besoin de contrôle de base pour construire son interface : des boutons, des champs d'entrée, etc
JavaFX fournit notamment les contrôles suivant :
Button
: un bouton qui, quand on clique dessus, déclenche un événement
TextField
: un champ text qui permet de donner du texte en entrée
Exemple d'application : compteur
On veut créer une application simple avec un bouton et un texte, le texte affiche le nombre de fois qu'on a appuyé sur le bouton.
On commence par créer notre Label
et notre Button
:
On peut ensuite ajouter notre logique de comptage, en retenant notre valeur dans un champ :
Exemple d'application : convertisseur de texte en casse alternée
On veut développer une application qui va transformer un texte, en le même têxte, mais en casse alternée, exemple : "bonjour" devient "bOnJoUr".
On va utiliser le contrôle d'interface TextArea
, qui est comme un TextField
, mais en plus grand.
Liaisons de données
La liaison de données (databinding en anglais) et un pattern, qui se base sur le pattern observer et qui permet de lier des événements entre elles afin de propager les données en cascade de l'une à l'autre, pour simplifier et standardiser la gestion d'événements.
JavaFX offre des propriétés réactives afin de faire de la liaison de donnée. Ces propriétés réactives enveloppent un type de base dans une implémentation du pattern observer, par exemple le type StringProperty
nous permet d'avoir une String
réactive, à laquelle on va pouvoir attacher des événements, mais surtout lier des données.
Les propriétés réactives de JavaFX ont deux méthodes particulièrement intéressantes :
addListener
: permet de gérer les changements de valeur de la propriétébind
: permet de lier la valeur de la propriété à celle d'une autre (éventuellement en utilisant un convertisseur, en utilisant la classeBindings
)bindBidirectional
: permet de lier la valeur de la propriété à cette d'une autre et réciproquement
Ainsi, en utilisant la liaison de donnée, on peut refactor le convertisseur de texte en casse alternée de la façon suivante :
Le pattern MVVM
Le pattern MVVM signifie :
Model: votre logique métier
View: votre interface graphique
ViewModel: la logique de l'interface graphique
Le but du pattern est de séparer ces trois aspects, notamment en abstrayant la logique de l'interface graphique de la déclaration de cette dernière, afin de pouvoir plus facilement la tester de façon unitaire.
Prenons l'exemple d'une page de login basique avec deux champs, pour le nom d'utilisateur et le mot de passe, ainsi qu'un bouton pour valider. Je vais avoir différentes classes pour les trois couches du pattern:
Les classes de la couche de logique métier (Model)
Le ViewModel, qui est ducoup l'abstraction de mon interface graphique : les données qu'elle contient et la logique d'interaction, et utilise des propriétés réactives pour pouvoir se lier aux contrôles de l'interface graphique :
La classe d'interface graphique va ensuite lier le ViewModel à une View:
Enfin, on peut lier tout cela fans la classe Application
:
Ainsi, on a bien une séparation des responsabilités dans notre application, le ViewModel peut être testé de façon unitaire, pour tester la logique de l'interface, sans pour autant avoir besoin de mettre en place des tests sur l'interface graphique elle-même, qui sont en général beaucoup plus complexe.
Threading et interfaces graphiques
Dans une application graphique, on peut avoir besoin de faire des appels réseau, ce qui peut prendre du temps. Pour éviter de bloquer l'interface graphique, il ne faut pas faire ces appels sur le thread qui la gère, le thread UI.
L'interface graphique en JavaFX n'est pas thread safe: il ne faut donc jamais la manipuler depuis un autre thread que le thread UI.
Il faut donc utiliser la programmation asynchrone afin d'exécuter toute opération bloquante dans un thread en arrière-plan, puis utiliser le résultat sur le thread UI.
Reprenons notre exemple précédent, en implémentant UserService
comme un appel à une API ReST (on utilise la librairie JAX-RS client pour gérer l'appel). Je me suis aussi créé une fausse API locale en utilisant Mockoon. On modifie notre interface de service pour avoir un appel asynchrone grâce à CompletableFuture
:
On adapte ensuite notre ViewModel pour qu'il gère l'asynchrone correctement :
Rappel sur l'asynchrone :
supplyAsync
exécute le code sur une thread pool donc de façon asynchrone, en arrière-planthenAccept
exécute une continuation sur le thread courant (ici le thread UI)