Intéressé par des cours d'informatique en ligne ?
Visitez mon nouveau site
https://www.yesik.it !
JavaFX est le nom d'un ensemble de technologies développées par Sun autour de la technologie Java, et destinées à faciliter l'écriture d'applications riches graphiquement pour une grande variété de cibles (ordinateur, téléphones portables, etc.).
Une des nouveautés les plus visibles dans JavaFX est l'introduction d'un nouveau langage, JavaFX Script. Celui-ci utilise une syntaxe déclarative pour décrire les différents éléments graphiques de l'application. Cet article va vous en présenter les grandes lignes.

Un point de vocabulaire
JavaFX est le nom de la technologie. JavaFX Script le nom du langage. Dans la pratique, on trouve presque systématiquement, JavaFX – ou une variation comme Java FX ou JFX.
Sommaire
Installer JavaFX
Avant de pouvoir utiliser JavaFX, il faut d'abord l'installer. Malheureusement, lors de la rédaction de cet article, il n'y avait pas de version de JavaFX pour Linux. Mais en rusant un peu, il est possible de s'en sortir avec le SDK pour Mac OS X. Voici donc la transcription des commandes shell que j'ai utilisées pour installer le SDK sur ma machine Debian/Etch à partir de l'image disque pour MacOS X.
sh# cd /usr/local/ sh# mkdir javafx-1.1.1 sh# ln -s javafx-1.1.1 javafx sh# cd javafx sh# bunzip2 /path/to/javafx_sdk-1_1_1-macosx-universal.dmg bunzip2: Can't guess original name for /path/to/javafx_sdk-1_1_1-macosx-universal.dmg -- using /path/to/javafx_sdk-1_1_1-macosx-universal.dmg.out bunzip2: /path/to/javafx_sdk-1_1_1-macosx-universal.dmg: trailing garbage after EOF ignored sh# losetup -o 17408 -s -f /path/to/javafx_sdk-1_1_1-macosx-universal.dmg.out sh# mkdir mount-point sh# mount -o loop -t hfsplus /dev/loop0 mount-point sh# cp 'mount-point/JavaFX 1.1 SDK.mpkg/Contents/Packages/javafxsdk.pkg/Contents/Archive.pax.gz' . sh# gunzip Archive.pax.gz sh# cpio -id < Archive.pax sh# umount mount-point/ sh# rmdir mount-point/ sh# rm Archive.pax sh# ls bin lib README.html src.zip COPYRIGHT.html LICENSE.txt samples THIRDPARTYLICENSEREADME.txt docs profiles servicetag timestamp
Maintenant, pour faciliter l'utilisation de l'outil du SDK, il suffit d'ajouter le sous-répertoire bin à votre PATH. Cela peut se faire aussi simplement qu'en une ligne dans votre shell:
sh$ export JAVAFX_HOME=/usr/local/javafx sh$ export PATH="${PATH}:${JAVAFX_HOME}/bin"
Par contre, vous devrez re-déclarer ces variables dans chaque nouveau shell. A l'inverse, sous Debian, vous pouvez aussi ajouter le fichier local-javafx.sh suivant dans le répertoire /etc/profile.d. Il sera alors disponible à tout nouveau shell ouvert:
sh$ cat /etc/profile.d/local-javafx.sh # # JavaFX configuration # if [ -z "${JAVAFX_HOME}" ] then export JAVAFX_HOME=/usr/local/javafx export PATH="${PATH}:${JAVAFX_HOME}/bin" fi sh$ . /etc/profile.d/local-javafx.sh {{Commentaire shell|Juste pour initialiser les variables d'environnement dans le shell courant sh$ echo $JAVAFX_HOME /usr/local/javafx

Note:
Dans d'autres distributions, si le répertoire /etc/profile.d n'existe pas, vous pouvez directement modifier le fichier /etc/profile.
Hello JavaFX
Puisque c'est la tradition, nous allons sacrifier au sempiternel "Hello World". Cela va au moins nous permettre de vérifier que l'installation précédente s'est bien passée.
Commencez donc par créer le fichier Hello.fx contenant le code suivant:
import javafx.scene.Scene; import javafx.scene.text.*; import javafx.scene.paint.Color; import javafx.stage.Stage; Stage { title: "Hello World JavaFX" scene: Scene { content: Text { content: "Hello World!" font: Font { size: 30 } translateX: 114 translateY: 45 } } width:400 height:100 }
Si vous programmez déjà en Java, ce code doit vous sembler un rien familier. De même si vous êtes un habitué de JavaScript ou Groovy. Mais JavaFX Script reste bel et bien un langage à part entière!
Tentons de compiler. Dans la pure tradition, le compilateur JavaFX est l'outil javafxc:
sh$ javafxc Hello.fx

Piège:
Faites bien attention à l'extention de votre fichier. Les sources JavaFX doivent se terminer avec l'extension .fx. Impérativement. Sinon le compilateur ne reconnaît pas le fichier comme un script JavaFX et affiche un message d'erreur sibyllin. Ainsi, par exemple, s'il vous avait pris la fantaisie d'utiliser l'extension .javafx voici ce que vous auriez obtenu:
sh$ javafxc Hello.javafx
javafxc: invalid flag: Hello.javafx
Usage: javafxc <options> <source files>
use -help for a list of possible options
Toujours par analogie avec Java, la commande pour exécuter les programmes JavaFX est javafx.
sh$ javafx Hello
Ici, vous aurez peut-être la surprise de voir apparaître l'écran ci-contre: en fait – et contrairement à ce que peut laisser penser le texte du message – il s'agit d'accepter la licence d'utilisation de JavaFX. Rien n'a besoin d'être installé puisque nous avons tout mis en place au début de cet article. Acceptez donc la licence pour que nous puissions continuer.
Une fois la licence acceptée, votre première fenêtre avec JavaFX apparaît:
JavaFX primer
La structure de base d'un script JavaFX est toujours la même. Un élément Stage représente une fenêtre de l'application. A l'intérieur de chaque fenêtre le graphe des objets à dessiner est décrit dans l'élément Scene.
Si je vous dis aussi qu'en plus des structures purement déclaratives, JavaFX contient aussi des instruction de répétition. En clair, la boucle for. Muni de toutes ces informations, vous comprenez que le programme suivant ouvre 2 fenêtres:
sh$ cat DeuxFenêtres.fx import javafx.stage.Stage; for(s in [0..<2]) { Stage { } }
Si vous lancez ce programme, vous verrez que les deux fenêtres sont minuscules et apparaissent superposées (sous Gnome, en plein centre de l'écran). Essayons de faire un peu mieux en fixant la taille de ces fenêtres:
import javafx.stage.Stage; for(s in [0..<2]) { Stage { width: 200 height: 100 } }

Note:
En fait, par défaut, la fenêtre est créée de telle sorte qu'elle soit suffisamment grande pour afficher la scène qu'elle contient. Mais comme ici il n'y a aucune scène à afficher, la taille se réduit au minimum possible.
Les propriétés width et height permettent de fixer la taille de la fenêtre indépendamment de la taille de la scène à afficher.
Bon, les deux fenêtres sont créés, mais elles sont superposées. Encore une fois, il est possible de faire mieux sans trop de problème. Avez-vous remarqué la boucle for:
for(s in [0..<2]) { ... }
Cette structure de contrôle répète ce qui est entre accolade un certain nombre de fois. Ici 2 fois. Et à chaque répétition la valeur de la variable s change. A la première itération (au premier tour), s vaut 0, au second 1. Dans notre exemple, la boucle commence avec s à 0, et tourne tant que s est <2.
Tout ça pour dire que puisque l'on peut savoir "dans quel tour on est" en examinant la variable s. Du coup, il est possible d'utiliser cette variable pour placer différemment les deux fenêtres:
import javafx.stage.Stage; for(s in [0..<2]) { Stage { x: s*100 // s "multiplié par" 100 y: s*100 // s "multiplié par" 100 width: 200 height: 100 }}
Si vous n'êtes pas familier avec la notion de boucle, n'hésitez pas à jouer un peu avec ce petit programme en changeant les bornes inférieures et supérieures pour voir ce que ça donne.

Note:
Si vous manquez d'imagination, essayez les boucles suivantes:
for(s in [0..2]) { ... }
for(s in [1..<3]) { ... }
for(s in [-1..<2]) { ... }
Backstage
Bien, nous avons joué avec les Stages. Mais créer plusieurs (pleins?) de fenêtres vides n'est guère excitant. Nous allons donc maintenant nous intéresser plus spécialement au contenu à afficher dans une de ces fenêtres. Formellement, dans le vocabulaire de JavaFX, à la scène.
En fait, la scène pour JavaFX est juste un container qui contient le graph des objets à dessiner dans la fenêtre. C'est un des points clés de JavaFX: en fait, nous n'allons pas directement dessiner dans la fenêtre. Mais créer dans la scène l'ensemble des objets à y faire figurer. Ces objets seront (re-)dessinés au besoin. C'est à dire aussi bien lorsque la fenêtre apparaît initialement, que lorsqu'elle doit être rafraîchie (sous parce qu'une partie cachée de la fenêtre vient d'apparaître, ou encore parce que la scène a changé).
La fenêtre
Pour cette introduction, nous allons rester sur des formes géométriques simples. Le programme de départ ressemble fortement à ce que nous avons vu précédemment, et pour l'instant se contente d'afficher une fenêtre (sans spécification de taille ou de position). L'exécution de ce programme donne la mini-fenêtre ci contre. Mais rassurez-vous, d'ici quelques minutes nous allons la remplir.
sh$ cat Dessin.fx import javafx.stage.Stage; Stage { } sh$ javafxc Dessin.fx sh$ javafx Dessin
Ajouter la scène
Nous allons y aller tout doucement. Ainsi, même si vous n'êtes pas familier de Java – ou de la programmation en général – vous ne serez pas noyés par de multiples modifications simultanées.
Donc commençons par ajouter une scène vide:
sh$ cat Dessin.fx import javafx.stage.Stage; import javafx.scene.Scene; /* * Démonstration des fonctionnalités de * dessin de JavaFX */ Stage { // La scène: scene: Scene { } }
Tout d'abord, vous vous en doutez certainement, le bloc entre /* ... */ est un commentaire. C'est un moyen de donner des informations sur le programme à une personne lisant votre code source. Vous pouvez aussi ajouter un commentaire avec la notation //. Dans ce cas, le commentaire s'étend jusqu'à la fin de la ligne.
La seule modification fonctionnelle du code source est l'ajout des lignes:
scene: Scene {
}
Celle ci initialise la propriété scene (en minuscule) de la fenêtre avec un nouvel objet de la classe Scene (avec une initiale en majuscule).
Quand à la ligne import ..., celle-ci permet juste au compilateur de savoir dans quel paquet se trouve la classe Scene que nous utilisons dans ce programme. Si vous êtes familier de Java, vous avez déjà entendu parler de paquet (package). Dans tous les cas, il vous suffit de savoir que c'est juste un moyen de regrouper différentes classes. Accessoirement, cela permet aussi lors de l'exécution de retrouver ces classes qui sont souvent dans des fichiers séparés.
Si vous compilez et testez ce programme vous verrez que c'est toujours la même mini-fenêtre qui apparaît. Évidemment: il y a bien maintenant une scène dans notre fenêtre, mais la scène est vide. Nous allons tout de suite y remédier.
Du contenu
Bon, il est plus que temps de dessiner quelque chose (Enfin!). Je vous livre tel quel le code source. Son exécution devrait vous mener au résultat ci-contre:
sh$ cat Dessin.fx import javafx.stage.Stage; import javafx.scene.Scene; import javafx.scene.shape.Circle; /* * Démonstration des fonctionnalités de * dessin de JavaFX */ Stage { scene: Scene { content: Circle { radius: 100 translateX: 100 translateY: 100 } } } sh$ javafxc Dessin.fx sh$ javafx Dessin
Vous remarquez la même structure que ce que nous avons détaillée dans la partie précédente: la propriété content de la scène est initialisée avec un nouvel objet de la classe Circle. Ce dernier objet a aussi un certain nombre de propriétés initialisées (si vous ne voyez pas de façon intuitive à qui servent ces propriétés, n'hésitez pas à essayer le programme en changeant ces valeurs).
Enfin, comme toujours, puisque j'utilise une nouvelle classe dans mon programme, j'indique au compilateur par une directive import que la classe Circle se trouve dans le paquet javafx.scene.shape.
Un peu de couleur
Comme il est triste ce cercle. Donnons lui un peu de peps en lui ajoutant un peu de couleur. La couleur de remplissage d'une forme est sa propriété fill. Et les couleurs de base sont pré-définies dans la classe Color du paquet javafx.scene.paint. Muni de ces informations, vous ne devriez avoir aucun mal à comprendre les modifications apportées à cette nouvelle version de notre programme:
sh$ cat Dessin.fx import javafx.stage.Stage; import javafx.scene.Scene; import javafx.scene.shape.Circle; import javafx.scene.paint.Color; /* * Démonstration des fonctionnalités de * dessin de JavaFX */ Stage { scene: Scene { content: Circle { radius: 100 translateX: 100 translateY: 100 fill: Color.YELLOW; } } }
Un peu de variété
Rajoutons maintenant un peu de variété à notre scène:
import javafx.stage.Stage; import javafx.scene.Scene; import javafx.scene.shape.Circle; import javafx.scene.shape.Rectangle; import javafx.scene.paint.Color; /* * Démonstration des fonctionnalités de * dessin de JavaFX */ Stage { scene: Scene { content: [ Circle { radius: 100 translateX: 100 translateY: 100 fill: Color.YELLOW; }, Circle { radius: 10 translateX: 100 translateY: 165 }, Rectangle { height: 25; width: 25; translateX: 55; translateY: 45 }, Rectangle { height: 25; width: 25; translateX: 120; translateY: 45 } ] } }
L'aspect important dans les modifications apportées au programme ne sont pas forcément l'ajout de deux objets de la classe Rectangle. A ce stade, cela devrait vous être compréhensible.
Par contre, ce qui est notable, c'est que jusqu'à présent le contenu de notre scène était composé d'un seul objet. Maintenant, il y en a plusieurs. Une solution dans JavaFX pour résoudre ce problème est d'utiliser une séquence. Vous voyez que nous utilisons une séquence ici, à la notation [ ... , ... ]. Formellement, une séquence commence par un crochet ouvrant et se termine par un crochet fermant. Et les éléments sont séparés par une virgule.

Note:
Les séquences ressemblent beaucoup à la notion de liste ou de tableau que l'on trouve dans d'autres langages. Mais en plus souple. Vous souvenez-vous de l'exemple – un peu plus haut – où nous nous amusions à créer plusieurs fenêtres? Et plus précisément du passage:
for(s in [0..<2]) {
/* ... */
}
La notation [0..<2] est aussi une séquence. Celle des entiers entre 0 (inclus) et 2 (exclu).
Move it baby!
Variables
Donc nous avons vu comment dessiner avec JavaFX. Mais il est aussi possible d'avoir non seulement des images fixes, mais aussi des animations.
Dans cet exemple, nous allons faire écarquiller (d'excitation?) les yeux de notre smiley. Tout d'abord, nous allons faire un peu de mathématiques. Juste un tout petit peu. En effet, si je veux que les yeux de mon smiley s'écartent chacun de leur côté, et en même temps, il faut que je trouve une formule qui permet de calculer la position de chaque oeil à un instant donné.
Ici, le problème est simple:
- au début, l'oeil gauche est à la position X = 55. L'oeil droit = X = 120;
- quelques temps après (disons 2 secondes), l'oeil gauche devra se décaler de 20 pixels sur la gauche (X = 35) et l'oeil droit de 20 pixels sur la droite (X = 140).
Bon la formule est simple:
- oeil gauche: X = 55 - décalage
- oeil droit: X = 120 + décalage
Et le décalage variera pendant l'animation. En fait, ce décalage sera ce que l'on appelle une variable en informatique. Nous allons tout de suite mettre en place cela dans le code de notre programme:
import javafx.stage.Stage; import javafx.scene.Scene; import javafx.scene.shape.Circle; import javafx.scene.shape.Rectangle; import javafx.scene.paint.Color; /* * Décalage des yeux */ var decalage: Number = 0; // définit la variable décalage qui vaut 0 /* * Démonstration des fonctionnalités de * dessin de JavaFX */ Stage { scene: Scene { content: [ Circle { radius: 100 translateX: 100 translateY: 100 fill: Color.YELLOW }, Circle { radius: 10 translateX: 100 translateY: 165 }, Rectangle { height: 25; width: 25; translateX: 55 - decalage translateY: 45 }, Rectangle { height: 25; width: 25; translateX: 120 + decalage translateY: 45 } ] } }
Si vous compilez et exécutez ce programme, le dessin ne devrait pas avoir changé d'un pixel. Maintenant, amusez-vous à changer la déclaration de la variable decalage pour l'initialiser à 20:
var decalage: Number = 20;
Et relancez le programme. Normalement, votre smiley écarquille les yeux. Maintenant, il faudrait un peu automatiser le processus pour avoir une véritable animation!
Timeline et KeyFrame
Le principe dans JavaFX est de définir une timeline. C'est à dire un repère temporel qui indique ce qui doit se passer à des instants précis. Si vous avez déjà fait du montage vidéo – ou travaillé avec une technologie d'animation comme Flash – le concept doit vous être familier.
L'autre concept clé quand ont parle d'animations en JavaFX est celui d'image-clé (keyframe). Si la timeline représente un intervalle de temps, un keyframe représente un instant. Tout l'intérêt est qu'il va être possible de dire ce qui se passe dans une animation à cet instant particulier. Un exemple vaut mieux de longs discours, donc voici le code que vous pourrez ajouter à la fin de votre programme:
Timeline {
keyFrames: [
KeyFrame {
time: 1s
values: [
decalage => 20
]
}
KeyFrame {
time: 2s
values: [
decalage => 0
]
},
]
}.play()
Globalement, vous devriez reconnaître dans ce code une structure similaire à celle vue précédemment. Il s'agit de la création d'un objet de la classe TimeLine qui contient une séquence (délimitée par les [ ... , ... ]) d'objets de la classe KeyFrame.
Par contre deux nouvelles notations sont introduites ici. Tout d'abord la flèche =>:
KeyFrame {
time: 1s
values: [
decalage => 20
]
}
Celle-ci peut se lire comme "voit sa valeur atteindre". Autrement dit, l'image-clé ci-dessus signifie qu'à 1 seconde du début de l'animation, decalage "verra sa valeur atteindre" 20.
KeyFrame {
time: 2s
values: [
decalage => 0
]
}
De la même manière, à 2 secondes du début de l'animation decalage "verra sa valeur atteindre" 0. Or souvenez-vous que decalage conditionne l'écartement des yeux de notre smiley. Vous comprenez donc qu'en faisant évoluer cette variable au cours du temps, les yeux de notre personnage s'animeront.

Pourquoi pas de KeyFrame à time=0?
J'aurais pu rajouter l'image clé suivante:
KeyFrame { time: 0s values: [ decalage => 0 ] }
Celle-ci servirait à initialiser la variable decalage à 0 au début de l'animation. Mais c'est inutile. En effet, cette variable a déjà été initialisée lors de sa déclaration:
var decalage: Number = 0;
Remarquez aussi que les temps sont exprimés "depuis le début de l'animation". La question étant de savoir quand démarrer l'animation. Pour ce programme, nous allons faire simple, en faisant démarrer l'animation dès le programme lancé. C'est ce que fait l'appel de méthode suivant:
Timeline {
/* ... */
}.play()
Le fragment de code précédent pourrait se lire: "créé un objet de la classe Timeline, et lance tout de suite l'animation qu'il contient".
N'oublie pas non plus que qui dit nouvelles classes, dit nouveaux imports. Au final, le code complet du programme est le suivant:
import javafx.stage.Stage; import javafx.scene.Scene; import javafx.scene.shape.Circle; import javafx.scene.shape.Rectangle; import javafx.scene.paint.Color; import javafx.animation.Timeline; import javafx.animation.KeyFrame; /* * Décalage des yeux */ var decalage: Number = 0; /* * Démonstration des fonctionnalités de * dessin de JavaFX */ Stage { scene: Scene { content: [ Circle { radius: 100 translateX: 100 translateY: 100 fill: Color.YELLOW }, Circle { radius: 10 translateX: 100 translateY: 165 }, Rectangle { height: 25; width: 25; translateX: 55 - decalage translateY: 45 }, Rectangle { height: 25; width: 25; translateX: 120 + decalage translateY: 45 } ] } } Timeline { keyFrames: [ KeyFrame { time: 1s values: [ decalage => 20 ] }, KeyFrame { time: 2s values: [ decalage => 0 ] } ] }.play()
Aller, vous en mourrez d'envie: compilez et testez:
sh$ javafxc Dessin.fx sh$ javafx Dessin
Et vous constaterez ... que rien ne s'anime! Pourquoi? La raison est dans la manière dont sont créés les rectangles qui forment les yeux. Examinons-en un:
Rectangle {
height: 25;
width: 25;
translateX: 55 - decalage
translateY: 45
},
En fait, ce code dit à JavaFX de créer un rectangle, et de lui appliquer une translation selon l'axe X de (55-decalage) pixels. Autrement dit de 55-0 soit 55 pixels. Puisque la variable decalage a été initialisée à 0 au début du programme.
Ce qui se passe, c'est que la valeur contenue dans décalage a été utilisée pour calculer cette translation quand le rectangle a été créé. Après, vous pouvez modifier autant que vous voulez le contenu de decalage: le rectangle restera en place! Or nous voulons exactement le contraire: que les deux valeurs restent synchronisées.
JavaFX le permet à condition de le préciser explicitement à l'aide du mot -clé bind. Changez simplement les deux créations de rectangles ainsi:
Rectangle { height: 25; width: 25; translateX: bind 55 - decalage translateY: 45 }, Rectangle { height: 25; width: 25; translateX: bind 120 + decalage translateY: 45 }
Après cette modification, la position horizontale des yeux restera synchronisée avec la valeur de la variable decalage. Ou autrement dit, quand decalage changera, les rectangles verront leur position modifiée en conséquence.
Sky is the limit
Nous avons fait dans cet article un rapide tour d'horizon des concepts de base de JavaFX. N'hésitez pas à expérimenter un peu avec ce programme. Pour vous familiariser autant avec la syntaxe du langage, qu'avec ces concepts.
Par exemple, vous pourriez changer le diamètre de la bouche de notre smiley en même temps qu'il écarquille les yeux (Ooooh!):
Stage { scene: Scene { content: [ /* ... */ Circle { radius: bind 10+decalage translateX: 100 translateY: 165 }, /* ... */
Ou encore, expérimenter d'autres formes de binding. Avec les couleurs – histoire de faire rougir notre smiley
/* ... */ var couleur: Color = Color.YELLOW; /* ... */ Stage { scene: Scene { content: [ Circle { radius: 100 translateX: 100 translateY: 100 fill: bind couleur }, /* ... */ ] } } Timeline { keyFrames: [ /* ... */ KeyFrame { time: 2s values: [ decalage => 0, couleur => Color.RED ] }, /* ... */
Comme vous le voyez, rien qu'avec les informations données dans cet article, il y a déjà de quoi s'amuser. Et nous n'avons fait qu'effleurer ce que peut faire JavaFX!