Date de première publication : 2018/01/29
Notions : JavaFX, JavaFX Application thread
Le but de l'exercice est de montrer qu'une interface graphique simple peut se figer facilement mais surtout que l'on peut faire plusieurs choses en même temps (multithreading).
Voici la correction pas à pas (ou presque) de l'exercice intégré au support de cours...
Une interface qui se fige
Il faut un composant graphique capable d'afficher des rectangles, des ellipse ou des polygones, ou même tout en même temps, au choix !
- Soit un
Group
qui contient desRectangle
- Soit un
Canvas
qui dessine dans une méthode que vous ajoutez ou à la construction de l'objet
MAX
une constante de la classe. Plus elle est grande, plus le composant sera long à afficherRandom
puis d'utiliser les méthodes comme rand.nextQuelquechose()
Ne pas oublier d'ajouter un menu avec au moins deux items : le premier pour dessiner l'interface et le second pour quitter.
Pour une grande valeur de MAX (et surtout avec un modèle de couleurs ARGB), vous devez constater un ralentissement ou un blocage de l'interface !
Une interface non bloquée
Groupe de formes
Si ce sont les rectangles d'un Group
qui sont affichés, l'idée est de créer un nouveau groupe en arrière-plan puis de remplacer quand c'est terminé !
Pour dessiner en arrière-plan, il est nécessaire de créer une tâche idoine, qui par exemple ne reverrait rien (représenté par le type objet Void
)
Task task = new Task<Void>() {
@Override public Void call() {
Group group = new Group();
for (int i=1; i<=MAX; i++) {
if (isCancelled())
break;
// ce qu'il y a à faire
// mise à jour de la barre de progression
}
// remplacer le noeud
return null;
}
};
Pour changer de noeud, il faut synchroniser l'opération avec le thread d'application JavaFX
Platform.runLater(()->root.setCenter(group));
Il reste encore à lancer cette tâche en arrière-plan car sinon cela ne sert à rien :
new Thread(task).start();
Il ne faut surtout pas appeler la méthode call()
de la tâche directement.
Si vous avez l'idée d'ajouter une barre de progression à l'affichage ProgressBar
progress.progressProperty().bind(task.progressProperty());
C'est d'ailleurs avec ce genre de relation qu'il est possible de lier la taille (largeur préférée) de la barre de progression à la largeur de la fenêtre :
progress.prefWidthProperty().bind(root.widthProperty());
Utilisation d'un Canvas
En JavaFX, contrairement à Swing, il n'est pas possible de dessiner dans une image directement pour ensuite placer cette image à l'écran. Cependant, tous les composants JavaFX disposent d'une méthode snapshot()
qui permet de généner une image qui les représente. Ma solution utilise donc les composants suivants :
- un composant de type Canvas redimensionnable qui permet d'afficher rapidement et efficacement une image générée elle-même par
- un autre
Canvas
qui n'est pas attaché à la scène principale mais à une scène qui ne sert qu'à cela.
Le seul bémol de cette méthode est qu'il faut faire le snapshot dans le thread de l'application. La méthode runAndWait()
n'existe pas alors voilà un exemple de code que j'ai trouvé (*)
public static void runAndWait(Runnable action) {
if (action == null)
throw new NullPointerException("action");
// run synchronously on JavaFX thread
if (Platform.isFxApplicationThread()) {
action.run();
return;
}
// queue on JavaFX thread and wait for completion
final CountDownLatch doneLatch = new CountDownLatch(1);
Platform.runLater(() -> {
try {
action.run();
} finally {
doneLatch.countDown();
}
});
try {
doneLatch.await();
} catch (InterruptedException e) {
// ignore exception
}
}
On peut aussi attendre "bêtement" après un runLater()
que l'image soit disponible.
Vous pouvez ajouter une barre de progression comme au premier point et lier la tâche en arrière-plan de génération de formes avec cette barre de progression.
Le résultat est bien meilleur / fluide avec cette version notamment au redimensionnement de la fenêtre.