Cours IHM-1 JavaFX

Transcription

Cours IHM-1 JavaFX
Java GUI – Bref historique [2]
§ Une refonte importante du toolkit a pris en compte les critiques formulées
et a conduit à une nouvelle mouture : JavaFX 2
Cours IHM-1
JavaFX
§ Caractéristiques principales :
• Abandon du langage de script JavaFX Script
• Choix de deux modes : interfaces basées sur du code Java (API) et/ou sur un
langage descriptif utilisant une syntaxe XML : FXML
1 - Introduction
Concepts de base
• Création d'un outil interactif Scene Builder pour créer graphiquement des
interfaces et générer automatiquement du code FXML
• Utilisation possible de feuilles de styles CSS pour adapter la présentation sans
toucher au code (créer des thèmes, des skins, etc.)
• Application du modèle de conception (design pattern) Builder avec un chaînage
de méthodes (Fluent API)
Jacques BAPST
[email protected]
IHM-1 – FX01
– Jacques BAPST
Java GUI – Bref historique [1]
Java GUI – Bref historique [3]
§ A l'origine du langage Java, les interfaces graphiques étaient créées
en utilisant la librairie AWT (java.awt)
§ Avec la sortie de Java 8, une nouvelle version baptisée JavaFX 8 a été
développée :
• Composants "lourds" (heavyweight) basés sur ceux de la machine cible
• Difficulté de créer des applications multiplateformes (write once, run
anywhere), lourdeur
§ Rapidement, la librairie Swing (javax.swing) est venu compléter
(et partiellement remplacer) la librairie AWT
• Composant "légers" (lightweight) dessinés par la librairie
• Pluggable Look&Feel
ð
ð
§ JavaFX 1 a tenté – sans grand succès – de remplacer Swing
– Jacques BAPST
ð
Plus de librairie externe à télécharger et à référencer
• Scene Builder 2 : nouvelle version de l'outil d'édition graphique de GUI (FXML)
• Cohabitation améliorée avec les composants Swing
• Prise en compte des nouveaux concepts introduits en Java 8 et notamment les
expressions lambda et les streams
•
•
•
•
Abandon des Builders introduits dans la version 2 (deprecated)
Ajout d'une nouvelle API pour gérer l'impression
Ajout de nouveaux composants riches (DatePicker, TreeTableView, …)
Gestion des écrans tactiles (TouchEvent, GestureEvent, …)
Amélioration des librairies graphiques 2D et 3D
• Ajout d'un outil de packaging pour simplifier le déploiement des applications
• JavaFX devient le standard officiel pour le développement des interfaces des
applications Java
• Essentiellement basé sur un (nouveau) langage de script (JavaFX Script)
• Vaine tentative pour concurrencer Flex (basé sur Flash et MXML)
IHM-1 – FX01
• Intégration dans la distribution de la plateforme standard Java (JDK, JRE)
ð
L&F multiplateforme ("Metal")
L&F imitant (plus ou moins bien) ceux des OS spécifiques (Windows, OS X, …)
3
2
IHM-1 – FX01
– Jacques BAPST
4
Java GUI – Bref historique [4]
Potentiel de JavaFX [2]
§ Chronologie des principales étapes :
§ La possibilité de découpler le design graphique (grâce à l'outil Scene
Builder et à FXML) permet de déléguer la conception graphique de
l'interface à un spécialiste (UI designer) qui n'a pas l'obligation de
connaître et maîtriser le langage Java et ses librairies (API).
§ L'application possible de feuilles de style CSS renforce encore cette
séparation entre le design graphique et les traitements qui seront
effectués à l'aide de code Java.
§ Différents composants complexes sont disponibles et permettent,
avec un minimum d'effort, de créer des applications riches :
•
•
•
•
IHM-1 – FX01
– Jacques BAPST
5
Effets visuels (ombrages, transitions, animations, …)
Graphiques 2D (charts)
Navigateur web (WebKit)
Images, audio, vidéo (media player)
IHM-1 – FX01
– Jacques BAPST
7
Potentiel de JavaFX [1]
Potentiel de JavaFX [3]
§ JavaFX étant le résultat de développements
récents, il bénéficie de concepts modernes qui
en font un framework intéressant pour la
réalisation d'applications dans des domaines très divers.
§ Actuellement, de nombreuses applications sont des applications
web, basées sur les technologies HTML5 + CSS + JavaScript (avec un
grand nombre de frameworks disponibles) ou sur Flash/Flex ou Silverlight
(les deux derniers cités étant clairement en perte de vitesse).
§ JavaFX est très bien doté pour développer des interfaces riches en
relation avec des données stockées dans des bases de données ou
accessibles au travers de serveurs d'informations.
§ Une comparaison détaillée entre ces technologies web et JavaFX
sort du cadre de ce cours mais plusieurs billets de blog et documents
décrivent les caractéristiques ainsi que les avantages et
inconvénients de ces différentes approches technologiques, par
exemple :
§ Sa riche librairie graphique 2D et 3D lui donne également un
intéressant potentiel dans des domaines variés :
•
•
•
•
•
•
• Introduction du livre Mastering JavaFX 8 Controls, Hendrik Ebbers,
Oracle Press, 2014
• Blog Code Makery : code.makery.ch/blog/javafx-vs-html5
Représentations graphiques
Animations graphiques
Modélisation (CAD, …)
Applications multimédia
Réalité virtuelle et augmentée
Jeux
IHM-1 – FX01
– Jacques BAPST
6
IHM-1 – FX01
– Jacques BAPST
8
I/F déclaratives vs procédurales [1]
Déploiement [1]
§ La plateforme JavaFX offre deux techniques complémentaires pour
créer les interfaces (I/F) graphiques des applications :
§ Une application JavaFX peut être déployée (mise à disposition des
utilisateurs) de différentes manières :
• Manière déclarative
• Installée localement comme une application autonome
(standalone/desktop application)
En décrivant l'interface dans un fichier FXML (syntaxe XML)
ð L'utilitaire graphique Scene Builder facilite la création et la
gestion des fichiers FXML
ð L'interface peut être créée par un designer (sans connaissance Java,
ou presque...)
ð Séparation entre présentation et logique de l'application ( MVC )
ð
ð
ð
• Installée sur un serveur et intégrée dans une page web
• Manière procédurale
Lancée depuis un navigateur, en cliquant sur un lien ou sur un autre élément
actif de la page
ð Lancée automatiquement dès qu'une page est chargée
ð Utilise la technique Java Web Start (fichier JNLP + descripteur de
déploiement XML)
ð Une fois téléchargée, l'application peut également être lancée horsconnexion (mise en cache local)
ð
Utilisation d'API pour construire l'interface avec du code Java
ð Création et manipulation dynamique des interfaces
ð Création d'extensions et variantes (par héritage)
ð Homogénéité des sources de l'application
ð
§ Il est possible de mélanger les deux techniques au sein d'une même
application (l'API javafx.fxml permet de faire le lien entre les deux).
IHM-1 – FX01
Semblable à une application native
La machine virtuelle Java peut être intégrée ou non dans l'exécutable
(.exe ou .jar)
– Jacques BAPST
9
IHM-1 – FX01
– Jacques BAPST
11
I/F déclaratives vs procédurales [2]
Déploiement [2]
§ Technique déclarative.
§ Différents efforts de développement sont en cours pour permettre
de déployer des applications JavaFX sur des terminaux mobiles
(smartphones et tablettes Android, iOS, …) ainsi que sur des systèmes
embarqués (Raspberry Pi, …).
<?xml version="1.0" encoding="UTF-8"?>
<?import
<?import
<?import
<?import
<?import
Résultat
javafx.geometry.*?>
javafx.scene.text.*?>
javafx.scene.control.*?>
java.lang.*?>
javafx.scene.layout.*?>
<BorderPane maxHeight="-Infinity" maxWidth="-Infinity" prefHeight="100.0" prefWidth="400.0" …>
<top>
<Label id="title" fx:id="title" text="Titre" textFill="#0e17c2" …>
<font>
Fichier
<Font name="SansSerif Bold" size="20.0" />
FXML
</font>
</Label>
</top>
<bottom>
<Button fx:id="btnHello" mnemonicParsing="false" onAction="#handleButtonAction"…/>
</bottom>
<padding>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
</padding>
</BorderPane>
§ La communauté javafxports.org (gluonhq.com) est notamment
active dans ce domaine assez prometteur qui permettrait, à partir
d'un code unique, de générer de réelles applications multiplateformes (standalone et mobiles) comportant des interfaces riches.
• Affaire à suivre…
Scene Builder
IHM-1 – FX01
– Jacques BAPST
10
IHM-1 – FX01
– Jacques BAPST
12
Projets connexes
Documentation [2]
§ JavaFX devrait, à terme, être totalement publié en open-source (ce
n'est que partiellement le cas) dans le cadre du projet OpenJFX.
§ Quelques livres :
§ De nombreux projets contribuent à enrichir l'écosystème JavaFX.
§ Parmi les principaux (et les plus dynamiques) on peut mentionner :
• ControlsFX : Projet open-source destiné à offrir des composants
supplémentaires (controls) de qualité
fxexperience.com/controlsfx
• JFXtras
• DataFX
• TestFX
: Projet open-source destiné à fournir aux développeurs
des éléments utiles dans leur vie de tous les jours et qui
manquent dans la version de base de JavaFX
jfxtras.org
: Projet open-source destiné à faciliter la collaboration
entre une application JavaFX et un système de gestion
des données (DB, …)
javafxdata.org
: Librairie pour automatiser le test des applications
JavaFX
github.com/TestFX
IHM-1 – FX01
13
– Jacques BAPST
• Learn JavaFX 8 - Building User Experience and Interfaces with Java 8
Kishori Sharan
Apress, 2015
ISBN: 978-1484211434
• Pro JavaFX 8 - A Definitive Guide to Building Desktop, Mobile, and
Embedded Java Clients
James Weaver, Weiqi Gao, Stephen Chin, Dean Iverson,
Johan Vos, Adrian Chin
Apress, 2014
ISBN: 978-1430265740
• JavaFX 8 - Introduction by Example
Carl Dea et Mark Heckler
Apress, 2014
ISBN: 978-1430264606
• Mastering JavaFX 8 Controls
Hendrik Ebbers
McGraw-Hill Professional - Oracle Press, 2014
ISBN: 978-0071833776
IHM-1 – FX01
– Jacques BAPST
15
Documentation [1]
Programmation Java
§ Quelques références web utiles (à mettre dans vos bookmarks) :
§ Avant d'aborder les concepts principaux de JavaFX il est nécessaire
de présenter quelques éléments de programmation Java qui sont
utiles voire nécessaires pour développer des applications avec des
interfaces graphiques.
• Tutoriel officiel Oracle
docs.oracle.com/javase/8/javase-clienttechnologies.htm
• FX-Experience : Blog géré par des experts du domaine
(news, demos, …)
• Autre blog dédié à différentes thématiques JavaFX
fxexperience.com
guigarage.com
• Communauté des développeurs du projet open-source OpenFJX
(qui est un sous-projet de OpenJDK)
javafxcommunity.com
• API JavaFX (Javadoc)
docs.oracle.com/javase/8/javafx/api
Attention : On trouve, sur le web, encore passablement de documentation et de code
JavaFX en version 1.x. Des changements majeurs sont intervenus dans les
versions 2.x et 8.x. Tout ce qui date d'avant 2012 doit être considéré avec
beaucoup de prudence et de circonspection.
IHM-1 – FX01
– Jacques BAPST
§ Il s'agit notamment des notions suivantes :
•
•
•
•
•
•
•
Types énumérés (enum)
Généricité (classes, interfaces et méthodes génériques)
Structures de données prédéfinies (Collections)
Annotations (@...)
Expressions lambda
Références de méthodes
Streams
§ Ces notions seront sommairement présentées au chapitre suivant
qui constitue une petite parenthèse technique de programmation
Java, indispensable pour aborder la suite.
14
IHM-1 – FX01
– Jacques BAPST
16
Cours IHM-1
JavaFX
Énumérations
2 - Compléments de
programmation
Jacques BAPST
[email protected]
IHM-1 – FX01
– Jacques BAPST
Préambule
Énumérations [1]
§ Ce chapitre introduit de manière sommaire quelques éléments de
programmation qui, faute de temps, n'ont pas été vus durant le
cours de programmation du premier semestre.
§ En Java, le mot-clé enum permet de définir un type énuméré.
§ Ces éléments sont cependant utiles, voire nécessaires dans le cadre
des programmes Java / JavaFX comportant des interfaces graphiques
(GUI).
§ La présentation de ces concepts se limite aux éléments essentiels qui
sont nécessaires dans le cadre du cours IHM-1.
§ On ne peut donc pas prétendre maîtriser ces concepts après cette
brève introduction. Elle devrait cependant suffire pour en
comprendre les mécanismes principaux .
– Jacques BAPST
§ L'utilisation de enum définit une classe spéciale (appelée enum type)
qui hérite implicitement de java.lang.Enum et qui permet de
représenter un ensemble fixe de constantes (des champs qui sont
implicitement static et final) que l'on écrit donc en majuscules.
§ Une variable de type énuméré ne peut prendre qu'une des valeurs
constantes définies pour le type considéré (ou null ).
§ Exemple de définition du type énuméré EColor :
public enum EColor {RED, BLUE, GREEN, YELLOW}
§ EColor correspond au type énuméré. Les variables de ce type ne
peuvent prendre que les valeurs des constantes RED, BLUE, GREEN,
YELLOW ou la référence null.
§ Pour plus de détail, il faut consulter l'abondante documentation
disponible (livres, web) sur ces sujets.
IHM-1 – FX02
3
2
IHM-1 – FX02
– Jacques BAPST
4
Énumérations [2]
Énumérations [4]
§ Exemple d'utilisation du type EColor :
§ Pour tous les types énumérés, une méthode statique values() est
implicitement déclarée.
public class TestEColor {
§ Elle retourne un tableau des constantes d'énumération dans leur
ordre de déclaration.
public static void main(String[] args) {
EColor color = EColor.BLUE;
if (args.length > 0) color = EColor.RED;
System.out.println(color);
}
§ Utile, par exemple, pour parcourir tous les éléments d'un type
énuméré :
public class TestEColor {
}
public static void main(String[] args) {
for (EColor color : EColor.values()) {
System.out.print(color + " ");
}
}
§ Le programme affichera BLUE (si aucun paramètre n'est passé au
lancement) ou RED (si au moins un paramètre est transmis au
lancement).
}
§ Le programme affichera :
RED BLUE GREEN YELLOW
IHM-1 – FX02
– Jacques BAPST
5
IHM-1 – FX02
7
– Jacques BAPST
Énumérations [3]
Énumérations [5]
§ On peut s'épargner la notation préfixée (type.constante) en ajoutant
une importation statique des éléments du type énuméré.
§ Pour tous les types énumérés, une méthode statique valueOf() est
implicitement déclarée.
§ Exemple d'utilisation avec importation statique des constantes :
§ Cette méthode convertit une chaîne de caractères (passée en
paramètre) en un élément du type énuméré ou lève l'exception
IllegalArgumentException si la conversion n'est pas possible
(attention, la méthode est sensible à la casse [minuscules/majuscules]).
import static ex.chap02.EColor.*;
public class TestEColor {
public static void main(String[] args) {
EColor color = BLUE;
if (args.length > 0) color = RED;
System.out.println(color);
}
§ Exemple de conversion d'une chaîne de caractères en un élément de
type énuméré :
public class TestEColor {
public static void main(String[] args) {
EColor yColor = EColor.valueOf("YELLOW");
System.out.println(yColor);
}
EColor pColor = EColor.valueOf(args[0]);
}
}
IHM-1 – FX02
– Jacques BAPST
6
IHM-1 – FX02
Quels risques ?
– Jacques BAPST
8
Énumérations [6]
Types génériques [1]
§ La méthode d'instance name() retourne un String correspondant
à la représentation textuelle d'une variable de type énuméré (elle
se comporte de la même manière que la méthode toString()).
§ Un type générique est une classe ou une interface qui prend en
paramètre un type.
• Exemple :
§ Prenons l'exemple d'une classe (non-générique) qui permet de
stocker et restituer un objet de type Integer :
String colName = yColor.name();
§ La méthode ordinal() retourne un int correspondant à la position
de l'élément dans la déclaration d'une variable de type énuméré (la
numérotation commence à zéro).
• Exemple :
int pos = yColor.ordinal();
public class IntegerBox {
private Integer anInt;
public Integer get() {
return anInt;
}
// 3 for YELLOW
§ Une énumération peut être définie à l'intérieur d'une classe ou dans
une unité de compilation séparée (comme toute classe Java).
§ Une énumération ne peut pas être définie dans une méthode.
}
§ Il est possible d'ajouter des champs, des constructeurs et des
méthodes dans une classe de type enum.
Consulter la
§ Que faudrait-il faire pour gérer de la même manière d'autres types
d'éléments, par exemple Float, String, Point, Rectangle, ... ?
documentation pour
plus de détails.
IHM-1 – FX02
– Jacques BAPST
public void set(Integer value) {
anInt = value;
}
9
IHM-1 – FX02
11
– Jacques BAPST
Types génériques [2]
§ Créer les variantes FloatBox, StringBox, ... ?
§ Créer une classe Box avec un champ de type Object ?
§ Une solution : créer une classe générique :
Généricité
Types génériques
public class Box<T> {
private T obj;
public T get() {
return obj;
}
Attention :
le principe
d'encapsulation
n'est respecté que
si T est immuable.
public void set(T obj) {
this.obj = obj;
}
}
§ La syntaxe className<type> permet de passer un type en paramètre
à la classe. Dans l'exemple précédent T peut être considéré comme
un paramètre formel qui représente un type (non-primitif)
quelconque.
IHM-1 – FX01
– Jacques BAPST
10
IHM-1 – FX02
– Jacques BAPST
12
Types génériques [3]
Types génériques [5]
§ Lorsqu'on utilise la classe (comme type ou pour l'instancier), on
passe, entre < et >, le type effectif souhaité :
§ On peut aussi utiliser un type générique comme paramètre effectif
d'un type paramétré.
• Par exemple pour créer une liste de "paires" :
public static void main(String[] args) {
Box<Integer> intBox = new Box<>();
// ou new Box<Integer>();
Box<String> strBox = new Box<>();
// ou new Box<String>();
Box<Point2D> p2dBox = new Box<>();
// ou new Box<Point2D>();
List< Pair<String, Integer> > pList = new ArrayList<>();
pList.add(new Pair<>("Small", 3));
intBox.set(5);
strBox.set("Hello");
p2dBox.set(new Point2D(1.2, 3.4));
System.out.println(p2dBox.get());
§ Pour assurer la compatibilité du code écrit avant le JDK 5.0, il est
possible d'omettre le paramètre effectif et de déclarer ce que l'on
nomme un raw type.
}
§ Dans ce cas, le type du paramètre est implicitement Object,
certaines limitations s'appliquent et des mises en garde peuvent
apparaître à la compilation (unchecked conversions).
§ On peut passer plus d'un type générique à une classe ou à une
interface en les séparant par une virgule :
public class PairBox<T, U> { … }
IHM-1 – FX02
– Jacques BAPST
13
Box b = new Box();
// Raw type
b.set(8);
b.set("Hello");
// Autoboxing int => Integer => Object
// String => Object
IHM-1 – FX02
15
– Jacques BAPST
Types génériques [4]
Types génériques [6]
§ Par convention, on utilise une seule lettre majuscule pour nommer
les paramètres formels de type.
§ Pour définir un paramètre générique qui inclut une classe donnée
ainsi que toutes ses sous-classes, on utilise la syntaxe :
< T extends type >
§ La syntaxe <> (nommée diamond) peut être utilisée lorsque le
compilateur est capable de déterminer (d'inférer) le type en fonction
du contexte.
§ Les interfaces génériques se basent exactement sur les mêmes
conventions que celles des classes génériques.
§ De nombreuses interfaces et classes de la plateforme Java
(notamment les Collections) utilisent la généricité.
List<Point2D>
polygon = new ArrayList<>();
– Jacques BAPST
public class ShapeBox<T extends Polygon> {
private T shape;
// Polygon or sub-class of Polygon
. . .
}
§ La formulation "extends type" doit être prise ici dans un sens
général (extends or implements). Elle doit être interprétée comme :
• La classe type ainsi que toutes ses sous-classes
• Toute classe qui implémente l'interface type
Pair<String, Integer> p1 = new Pair<>("Even", 8);
Pair<String, String> p2 = new Pair<>("Hi", "World");
IHM-1 – FX02
§ Par exemple :
ou
§ Avec cette syntaxe, on indique ainsi la "borne supérieure" du type
accepté comme paramètre (upper bound).
14
IHM-1 – FX02
– Jacques BAPST
16
Types génériques [9]
§ Le symbole '?' peut être utilisé comme caractère joker (wildcard)
dans différentes situations en lien avec les classes et interfaces
génériques : type d'un paramètre, d'un champ, d'une variable locale,
etc.
§ Lors de l'introduction de la généricité dans le langage, la machine
virtuelle n'a pas été modifiée. Une classe paramétrée est compilée
en une classe non paramétrée.
public static int countNonNull(List<?> list) {
int count = 0;
for (Object e : list) {
if (e != null) count++;
}
return count;
}
< < <
§ Par exemple :
Compléme nt
§ Il représente un type (inconnu) quelconque.
> > >
Types génériques [7]
§ Dans cet exemple, seules les méthodes de l'interface List ou de
Object (sur les éléments de la liste) peuvent être utilisées.
IHM-1 – FX02
public class Box<T> {
. . .
T temp = new T();
. . .
// Erreur à la compilation
}
17
– Jacques BAPST
§ Les variables de types sont donc perdues (oubliées) après la
compilation (type erasure). Certaines opérations qui utilisent les
types génériques à l'exécution (par exemple certains transtypages,
création d'objets, utilisation de instanceof, …) ne sont donc pas
autorisées et provoquent donc des erreurs (qui peuvent paraître un
peu surprenantes) lors de la compilation.
IHM-1 – FX02
– Jacques BAPST
Types génériques [8]
Types génériques [10]
§ Le caractère joker ? n'est pas équivalent à Object.
§ Le caractère joker '?' (unbounded wildcard) peut également être
combiné avec les mots-clés extends ou super pour définir des
bornes supérieures (upper bound) respectivement des bornes
inférieures (lower bound) pour les types acceptés.
: une liste d'éléments de n'importe quel type
• List<Object> : une liste d'éléments de type Object
> > >
• List<?>
List<Integer> n'est pas une sous-classe de List<Object>
Object
Integer
IHM-1 – FX02
List<Object>
X
List<Integer>
Object
List<Object>
– Jacques BAPST
List<Integer>
18
< < <
Compléme nt
§ Attention : Même si (par ex.) Integer est une sous-classe de Object,
19
• <? extends type> acceptera la classe type ainsi que toutes ses sous-
classes. Cette notation est également valable pour toutes les classes
qui implémentent l'interface type.
[upper bounded]
• <? super type> acceptera la classe type ainsi que toutes les classes
parentes de type.
[lower bounded]
§ Par exemple, pour ajouter des nombres à une liste d'Integer, de
Number ou d'Object :
public static void addOneToTen(List<? super Integer> list) {
for (int i = 1; i <= 10; i++) {
list.add(i);
}
}
IHM-1 – FX02
– Jacques BAPST
20
Types génériques [11]
< < <
Compléme nt
> > >
Quelques règles et recommandations
§ L'utilisation du caractère joker <? …> dépendra du rôle que joue le
paramètre générique dans la méthode/fonction :
• Paramètre 'in' (valeur utilisée en entrée de la méthode)
[Typiquement la méthode consomme les éléments d'une collection]
Annotations
• Paramètre 'out' (valeur utilisée en sortie, fournie par la méthode)
[Typiquement la méthode produit les éléments d'une collection]
• Paramètre 'in/out' (valeur utilisée en entrée et modifié par la méthode)
§ Il est recommandé d'appliquer les règles suivantes :
• Tous les paramètres 'in' seront définis avec extends (upper-bound)
• Tous les paramètres 'out' seront définis avec super (lower-bound)
• Pour les paramètres 'in' accédés exclusivement avec des méthodes de
Object, on utilisera un joker non borné (unbounded-wildcard <?>)
• Pour tous les paramètres 'in/out' on n'utilisera pas de joker
• On n'utilisera pas de joker comme type de retour
IHM-1 – FX02
– Jacques BAPST
21
IHM-1 – FX01
– Jacques BAPST
23
Types génériques [12]
Annotations [1]
§ Dans une classe (générique ou non) il est possible d'écrire une
méthode générique (statique ou non-statique).
§ En Java, une annotation constitue une métadonnée que l'on peut
appliquer à différents éléments du code source.
§ Syntaxiquement, le ou les types génériques <…> se placent, dans
l'en-tête, après les modificateurs et avant le type de retour.
§ Les annotations n'ont pas d'effet direct sur le fonctionnement des
instructions annotées.
§ Exemple :
§ Le caractère '@' indique au compilateur que l'identificateur qui suit
est une annotation.
public static <T> boolean contains(Box<T> box, T value) {
return box.get().equals(value);
}
§ Un exemple simple :
@Override
public String toString() {
//--- A String representation of current object
return "Circle: Radius=" + radius;
}
§ En général, le paramètre générique n'est pas nécessaire lors de
l'invocation de la méthode, il est inféré par le contexte.
Box.contains(fBox, 1.2f);
Box.<Float>contains(fBox, 1.2f);
IHM-1 – FX02
§ L'annotation @Override indique au compilateur que la méthode
redéfinit une méthode héritée. Il pourra ainsi générer une erreur si
l'en-tête n'est pas correcte
// Syntaxe rarement nécessaire
– Jacques BAPST
22
IHM-1 – FX02
– Jacques BAPST
24
Annotations [2]
Annotations [4]
§ Sans l'annotation @Override le code fonctionne parfaitement (et de
manière identique) mais le programmeur ne serait pas averti qu'il crée
une nouvelle méthode d'instance (faute de frappe par ex.) alors qu'il
voulait redéfinir une méthode d'une des classes parentes.
§ Il existe un certain nombre d'annotations prédéfinies, traitées par le
compilateur ou par certains outils standard de la plateforme Java.
§ Une annotation peut inclure un ou plusieurs paramètres :
§ Il est également possible de définir ses propres annotations en
écrivant une interface particulière (@interface …)
@Author (name = "Tryphon Tournesol",
date = "06.06.2016"
)
public class MyClass {
. . .
}
// Metadata about the class
import java.lang.annotation.Documented;
@Documented
// Will appear in Javadoc-generated documentation
public @interface ClassPreamble {
String
author();
// Parameter
String
date();
int
currentRevision() default 1; // Param. with default value
String
lastModifiedBy() default "N/A";
String[] reviewers();
// Array parameter
}
§ Si l'annotation ne comporte qu'un seul paramètre dont le nom est
value, le nom peut être omis :
@SuppressWarnings("unchecked")
public class MyClass {
. . . // Warnings about unchecked generic operations are suppressed
}
IHM-1 – FX02
– Jacques BAPST
• Exemples : @Deprecated, @Override, @SuppressWarnings,
@SafeVarargs, @FunctionalInterface, …
25
IHM-1 – FX02
– Jacques BAPST
27
Annotations [3]
§ Des annotations multiples peuvent être attribuées à une même
déclaration (une même annotation peut également être répétée).
@Author (name = "Tryphon Tournesol")
@Author (name = "Archibald Haddock")
@EBook
public class MyClass {
. . .
}
// Repeating annotations
// Repeating annotations
Expressions Lambda
Références de méthodes
§ Des annotations peuvent être appliquées à des déclarations (classes,
champs, méthodes, etc.) mais également lors de l'utilisation des types
(ces Type Annotations requièrent l'installation d'un Checker Framework,
voir par exemple types.cs.washington.edu/checker-framework).
public void check(String name) {
@NonNull String loginName = name;
. . .
}
IHM-1 – FX02
// Exception thrown if null
– Jacques BAPST
26
IHM-1 – FX01
– Jacques BAPST
28
Expressions lambda [1]
Expressions lambda [3]
§ Une expression lambda peut être assimilée à une fonction anonyme
qui a potentiellement accès au contexte du code englobant.
§ Pour illustrer l'utilisation des expressions lambda, nous allons
donner un premier exemple qui fait appel à l'interface fonctionnelle
Runnable (prédéfinie dans le package java.lang) :
§ Il s'agit essentiellement d'un bloc de code avec des paramètres et
qui est destiné à être exécuté ultérieurement (une ou plusieurs fois).
§ Une expression lambda peut être utilisée pour implémenter une
interface fonctionnelle (interface ne comportant qu'une seule méthode
d'instance abstraite).
§ La syntaxe de base est :
ou
(parameters) ->
expression
(parameters) -> { statements; }
– Jacques BAPST
§ Les objets de type Runnable sont notamment utilisés pour exécuter
du code en parallèle avec l'activité principale de l'application (par
exemple imprimer en tâche de fond pendant qu'on continue à travailler sur
l'application principale). Ces "processus légers" ou "fils d'exécution"
(pseudo-)parallèles sont nommé "threads".
§ Remarque : Les parenthèses autour des paramètres sont obligatoires
s'il y a zéro (burger arrow : () -> ) ou plusieurs paramètres,
mais optionnelles s'il n'y en a qu'un seul et que le type peut
être inféré (voir page suivante).
IHM-1 – FX02
@FunctionalInterface
public interface Runnable {
public void run();
}
§ Par défaut le code Java s'exécute dans un thread appelé main-thread
(créé automatiquement). Mais il est possible de créer d'autres threads
qui permettent une exécution parallèle (ou pseudo-parallèle) du code.
29
IHM-1 – FX02
– Jacques BAPST
31
Expressions lambda [2]
Expressions lambda [4]
§ S'il y a plusieurs paramètres, il seront séparés par des virgules.
§ Pour créer un thread il faut créer une instance de la classe Thread qui
se chargera d'exécuter le code transmis sans interrompre l'exécution
des instructions du thread en cours (exécution en parallèle avec elles).
§ Le type des paramètres peut être indiqué explicitement ou être
automatiquement déterminé par le compilateur (inféré) en fonction
du contexte.
§ Le type de retour est toujours inféré du contexte.
§ Concrètement, pour lancer du code dans un thread séparé, il faut
§ Exemples d'expressions lambda :
(int x, int y) -> { return x + y; }
// Types explicites, valeur de retour
(x, y) -> x + y
// Types inférés, valeur de retour
x -> x * x
// Type inféré, valeur de retour
() -> 123
// Pas de paramètre, valeur de retour
s -> System.out.println(s)
// Type inféré, pas de valeur de retour
() -> { for (int i=0; i<10; i++) doIt(); }
// Pas de valeur de retour
IHM-1 – FX02
§ Le constructeur de la classe Thread peut recevoir en paramètre un
objet de type Runnable dont la méthode run() déterminera les
instructions à exécuter dans le thread parallèle.
– Jacques BAPST
• Créer une instance de la classe Thread en passant l'objet Runnable
à son constructeur : Thread t = new Thread(myRunnable);
• Appeler la méthode start() de l'instance de Thread : t.start();
qui lancera les instructions de la méthode run(). La méthode start()
retourne immédiatement (elle n'attend pas la fin d'exécution de run()).
Pour information, il y a une autre manière de faire :
• Créer une sous-classe de Thread et redéfinir la méthode run().
Créer une instance et invoquer start().
30
IHM-1 – FX02
– Jacques BAPST
32
Expressions lambda [5]
Interfaces fonctionnelles [2]
Quelques interfaces fonctionnelles prédéfinies
§ Exemple : création de deux threads à l'aide d'expressions lambda.
public static void main(String[] args) {
Runnable rA = () -> { for (int i=0; i<100; i++)
System.out.println("A"+i); };
Runnable rB = () -> { for (int i=0; i<100; i++)
System.out.println("B"+i); };
Thread tA = new Thread(rA);
Thread tB = new Thread(rB);
tA.start();
tB.start();
// Création du thread
// Lancement du thread : méthode run()
}
Un des résultats
possibles à l'exécution
IHM-1 – FX02
– Jacques BAPST
A0
B0
B1
B2
A1
A2
B3
B4
B5
A3
B6
B7
B8
A4
B9
A5
A6
A7
A8
A9
A10
A11
B10
...
33
Interfaces fonctionnelles [1]
Interface
fonctionnelle
Type
Param
Type
Retour
Méthode
Description
abstraite
Runnable
-
void
run()
Exécute une action
Supplier<T>
-
T
get()
Fournit une valeur de type T
Consumer<T>
T
void
accept()
Consomme une valeur de type T
chain
void
accept()
Consomme des valeurs de type T et U
chain
R
apply()
Fonction avec un seul paramètre
compose,
andThen,
identity
BiFunction
T, U
<T, U, R>
R
apply()
Fonction avec deux paramètres
andThen
UnaryOperator
T
<T>
T
apply()
Opérateur unaire sur le type T
compose,
andThen,
identity
BinaryOperator
T, T
<T>
T
apply()
Opérateur binaire sur le type T
andThen
BiConsumer
T, U
<T, U>
Function
<T, R>
T
IHM-1 – FX02
Interfaces fonctionnelles [3]
Quelques interfaces fonctionnelles prédéfinies
Interface
fonctionnelle
Type
Param
Type
Retour
§ Deux exemples représentatifs (utilisés par la suite) :
Predicate<T>
T
boolean test()
Fonction booléenne (prédicat)
boolean test()
Fonction booléenne avec 2 paramètres
// Supplier : Represents a supplier of results
//---------------------------------------------------------------
BiPredicate
T, U
<T, U>
@FunctionalInterface
public interface Supplier<T> {
T get(); // Gets a T type result
}
Méthode
abstraite
Description
Autres
Méthodes
and, or,
negate,
isEqual
and, or,
negate
§ Il existe également un certain nombre d'interfaces fonctionnelles
prédéfinies pour les types primitifs int, long et double. Exemples :
// Function : Represents a function that accepts one argument
//
of type T and produces a result of type R
//---------------------------------------------------------------
@FunctionalInterface
public interface Function<T, R> {
R apply(T t); // Applies this function to the given argument
}
– Jacques BAPST
35
– Jacques BAPST
§ Les interfaces fonctionnelles jouent un rôle important en lien avec
les expressions lambda. Plusieurs de ces interfaces sont prédéfinies
(notamment dans le package java.util.function).
IHM-1 – FX02
Autres
Méthodes
34
Interface
fonctionnelle
Type
Param
Type
Retour
Méthode
abstraite
Description
IntFunction<T>
int
T
apply()
Fonction avec paramètre int
LongPredicate
long
boolean test()
Fonction booléenne avec paramètre long
and, or,
negate
...
...
...
. . . et beaucoup d'autres . . .
...
IHM-1 – FX02
...
– Jacques BAPST
Autres
Méthodes
36
Références de méthodes [1]
Références de méthodes [3]
§ Une méthode existante peut être utilisée en tant qu'implémentation
de la méthode abstraite d'une interface fonctionnelle.
§ Dans la troisième variante (Class::instanceMethodName), même si on
mentionne le nom de la classe en préfixe, c'est bien une méthode
d'instance qui sera invoquée. Le premier paramètre de l'expression
lambda représente l'objet sur lequel la méthode sera invoquée.
§ On définit pour cela une référence de méthode avec l'opérateur ::
§ Trois variantes existent :
§ Exemple variante 3 :
Class :: staticMethodName
instance :: instanceMethodName
Class :: instanceMethodName
Function<String, Integer> fLen = String::length;
String s1 = "Hello";
String s2 = "World !";
§ Exemples avec équivalences en expressions lambda :
int s1Len = fLen.apply(s1);
int s2Len = fLen.apply(s2);
// s1.length()
// s2.length()
Variante
Exemple
Lambda équivalent
Class::staticMethodName
String::valueOf
(s) -> String.valueOf(s)
instance::instanceMethodName
obj::toString
() -> obj.toString()
Class::instanceMethodName
String::toString
(s) -> s.toString()
§ Les références de méthodes représentent donc une alternative aux
expressions lambda dans le cas où il n'y a qu'une seule et unique
méthode à exécuter.
Class::instanceMethodName
String::charAt
(s, i) -> s.charAt(i)
§ Cela améliore généralement la lisibilité et la clarté du code.
IHM-1 – FX02
37
– Jacques BAPST
IHM-1 – FX02
– Jacques BAPST
39
Références de méthodes [2]
Références de constructeurs [1]
§ Dans les deux premières variantes de syntaxe (Class::staticMethodName et
instance::instanceMethodName), la référence de méthode est équivalente à
une expression lambda qui fournit les paramètres de la méthode.
§ Les références de constructeurs sont semblables aux références de
méthodes à la différence que le nom de la "méthode" est new.
§ Exemple variante 1 :
Supplier<Double> random = Math::random;
double result = random.get(); // Math.random();
§ Exemple variante 2 :
Random r = new Random();
Supplier<Double> random = r::nextDouble;
double result2 = random.get(); // r.nextDouble();
§ Syntaxe :
Invocation sur
l'instance r
– Jacques BAPST
Class :: new
§ Exemples avec équivalences en expressions lambda :
Ces deux exemples utilisent l'interface
fonctionnelle prédéfinie Supplier<T>
décrite dans les pages précédentes.
IHM-1 – FX02
§ Les références de constructeurs servent à instancier une interface
fonctionnelle avec un constructeur compatible avec le contexte
d'utilisation.
38
Syntaxe
Exemple
Lambda équivalent
Class::new
String::new
() -> new String()
Class::new
PointHolder::new
(p) -> new PointHolder(p)
IHM-1 – FX02
– Jacques BAPST
40
Références de constructeurs [2]
Accès aux variables locales [2]
§ Exemple d'utilisation de références de constructeurs pour créer des
objets de type Button (un des composants classiques de JavaFX) .
§ Dans l'exemple qui suit, les variables text et count ne sont pas
définies dans l'expression lambda. Ce sont des paramètres de la
méthode englobante capturés par l'expression lambda (variables
locales effectivement finales).
• Un constructeur de la classe Button permet de passer le libellé du
bouton en paramètre.
public static void repeatMessage(String text, int count) {
Runnable r = () -> { for (int i=0; i<count; i++) {
System.out.println(text);
Thread.yield(); // Gives a chance other threads to run
}
};
new Thread(r).start();
}
Function<String, Button> btFactory = Button::new;
Button bOk
= btFactory.apply("Ok");
Button bQuit = btFactory.apply("Quit");
// new Button("Ok");
// new Button("Quit");
§ Ces variables restent accessibles malgré l'exécution différée du code.
public static void main(String[] args) {
repeatMessage("Hello", 500);
repeatMessage("Jane", 1000);
}
IHM-1 – FX02
– Jacques BAPST
41
IHM-1 – FX02
– Jacques BAPST
43
Accès aux variables locales [1]
§ En plus des éventuels paramètres définis, des variables déclarées
dans son corps et des champs statiques, une expression lambda à
accès à toutes les variables locales qui sont effectivement finales.
§ Une variable effectivement finale est une variable locale :
• déclarée avec le modificateur final
ou
• non modifiée après sa première affectation
Stream
§ Une expression lambda qui utilise (capture) des variables locales
externes est parfois désignée par le terme de capturing lambda.
§ On utilise également le terme closure pour désigner le bloc de code
de l'expression lambda accompagné de l'ensemble des variables
locales capturées.
IHM-1 – FX02
– Jacques BAPST
42
IHM-1 – FX01
– Jacques BAPST
44
Stream API [1]
Stream API [3]
§ Un stream représente une séquence d'éléments. Cette structure
supporte différents types d'opérations sur ces éléments.
§ Les opérations appliquées à un stream peuvent être :
§ Les streams constituent un concept important en lien avec les
expressions lambda qui sont souvent utilisées pour les manipuler.
§ L'essentiel des librairies (API) liées aux streams se trouve dans le
package java.util.stream avec notamment l'interface Stream<T>
§ Il est possible de créer un stream à partir d'un tableau, d'une
collection, d'un générateur (Stream.generate()), d'un itérateur
(Stream.iterate()), d'un canal d'entrée/sortie, etc.
• D'un stream
IHM-1 – FX02
:
Arrays.stream(array)
§ Après avoir exécuté une opération terminale, un stream est fermé
et ne peut plus être réutilisé (si on veut le faire, il faut en créer un
nouveau).
collection.stream()
Stream.of(v1, v2, …)
stream.filter(predicate)
45
– Jacques BAPST
Stream API [2]
§ Un exemple d'utilisation à partir d'une liste de String sur laquelle
on applique une série d'opérations :
public static void main(String[] args) {
List<String> myList =
Arrays.asList("a1", "a2", "b1", "c2", "d1", "c1", "e2");
myList.stream()
.filter(s -> s.startsWith("c"))
.map(String::toUpperCase)
.sorted()
.forEach(System.out::println);
Operation
pipeline
}
§ A l'exécution, ce programme affichera :
C1
C2
§ Une expression lambda et deux références de méthodes sont
utilisées dans cet exemple pour définir certaines des opérations
effectuées sur le flux.
IHM-1 – FX02
– Jacques BAPST
§ Les opérations intermédiaires ne sont exécutées que s'il y a une
opération terminale (laziness).
§ L'ordre dans lequel les opérations sont enchaînées peut jouer un
rôle important sur le résultat et sur le nombre d'opérations à
effectuer par le système (typiquement, un filtrage est à effectuer le
plus tôt possible dans la chaîne des opérations).
§ Quelques manières classiques de créer un stream à partir :
• D'un tableau
:
• D'une collection
:
• D'une séquence de valeurs :
• Intermédiaires : elles retournent un stream ce qui permet un
enchaînement d'opérations (pipeline, fluent API)
• Terminales
: elles ne retournent rien (effectuent une opération)
ou retournent autre chose qu'un stream
46
IHM-1 – FX02
– Jacques BAPST
47
Première application [1]
§ Comment se présente une application JavaFX ?
Cours IHM-1
JavaFX
(petite immersion avant de décrire plus en détail les concepts de base).
§ L'application est codée en créant une sous-classe de Application.
§ La fenêtre principale d'une application est représentée par un objet
de type Stage qui est fourni par le
système au lancement de l'application.
3 - Architecture
Concepts techniques
§ L'interface est représentée par un objet
de type Scene qu'il faut créer et associer
à la fenêtre (Stage).
§ La scène est composée des différents
éléments de l'interface graphique
(composants de l'interface graphique)
qui sont des objets de type Node.
Jacques BAPST
[email protected]
§ La méthode start() construit le tout.
IHM-1 – FX03
– Jacques BAPST
3
Métaphore de la salle de spectacle
§ Les éléments structurels principaux d'une application JavaFX se
basent sur la métaphore de la salle de spectacle (theater).
Remarque : En français, on utilise le terme 'scène' pour parler de l'endroit où se passe
le spectacle (l'estrade, les planches) mais
également pour parler de ce qui s'y déroule
(jouer ou tourner une scène) ce qui peut
conduire à un peu de confusion avec cette
métaphore.
Une première immersion
( Hello World ! )
§ Stage
: L'endroit où a lieu l'action, où
se déroule la scène
§ Scene
: Tableau ou séquence faisant
intervenir les acteurs
§ Controls : Acteurs, figurants, éléments du
Components décor, … (éléments actifs/passifs)
Nodes
qui font partie de la scène en
Widgets
train d'être jouée.
IHM-1 – FX03
– Jacques BAPST
2
IHM-1 – FX03
– Jacques BAPST
4
Hello World [1]
Cycle de vie d'une application [1]
§ Une première application, le traditionnel Hello World !
§ Le point d'entrée d'une application JavaFX est constitué d'une
instance de la classe Application (généralement une sous-classe).
public class HelloWorld extends Application {
//--------------------------------------------@Override
public void start(Stage primaryStage) {
primaryStage.setTitle("My First JavaFX App");
BorderPane root = new BorderPane();
Button btnHello = new Button("Hello World");
root.setCenter(btnHello);
Scene scene = new Scene(root, 250, 100);
primaryStage.setScene(scene);
primaryStage.show();
}
§ Lors du lancement d'une application par la méthode statique
Application.launch() le runtime JavaFX effectue les opérations
suivantes :
1. Crée une instance de la classe qui hérite de Application
2. Appelle la méthode init()
3. Appelle la méthode start() et lui passe en paramètre une instance
de Stage (qui représente la fenêtre principale [primary stage])
4. Attend ensuite que l'application se termine; cela se produit lorsque :
ü
//--------------------------------------------public static void main(String[] args) {
launch(args);
}
ü
La dernière fenêtre de l'application a été fermée
(et Platform.isImplicitExit() retourne true)
L'application appelle Platform.exit() (ne pas utiliser System.Exit())
5. Appelle la méthode stop()
}
launch()
IHM-1 – FX03
5
– Jacques BAPST
IHM-1 – FX03
init()
start()
stop()
– Jacques BAPST
Hello World [2]
Cycle de vie d'une application [2]
§ JavaFX étant intégré à la plateforme de base Java, aucune librairie
externe n'est nécessaire au fonctionnement de l'exemple précédent.
§ La méthode launch() est généralement lancée depuis la méthode
main(). Elle est implicitement lancée s'il n'y a pas de méthode
main() (ce qui est toléré depuis Java 8).
§ Un certain nombre d'importations doivent cependant être
effectuées (on y retrouve les classes principales Application, Stage,
§ D'éventuels paramètres de lancement peuvent être récupérés en
invoquant la méthode getParameters() dans la méthode init()
ou ultérieurement (par ex dans la méthode start()).
Scene ainsi que les composants BorderPane et Button) :
•
•
•
•
•
import
import
import
import
import
javafx.application.Application;
javafx.scene.Scene;
javafx.scene.control.Button;
javafx.scene.layout.BorderPane;
javafx.stage.Stage;
En principe, Eclipse
s'en charge
automatiquement
§ Au lancement d'une application JavaFX, il est possible de passer des
paramètres nommés (syntaxe --name=value) ou anonymes.
• java -jar Hello.jar --steps=50 --mode=Debug 300 FR
§ Dans cet exemple, la méthode main() lance uniquement la méthode
statique launch() (qui est héritée de Application).
• Dans une application un peu plus complexe, elle pourrait effectuer
d'autres opérations d'initialisation avant l'invoquer launch().
IHM-1 – FX03
– Jacques BAPST
7
§ getParameters() retourne un objet de type Parameters dont on
peut extraire les valeurs des paramètres à l'aide des méthodes :
• getRaw()
List<String>
Liste brute de tous les paramètres
• getNamed()
Map<String, String> Liste des paramètres nommés
• getUnnamed() List<String>
Liste des paramètres anonymes
6
IHM-1 – FX03
– Jacques BAPST
8
Cycle de vie d'une application [3]
§ La méthode start() est abstraite et doit être redéfinie.
§ Les méthodes init() et stop() ne doivent pas obligatoirement être
redéfinies (par défaut elle ne font rien).
§ La méthode start() s'exécute dans le JavaFX Application Thread.
C'est dans ce thread que doit être construite l'interface (notamment
la création de l'objet Scene) et que doivent être exécutées toutes les
opérations qui agissent sur des composants attachés à une scène
placée dans une fenêtre (live components).
Propriétés
§ La méthode stop() s'exécute aussi dans le JavaFX Application
Thread.
§ La méthode init() ainsi que le constructeur de la classe qui étend
Application s'exécutent par contre dans le thread JavaFX Launcher.
• Dans ce code, il est possible de créer des composants et des conteneurs
mais on ne peut pas les placer dans une scène active (live scene).
IHM-1 – FX03
– Jacques BAPST
9
IHM-1 – FX03
– Jacques BAPST
Traiter une action de l'utilisateur
Notion de propriété [1]
§ Dans l'exemple Hello World, pour que le clic sur le bouton déclenche
une action, il faut traiter l'événement associé.
§ La notion de "propriété" (property) est très présente dans JavaFX.
§ La méthode setOnAction() du bouton permet d'enregistrer un
Event Handler (c'est une interface fonctionnelle possédant la méthode
handle(event) qui définit l'action à effectuer).
btnHello.setOnAction(
event -> System.out.println("Hello World !"));
§ En plus des méthodes get…() et set…(), les propriétés JavaFX
possèdent une troisième méthode …Property() qui retourne un
objet qui implémente l'interface Property [ … : nom de la propriété ].
§ Intérêt des propriétés :
• Elles peuvent être liées entre-elles (Binding), c-à-d que le changement
d'une propriété entraîne automatiquement la mise à jour d'une autre.
• Elles peuvent déclencher un événement lorsque leur valeur change et
un gestionnaire d'événement (Listener) peut réagir en conséquence.
root.setCenter(btnHello);
Scene scene = new Scene(root, 250, 100);
primaryStage.setScene(scene);
primaryStage.show();
}
– Jacques BAPST
§ Une propriété est un élément d'une classe que l'on peut manipuler
à l'aide de getters (lecture) et de setters (écriture).
§ Les propriétés sont généralement représentées par des attributs de
la classe mais elles pourraient aussi être stockées dans une base de
données ou autre système d'information.
public void start(Stage primaryStage) {
primaryStage.setTitle("My First JavaFX App");
BorderPane root = new BorderPane();
Button btnHello = new Button("Say Hello");
IHM-1 – FX03
11
10
IHM-1 – FX03
– Jacques BAPST
12
Notion de propriété [2]
Notion de propriété [4]
§ Exemple d'une classe définissant la propriété balance (solde) pour
un compte bancaire.
public class BankAccount {
private DoubleProperty balance = new SimpleDoubleProperty();
public final double getBalance() {
return balance.get();
}
public final void setBalance(double amount) {
balance.set(amount);
}
public final DoubleProperty balanceProperty() {
return balance;
}
Par convention, les
getters et les setters
sont déclarés final.
Interfaces principales
associées aux propriétés.
. . .
}
IHM-1 – FX03
– Jacques BAPST
13
IHM-1 – FX03
15
– Jacques BAPST
Notion de propriété [3]
Propriétés read-only [1]
§ La classe abstraite DoubleProperty permet d'emballer une valeur
de type double et d'offrir des méthodes pour consulter et modifier
la valeur mais également pour "observer" et "lier" les changements.
§ Il existe des propriétés qui sont en lecture seule (read-only property).
§ SimpleDoubleProperty est une classe concrète prédéfinie.
§ La plateforme Java offre des classes similaires pour la plupart des
types primitifs, les chaînes de caractères, certaines collections ainsi
que le type Object qui peut couvrir tous les autres types.
/
/
• ListProperty<E>
/
• ObjectProperty<T> /
• IntegerProperty
SimpleIntegerProperty
• StringProperty
SimpleStringProperty
SimpleListProperty<E>
§ La création de telles propriétés nécessite l'utilisation de classes
wrappers qui servent à isoler la partie de la propriété en lecture
seule (visible de l'extérieur) de la partie qui permet de modifier la
valeur de la propriété, ce qui doit être possible uniquement dans le
composant (bean) qui définit cette propriété.
§ A l'interieur du wrapper, la propriété en lecture seule est en réalité
liée (bound) à une autre propriété interne qui est, elle, en lecture et
écriture.
Visible à
l'extérieur
SimpleObjectProperty<T>
• ...
§ Des classes existent également pour définir des propriétés read-only
Intérieur du
composant (bean)
• ReadOnlyIntegerWrapper, ReadOnlyIntegerProperty
• ...
IHM-1 – FX03
– Jacques BAPST
14
IHM-1 – FX03
– Jacques BAPST
16
Propriétés read-only [2]
Observation des propriétés [1]
§ Exemple d'une classe définissant la propriété balance (solde) pour
un compte bancaire en lecture seule (read-only).
§ Toutes les classes de propriétés implémentent l'interface
Observable et offrent de ce fait, la possibilité d'enregistrer des
observateurs (Listener) qui seront avertis lorsque la valeur de la
propriété change.
public class BankAccount {
private ReadOnlyDoubleWrapper balance = new ReadOnlyDoubleWrapper();
§ Une instance de l'interface fonctionnelle ChangeListener<T>
pourra ainsi être créée pour réagir à un tel changement. La méthode
changed() sera alors invoquée et recevra en paramètre la valeur
observée ainsi que l'ancienne et la nouvelle valeur de la propriété.
public final double getBalance() {
return balance.get();
}
public final void addInterests(double interestsRate) {
double newBalance = balance.get() * (1+interestsRate);
balance.set(newBalance);
Mise à jour de la propriété
}
seulement possible en interne.
DoubleProperty sum = account.balanceProperty();
sum.addListener( (ObservableValue<? extends Number> obsVal,
Number
oldVal,
Number
newVal ) ->
{
//--- ChangeListener code
System.out.println(oldVal+" becomes "+ newVal);
. . .
} );
public final ReadOnlyDoubleProperty balanceProperty() {
return balance.getReadOnlyProperty();
}
Et surtout pas :
. . .
return balance;
}
– Jacques BAPST
17
IHM-1 – FX03
– Jacques BAPST
19
Observation des propriétés [2]
§ Exemple de diagramme
de classes (partiel) pour
les propriétés Integer.
Remarque: Dans l'exemple précédent, une expression lambda est utilisée
pour implémenter la méthode changed(). On pourrait également
utiliser une classe anonyme ou créer l'instance d'une classe
"ordinaire" qui implémente l'interface ChangeListener<T>.
> > >
API des propriétés JavaFX
Compléme nt
§ Des classes/interfaces
équivalentes existent
pour les autres types
(Boolean, Double, Float,
Long, String, List, Map,
Set, Object)
• Cela donne une idée de
la complexité des API
des propriétés JavaFX.
• On utilise généralement
seulement quelquesunes de ces classes.
IHM-1 – FX03
< < <
< < <
Compléme nt
> > >
IHM-1 – FX03
– Jacques BAPST
18
§ L'interface fonctionnelle InvalidationListener<T> permet
également de réagir aux changements des valeurs de propriétés
dans les situations où les propriétés sont calculées à partir d'autres
et que l'on veut éviter d'effectuer les calculs à chaque changement.
§ Avec cette interface, c'est la méthode invalidated(Observable o)
qui est invoquée lorsqu'un changement potentiel de la valeur de la
propriété est intervenu.
§ Cette méthode peut cependant être invoquée alors que le résultat
observé n'a finalement pas changé (cela dépend des opérations
effectuées).
IHM-1 – FX03
– Jacques BAPST
20
Propriétés des composants
Lier des propriétés [2]
§ Les composants utilisés dans les interfaces graphiques (boutons,
champs texte, cases à cocher, sliders, etc.) possèdent tous de
nombreuses propriétés.
§ Illustration des liens possibles entre deux propriétés A et B :
A
§ Pour chacun des composants, la documentation (Javadoc) décrit
dans une des rubriques (Property Summary) la liste des propriétés
de la classe concernée ainsi que celles qui sont héritées.
IHM-1 – FX03
– Jacques BAPST
A
B.bindBidirectional(A)
B.bind(A)
21
ou
A.bindBidirectional(B)
B
B
Liaison unidirectionnelle
Liaison bidirectionnelle
(la valeur de la propriété B
dépend de la valeur de la
propriété A)
(la valeur de la propriété B
dépend de la valeur de la
propriété A et inversement)
IHM-1 – FX03
– Jacques BAPST
Lier des propriétés [1]
Lier des propriétés [3]
§ Un des avantages des propriétés JavaFX est la possibilité de pouvoir
les lier entre-elles. Ce mécanisme, appelé binding, permet de mettre
à jour automatiquement une propriété en fonction d'une autre.
§ La méthode bind() permet de créer un lien unidirectionnel.
§ Dans les interfaces utilisateurs, on a fréquemment ce type de liens.
• Par exemple, lorsqu'on déplace le curseur
d'un slider, la valeur d'un champ texte
changera (ou la luminosité d'une image,
la taille d'un graphique, le niveau sonore,
etc.).
BankAccount account = new BankAccount();
slider
= new Slider();
slider.valueProperty().bind(account.balanceProperty());
• Unidirectionnelle : un changement de A entraînera un changement
de B mais pas l'inverse (B non modifiable autrement)
• Bidirectionnelle : un changement de A entraînera un changement
de B et réciproquement (les deux sont modifiables)
– Jacques BAPST
§ La méthode doit être appelée sur la propriété qui sera "soumise" à
l'autre (celle qui est passée en paramètre).
Slider
§ Il est possible de lier deux propriétés A
et B de manière
IHM-1 – FX03
23
22
§ Dans cet exemple la position du curseur du slider est liée (asservie) à
la valeur du solde du compte bancaire. Si l'on tente de modifier la
valeur de la propriété associée à la position du slider (qui deviendra
non-éditable) d'une autre manière, une exception sera générée.
§ Une liaison bidirectionnelle s'effectue de manière similaire, mais en
utilisant la méthode bindBidirectional().
§ Pour supprimer le lien : unbind() et unbindBidirectional()
IHM-1 – FX03
– Jacques BAPST
24
Lier des propriétés [4]
Lier des propriétés [6]
§ Une propriété ne peut être liée (asservie) qu'à une seule autre si le
lien est unidirectionnel (bind()). Par contre, les liens bidirectionnels
(bindBidirectional()) peuvent être multiples.
§ Même exemple, en utilisant des méthodes chaînées (Fluent API) :
Slider
Slider
Slider
§ Parfois, une propriété dépend d'une autre mais avec une relation
plus complexe. Il est ainsi possible de créer des propriétés calculées.
areaPty.bind(widthPty.multiply(heightPty));
§ Un jeu d'opérations est disponible aussi bien avec la classe Bindings
qu'avec les méthodes chaînables.
§ Des opérations de conversions sont parfois nécessaires si le type des
propriétés à lier n'est pas le même. Par exemple pour lier un champ
texte (StringProperty) à un slider dont la valeur est numérique
(DoubleProperty).
IHM-1 – FX03
25
– Jacques BAPST
// Input
// Input
// Ouput
DoubleProperty widthPty = widthSlider.valueProperty();
DoubleProperty heightPty = heightSlider.valueProperty();
DoubleProperty areaPty
= areaSlider.valueProperty();
§ Deux techniques sont à disposition (elles peuvent être combinées) :
• Utiliser la classe utilitaire Bindings qui possède de nombreuses
méthodes statiques permettant d'effectuer des opérations.
• Utiliser les méthodes disponibles dans les classes qui représentent
les propriétés; ces méthodes peuvent être chaînées (Fluent API).
widthSlider = new Slider();
heightSlider = new Slider();
areaSlider
= new Slider();
•
•
•
•
•
min(), max()
equal(), notEqual(), lessThan(), lessThanOrEqual(), …
isNull(), isNotNull(), isEmpty(), isNotEmpty()
convert(), concat(), format(), …
et beaucoup d'autres . . .
IHM-1 – FX03
27
– Jacques BAPST
Lier des propriétés [5]
Lier des propriétés [7]
§ Exemple de binding permettant de coupler la valeur d'un slider
(Area) aux valeurs d'entrée de deux autres sliders (Width et Height)
§ Si les opérations disponibles dans les API, dites de haut-niveau, ne
permettent pas d'exprimer la relation entre les propriétés, il est
possible de définir une liaison de plus bas niveau (low-level binding)
en redéfinissant la méthode abstraite computeValue() d'une des
classes de binding (DoubleBinding, BooleanBinding, StringBinding, …).
§ En utilisant les méthodes statiques de la classe Bindings :
Slider
Slider
Slider
widthSlider = new Slider();
heightSlider = new Slider();
areaSlider
= new Slider();
// Input
// Input
// Ouput
DoubleBinding complexBinding = new DoubleBinding() {
{
//--- Set listeners to 'in'-properties
super.bind(aSlider.valueProperty(), bSlider.valueProperty());
}
DoubleProperty widthPty = widthSlider.valueProperty();
DoubleProperty heightPty = heightSlider.valueProperty();
DoubleProperty areaPty
= areaSlider.valueProperty();
@Override //--- Compute 'out' value
protected double computeValue() {
double w = aSlider.valueProperty().get();
double h = bSlider.valueProperty().get();
return Math.sqrt(h) * Math.pow(w, 3);
}
};
areaPty.bind(Bindings.multiply(widthPty, heightPty));
Width Slider
Height Slider
Devient non éditable
car lié aux deux autres
IHM-1 – FX03
Area Slider
(produit des 2 valeurs)
– Jacques BAPST
outSlider.valueProperty().bind(complexBinding);
26
IHM-1 – FX03
– Jacques BAPST
// Do the binding
28
Éléments d'une application
Component
Control
§ Structure générale d'une
application JavaFX.
Architecture JavaFX
Concepts techniques
Stage
Container
Layout
Component
Control
Scene
Component
Control
Container
Layout
...
Component
Control
Container
Layout
Component
Control
...
Component
Control
...
...
...
Graphe de scène
(défini en Java ou avec FXML)
IHM-1 – FX03
29
– Jacques BAPST
IHM-1 – FX03
– Jacques BAPST
Architecture technique [1]
Graphe de scène [1]
§ L'architecture technique de la plateforme JavaFX est composée de
plusieurs couches (library stack) qui reposent sur la machine virtuelle
Java (JVM).
§ Le graphe de scène (scene graph) est une notion importante qui
représente la structure hiérarchique de l'interface graphique.
Applications JavaFX
Public Layer
Private Layer
Native Layer
31
§ Techniquement, c'est un graphe acyclique orienté (arbre orienté)
avec :
• une racine (root)
• des nœuds (nodes)
• des arcs qui représentent les relations parent-enfant
§ Les nœuds (nodes) peuvent être
de trois types :
§ Les développeurs ne devrait utiliser que la couche (API) publique car
les couches inférieures sont susceptibles de subir des changements
importants au fil des versions (sans aucune garantie de compatibilité).
• Racine
• Nœud intermédiaire
• Feuille (leaf)
§ Les "briques" intermédiaires ne seront donc pas décrites dans ce
cours.
IHM-1 – FX03
– Jacques BAPST
30
IHM-1 – FX03
– Jacques BAPST
32
Graphe de scène [2]
Graphe de scène [4]
§ Les feuilles de l'arbre sont généralement constitués de composants
visibles (boutons, champs texte, …) et les nœuds intermédiaires (y
compris la racine) sont généralement des éléments de structuration
(souvent invisibles), typiquement des conteneurs ou panneaux de
différents types (HBox, VBox, BorderPane, …).
§ Parmi les sous-classes de Node on distingue différentes familles :
• Les formes primitives (Shape) 2D et 3D
ð
Line, Circle, Rectangle, Box, Cylinder, …
• Les conteneurs (Layout-Pane) qui se chargent
de la disposition (layout) des composants enfants
et qui ont comme classe parente Pane.
ð
AnchorPane, BorderPane, GridPane, HBox, VBox, …
• Les composants standard (Controls) qui
étendent la classe Control.
ð
Label, Button, TextField, ComboBox, …
• Les composants spécialisés qui sont dédiés à
un domaine particulier (par exemple : lecteur
multimédia, navigateur web, etc.).
ð
IHM-1 – FX03
33
– Jacques BAPST
MediaView, WebView, ImageView, Canvas, Chart, …
IHM-1 – FX03
35
– Jacques BAPST
Graphe de scène [3]
Graphe de scène [5]
§ Tous les éléments contenus dans un graphe de scène sont des objets
qui ont pour classe parente la classe Node.
§ Application JavaFX et son
graphe de scène.
§ La classe Node comporte de nombreuses sous-classes :
Stage
HBox
Conteneur
Button
Composant
Scene
BorderPane
Composants
(Controls)
Conteneurs
(Layout-Panes)
[simplifié]
[simplifié]
Label
Simplifié !
(il y a des classes intermédiaires)
IHM-1 – FX03
– Jacques BAPST
34
VBox
ChoiceBox TextField
IHM-1 – FX03
HBox
Button
Button
VBox
ChoiceBox TextField
– Jacques BAPST
HBox
Button
Button
36
Conteneurs
Region - Structure visuelle [2]
§ Les conteneurs (Layout-Pane) représentent une famille importante
parmi les sous-classes de Node. Ils ont pour classe parente Pane et
Region qui possèdent de nombreuses propriétés et méthodes
héritées par tous les conteneurs.
§ Comme la classe Region est la classe parente de tous les conteneurs
et de tous les composants, ses propriétés sont héritées et peuvent
s'appliquer à une grande variété d'éléments qui sont utilisés pour
créer les interfaces.
§ Parmi ces propriétés, on peut mentionner :
• border
ð Bordure autour de la région
• background
ð Couleur ou image d'arrière-plan de la région
• padding
ð Marge (espace) autour du contenu de la région
§ L'utilisation et le codage des propriétés border et background sont
expliqués plus en détail dans le chapitre suivant consacré aux
conteneurs et layout-panes (à la section Disposition des composants Généralités).
...
IHM-1 – FX03
37
– Jacques BAPST
IHM-1 – FX03
– Jacques BAPST
Region - Structure visuelle [1]
Style – Look and Feel [1]
§ La classe Region est la classe parente des composants (Controls) et
des conteneurs (Layout-Panes).
§ La notion de style, skin, thème ou look and feel (L&F) caractérise
l'ensemble des aspects visuels de l'interface graphique et de ses
composants (forme, couleur, texture, ombre, police de caractères, …).
§ Elle définit des propriétés qui affectent la représentation visuelle.
§ En JavaFX, le style des composants est défini par des feuilles de style
de type CSS. Il est ainsi possible de changer globalement l'aspect de
l'interface sans avoir à modifier le code de l'application.
§ Les différentes zones d'une région sont basées sur la spécification
du Box-Model CSS 3 (selon normalisation du W3C).
• Elles définissent les notions Margin, Border, Padding, Insets, Content
§ La méthode setUserAgentStylesheet() de la classe Application
permet d'indiquer l'URL de la feuille de style qui est à appliquer
globalement.
Les zones Border et
Padding définissent
les Insets (encarts)
de la région
§ Deux styles, nommés Modena et Caspian, sont prédéfinis; ils sont
associés aux constantes :
• STYLESHEET_MODENA
Les composants
enfants sont ajoutés
dans la zone Content
IHM-1 – FX03
– Jacques BAPST
39
• STYLESHEET_CASPIAN
38
IHM-1 – FX03
: Utilisé par défaut depuis JavaFX 8
: A été défini pour JavaFX 2
– Jacques BAPST
40
Style – Look and Feel [2]
§ Style Modena sur différents OS.
IHM-1 – FX03
41
– Jacques BAPST
Style – Look and Feel [3]
§ Le style utilisé par défaut peut évoluer au fil des versions de JavaFX.
§ Si l'on veut fixer ou changer le look and feel des interfaces, on peut
le faire au démarrage de l'application :
@Override
public void start(Stage primaryStage) {
. . .
setUserAgentStylesheet(STYLESHEET_CASPIAN);
. . .
§ Des styles externes (third-party) peuvent également être importés
et appliqués. On trouve différentes réalisations, par exemple :
•
•
•
•
Apple Aqua
Microsoft Modern UI
Twitter Bootstrap
Flatter (Embedded UI),
IHM-1 – FX03
(AquaFX)
(JMetro)
(Fextile)
...
Windows-7 Aero
– Jacques BAPST
(AeroFX)
42
Disposition des composants [1]
§ La qualité d'une interface graphique repose sur de nombreux
éléments mais la disposition des composants dans la fenêtre figure
certainement parmi les plus importants.
Cours IHM-1
JavaFX
§ Quand on parle de la disposition (layout) d'une interface, on
s'intéresse plus particulièrement :
• A la taille des composants
• A la position des composants
4 - Conteneurs
Layout-Panes
ð
ð
Position dans la fenêtre
Position relative des éléments les uns par rapport aux autres
• Aux alignements et espacements qui favorisent la structuration visuelle
• Aux bordures et aux marges (notamment autour des conteneurs)
• Au comportement dynamique de l'interface lorsqu'on redimensionne
la fenêtre, lorsqu'on déplace une barre de division (splitpane), etc.
Jacques BAPST
[email protected]
IHM-1 – FX04
– Jacques BAPST
3
Disposition des composants [2]
§ Avec JavaFX, il est possible de dimensionner et de positionner les
composants de manière absolue (en pixels, mm, etc.).
§ Une disposition avec des valeurs absolues peut être utile et même
nécessaire dans certaines situations particulières. Cependant, dans
la plupart des cas, elle présente de nombreux inconvénients, car :
Disposition des composants
Généralités
Notions de base
• La taille naturelle des composants peut varier, en fonction
De la langue choisie (libellés, boutons, menus, …)
De la configuration de la machine cible (paramètres de l' OS)
ð Du look & feel, thème, skin, style (CSS) choisi par l'utilisateur
ð
ð
• La taille de la fenêtre peut également varier
ð
ð
IHM-1 – FX04
– Jacques BAPST
2
Par le souhait de l'utilisateur
Par obligation, pour s'adapter à la résolution de l'écran de
la machine cible (pour afficher l'intégralité de l'interface)
IHM-1 – FX04
– Jacques BAPST
4
Disposition des composants [3]
Système de coordonnées [1]
§ Pour éviter ces inconvénients on préfère déléguer la disposition des
composants à des gestionnaires de disposition (layout managers)
qui sont associés à des conteneurs.
§ Le système de coordonnées utilisé par JavaFX place l'origine dans le
coin supérieur gauche du conteneur ou du composant considéré.
§ L'idée principale est de définir des règles de disposition (des
contraintes) que le gestionnaire se chargera de faire respecter en
fonction du contexte spécifique de la machine cible.
§ Les valeurs de l'axe y croissent vers le bas (traditionnel en infographie).
§ La taille des composants est définie par leur largeur (width) et par
leur hauteur (height).
§ C'est donc par le choix du layout-pane et en fonction des contraintes
données que sont déterminées les règles de disposition.
§ Plusieurs layout-panes sont prédéfinis dans JavaFX. Il est également
possible de définir ses propres conteneurs avec des règles de
disposition spécifiques, mais c'est rarement nécessaire.
– Jacques BAPST
5
x
xc
[0, 0]
yc
Component
Height
§ Avec JavaFX, les layout managers sont intégrés aux conteneurs
(layout-panes) et ne sont pas manipulés directement par les
programmeurs (seulement au travers des propriétés des conteneurs).
IHM-1 – FX04
§ Les valeurs de l'axe x croissent vers la droite.
Width
y
IHM-1 – FX04
– Jacques BAPST
Taille des composants
Système de coordonnées [2]
§ Les composants (nodes) que l'on peut placer dans des conteneurs
possèdent des propriétés qui peuvent être prises en compte lors du
calcul de leur disposition.
§ Coordonnées d'origine de différents éléments d'une application
JavaFX.
• minWidth
• prefWidth
• maxWidth
• minHeight
• prefHeight
• maxHeight
:
:
:
:
:
:
Largeur minimale souhaitée pour le composant
Largeur préférée (idéale) du composant
Largeur maximale souhaitée pour le composant
Hauteur minimale souhaitée pour le composant
Hauteur préférée (idéale) du composant
Hauteur maximale souhaitée pour le composant
7
Screen
Window / Stage
Scene / Root
Node / Component
§ L'effet de ces propriétés dépend naturellement du type de
conteneur (layout-pane) utilisé et de ses règles spécifiques de
positionnement et de dimensionnement.
IHM-1 – FX04
– Jacques BAPST
6
IHM-1 – FX04
– Jacques BAPST
8
Système de coordonnées [3]
Fenêtre principale [2]
§ En réalité, toute la librairie graphique est basée sur un système de
coordonnées en trois dimensions (3D).
§ Autres méthodes de l'objet Stage :
§ La troisième dimension est représentée
par l'axe z dont les valeurs augmentent
lorsqu'on s'éloigne de l'observateur.
§ Pour la gestion des interfaces "classiques"
(de type WIMP), on ne s'occupe généralement pas de l'axe z et on
travaille uniquement dans le plan x/y.
: Place la fenêtre en mode plein-écran ou en mode
standard (si paramètre false) (selon OS)
• getIcons().add()
: Définit l'icône dans la barre de titre
• setAlwaysOnTop()
: Place la fenêtre toujours au dessus des autres
(généralement à éviter)
: Définit la scène (sa racine) qui est associée à la fenêtre
• setScene()
• showAndWait()
• Le système n'est donc pas nécessairement lié aux pixels de l'écran
• Des transformations sont ainsi facilement possibles (translation,
homothétie, rotation, …) en utilisant l'algèbre linéaire
• Lors du rendu un alignement sur les pixels peut être automatiquement
effectué (voir méthode snapToPixel())
– Jacques BAPST
: Définit le titre de la fenêtre (affiché selon OS)
• setFullScreen()
• show()
§ Dans les API, pratiquement toutes les coordonnées sont données
avec des nombres à virgule (généralement des double).
IHM-1 – FX04
• setTitle()
9
• . . .
IHM-1 – FX04
: Affiche la fenêtre à l'écran (et la scène qu'elle contient)
: Affiche la fenêtre à l'écran et attend que la fenêtre soit
fermée pour retourner (méthode bloquante).
Cette méthode n'est pas applicable à la fenêtre
principale (primary stage).
Et beaucoup d'autres (consulter la documentation)
– Jacques BAPST
11
Fenêtre principale [1]
Fenêtre principale [3]
§ Par défaut, au lancement d'une application, la fenêtre principale
(primary stage) est centrée sur l'écran.
§ Pour déterminer la taille de l'écran, on peut utiliser la classe Screen
et rechercher le rectangle qui englobe la zone utilisable de l'écran
(ou l'intégralité de la surface de l'écran).
§ Différentes méthodes peuvent être invoquées sur l'objet Stage pour
influencer la position et la taille de cette fenêtre :
• setX()
: Position en x du coin supérieur gauche
• setY()
: Position en y du coin supérieur gauche
• centerOnScreen()
:
:
:
:
:
:
• setMinWidth()
• setMinHeight()
• setMaxWidth()
• setMaxHeight()
• setResizable()
• sizeToScene()
IHM-1 – FX04
§ La méthode getBounds() retourne par contre un rectangle qui
représente la surface totale de l'écran.
Centrage sur l'écran (par défaut)
Fixe la largeur minimale de la fenêtre
Fixe la hauteur minimale de la fenêtre
Fixe la largeur maximale de la fenêtre
Fixe la hauteur maximale de la fenêtre
Détermine si la fenêtre est redimensionnable
Screen screen = Screen.getPrimary();
Rectangle2D bounds = screen.getVisualBounds();
: Adapte la taille de la fenêtre à la taille de la scène
liée à cette fenêtre (utile si l'on change dynamiquement
le contenu du graphe de scène)
– Jacques BAPST
§ La méthode getVisualBounds() prend en compte l'espace occupé
par certains éléments du système d'exploitation (barre de tâche,
menu, etc.).
10
double screenWidth = bounds.getWidth();
double screenHeight = bounds.getHeight();
double screenDpi
IHM-1 – FX04
= screen.getDpi();
// Dot per inch
– Jacques BAPST
12
Fenêtre principale [4]
Bordures [2]
§ La fenêtre principale, appelée primary stage est celle qui est passée
en paramètre (par le système) à la méthode start().
§ Exemple 1 : bordure rectangulaire
§ Dans une application JavaFX, il est possible de créer d'autres
fenêtres indépendantes (d'autres objets Stage) et de les gérer.
Button btnA = new Button("A Border Test");
HBox
root = new HBox();
root.getChildren().add(btnA);
private void createStage() {
Stage secondStage = new Stage();
secondStage.setTitle("Second Stage");
root2 =
. . .
primaryStage.setTitle("Test Border 1");
root.setPadding(new Insets(30, 50, 30, 50));
root.setAlignment(Pos.CENTER);
// Création de la scène de la deuxième fenêtre
Border border1 = new Border(
new BorderStroke(Color.GREEN,
BorderStrokeStyle.SOLID,
CornerRadii.EMPTY,
new BorderWidths(6),
new Insets(0)
));
root.setBorder(border1);
secondStage.setScene(new Scene(root2));
secondStage.show();
}
@Override
public void start(Stage primaryStage) {
. . .
primaryStage.setScene(new Scene(root));
primaryStage.show();
createStage();
. . .
}
IHM-1 – FX04
– Jacques BAPST
13
IHM-1 – FX04
– Jacques BAPST
Bordures [1]
Bordures [3]
§ Des bordures peuvent être appliquées à toutes les sous-classes de
Region, notamment autour des conteneurs (layout-panes) et autour
des composants (moins fréquent).
§ Exemple 2 : bordure pointillée, aux coins arrondis
primaryStage.setTitle("Test Border 2");
Button btnA = new Button("A Border Test");
HBox
root = new HBox();
root.getChildren().add(btnA);
§ C'est la propriété border (héritée de Region) qui est utilisée pour
définir les caractéristiques de la bordure.
§ Les classes Border et BorderStroke permettent de créer des
bordures et de les assigner à la propriété border. Border est
immuable et permet ainsi de créer des objets réutilisables (on peut
appliquer une même bordure à plusieurs conteneurs ou composants).
root.setPadding(new Insets(30));
root.setAlignment(Pos.CENTER);
§ Pour les bordures, JavaFX suit le modèle de conception proposé
pour les feuilles de style CSS 3. Cette spécification est relativement
complexe et permet la gestion de bordures assez sophistiquées.
Border border2 = new Border(
new BorderStroke(Color.ORANGE,
BorderStrokeStyle.DOTTED,
new CornerRadii(30),
new BorderWidths(10),
new Insets(20)
));
root.setBorder(border2);
§ Les exemples ci-après illustrent une utilisation simple des bordures
pour entourer un conteneur (de type HBox).
primaryStage.setScene(new Scene(root));
primaryStage.show();
IHM-1 – FX04
– Jacques BAPST
15
14
IHM-1 – FX04
– Jacques BAPST
16
Arrière-plans [1]
Arrière-plans [3]
§ Des couleurs et/ou des images d'arrière-plan peuvent être
appliquées à toutes les sous-classes de Region, et donc aussi bien
aux conteneurs (layout-panes) qu'aux composants (controls).
§ Exemple avec la répétition d'une image d'arrière-plan :
Button btnA = new Button("A Background Test");
Image img = new Image("/resources/warn_1.png");
HBox
root = new HBox();
root.getChildren().add(btnA);
§ C'est la propriété background (héritée de Region) qui est utilisée
pour définir les caractéristiques de l'arrière-plan.
§ Les classes Background, BackgroundFill et BackgroundImage
permettent de créer des arrière-plans qui peuvent être composés
d'une superposition de remplissage (fill) et/ou d'images. Ces classes
sont immuables et permettent ainsi de créer des objets réutilisables
(on peut appliquer un même arrière-plan à plusieurs conteneurs ou
composants).
§ Les exemples qui suivent illustrent l'application d'arrière-plans à des
conteneurs (de type HBox).
root.setPadding(new Insets(30, 50, 30, 50));
root.setAlignment(Pos.CENTER);
Background bgImg = new Background(
new BackgroundImage(
img,
BackgroundRepeat.ROUND,
BackgroundRepeat.ROUND,
BackgroundPosition.CENTER,
BackgroundSize.DEFAULT
));
root.setBackground(bgImg);
IHM-1 – FX04
– Jacques BAPST
17
IHM-1 – FX04
– Jacques BAPST
19
Arrière-plans [2]
§ Exemple avec deux remplissages (fill) d'arrière-plan superposés :
Button btnA = new Button("A Background Test");
HBox
root = new HBox();
root.getChildren().add(btnA);
Conteneurs
Layout-Panes de base
root.setPadding(new Insets(30, 50, 30, 50));
root.setAlignment(Pos.CENTER);
(disponibles par défaut)
Background bgGreen = new Background(
new BackgroundFill(Color.LIGHTGREEN,
CornerRadii.EMPTY,
null
),
new BackgroundFill(Color.GREEN,
new CornerRadii(20),
new Insets(15)
));
root.setBackground(bgGreen);
IHM-1 – FX04
– Jacques BAPST
18
IHM-1 – FX04
– Jacques BAPST
20
Layout-Panes
HBox [2]
§ Dans la création des graphes de scène, les conteneurs (appelés
layout-panes ou parfois simplement layouts ou panes) jouent donc
un rôle important dans la structuration et la disposition des
composants qui seront placés dans les interfaces.
§ L'ajout des composants enfants dans le conteneur s'effectue en
invoquant d'abord la méthode générale getChildren() qui
retourne la liste des enfants du conteneur et en y ajoutant ensuite
le composant considéré (méthodes add() ou addAll()) :
§ En fonction du design adopté (phase de conception de l'interface),
il est important de réfléchir au choix des conteneurs qui permettront
au mieux de réaliser la mise en page souhaitée.
§ Les pages qui suivent décrivent sommairement les layouts-panes qui
sont prédéfinis dans JavaFX.
§ Seules les caractéristiques principales sont mentionnées dans ce
support de cours. Ce n'est en aucun cas un manuel de référence.
§ Il faut donc impérativement consulter la documentation disponible
(Javadoc, tutorials, …) pour avoir une description détaillée de l'API
(constantes, propriétés, constructeurs, méthodes, …).
IHM-1 – FX04
– Jacques BAPST
21
• HBox root = new HBox();
• root.getChildren().add(btnA);
§ Des méthodes statiques de HBox peuvent être invoquées pour
appliquer des contraintes de positionnement :
• hgrow() : permet d'agrandir le composant passé en paramètre jusqu'à
sa taille maximale selon la priorité (Priority) donnée
• margin() : fixe une marge (objet de type Insets) autour du composant
passé en paramètre (zéro par défaut Insets.EMPTY)
IHM-1 – FX04
– Jacques BAPST
HBox [1]
HBox [3]
§ Le layout HBox place les composants sur une ligne horizontale. Les
composants sont ajoutés à la suite les uns des autres (de gauche à
droite).
§ Exemple (déclaration des composants et code de la méthode start())
private
private
private
private
HBox
Button
Label
ComboBox<String>
23
root;
btnA = new Button("Alpha");
lblB = new Label("Bravo");
cbbC = new ComboBox<>();
primaryStage.setTitle("Test HBox");
§ L'alignement des composants enfants est déterminé par la propriété
alignment, par défaut TOP_LEFT (type énuméré Pos).
§ L'espacement horizontal entre les composants est défini par la
propriété spacing (0 par défaut). La valeur de cette propriété peut
être passée en paramètre au constructeur (new HBox(8)).
§ Si possible, le conteneur respecte la taille préférée des composants.
Si le conteneur est trop petit pour afficher tous les composants à
leur taille préférée, il les réduit jusqu'à minWidth.
IHM-1 – FX04
– Jacques BAPST
22
root = new HBox(10);
root.setAlignment(Pos.CENTER);
root.getChildren().add(btnA);
root.getChildren().add(lblB);
// Horizontal Spacing : 10
cbbC.getItems().addAll("Charlie", "Delta");
cbbC.getSelectionModel().select(0);
root.getChildren().add(cbbC);
primaryStage.setScene(new Scene(root, 300, 100));
primaryStage.show();
IHM-1 – FX04
– Jacques BAPST
24
HBox [4]
VBox [1]
§ La propriété padding permet de définir l'espace (marge) entre le
bord du conteneur et les composants enfants.
§ Le layout VBox place les composants verticalement, sur une colonne.
Les composants sont ainsi ajoutés à la suite les uns des autres (de
haut en bas).
• Un paramètre de type Insets est passé en paramètre, il définit les
espacements dans l'ordre suivant : Top, Right, Bottom, Left ou une
valeur identique pour les quatre côtés si l'on passe un seul paramètre
root.setPadding(new Insets(10, 5, 10, 5));
root.setPadding(new Insets(10));
§ Une couleur de fond peut être appliquée au conteneur en
définissant la propriété style qui permet de passer en chaîne de
caractères, un style CSS.
§ Toutes les propriétés et méthodes décrites pour le conteneur HBox
s'appliquent également au conteneur VBox avec seulement quelques
adaptations assez évidentes, par exemple la propriété hgrow devient
vgrow. La différence essentielle est donc simplement que le sens de
l'empilement des composants enfants est vertical et non horizontal.
• On définit la propriété CSS -fx-background-color.
root.setStyle("-fx-background-color: #FFFD33");
§ Ce conteneur ne sera donc pas décrit plus en détail ici.
On peut aussi utiliser
setBorder() discuté
au début du chapitre
IHM-1 – FX04
– Jacques BAPST
25
IHM-1 – FX04
– Jacques BAPST
HBox [5]
VBox [2]
§ Exemple (conteneur avec padding et couleur de fond)
§ Exemple (avec agrandissement vertical d'un composant)
private
private
private
private
primaryStage.setTitle("Test Padding+Background");
root = new HBox(10);
//--- Top, Right, Bottom, Left Spacing
root.setPadding(new Insets(20, 10, 20, 10));
root.setStyle("-fx-background-color: #FFFE99");
primaryStage.setTitle("Test VBox");
root = new VBox(10);
// Vertical Spacing : 10
root.setAlignment(Pos.TOP_CENTER);
root.getChildren().add(btnA);
root.getChildren().add(lblB);
cbbC.getItems().addAll("Charlie", "Delta");
cbbC.getSelectionModel().select(0);
root.getChildren().add(cbbC);
cbbC.getItems().addAll("Charlie", "Delta");
cbbC.getSelectionModel().select(0);
root.getChildren().add(cbbC);
primaryStage.setScene(new Scene(root));
primaryStage.show();
– Jacques BAPST
root;
btnA = new Button("Alpha");
lblB = new Label("Bravo");
cbbC = new ComboBox<>();
VBox.setVgrow(btnA, Priority.ALWAYS);
btnA.setMaxHeight(Double.MAX_VALUE);
root.getChildren().add(btnA);
root.getChildren().add(lblB);
IHM-1 – FX04
VBox
Button
Label
ComboBox<String>
27
primaryStage.setScene(new Scene(root, 180, 150));
primaryStage.show();
26
IHM-1 – FX04
– Jacques BAPST
28
FlowPane [1]
FlowPane [3]
§ Le layout FlowPane place les composants sur une ligne horizontale
ou verticale et passe à la ligne ou à la colonne suivante (wrapping)
lorsqu'il n'y a plus assez de place disponible.
§ Exemple (déclaration du conteneur et des composants)
§ Un des paramètres du constructeur (de type Orientation) détermine
s'il s'agit d'un FlowPane horizontal (par défaut) ou vertical.
private FlowPane
root;
private
private
private
private
private
private
private
btnA
lblB
btnC
lblD
btnE
lblF
btnG
Button
Label
Button
Label
Button
Label
Button
=
=
=
=
=
=
=
new
new
new
new
new
new
new
Button("__Alpha__");
Label("__Bravo__");
Button("__Charlie__");
Label("__Delta__");
Button("__Echo__");
Label("__Foxtrot__");
Button("__Golf__");
. . .
FlowPane horizontal
IHM-1 – FX04
FlowPane vertical
– Jacques BAPST
29
IHM-1 – FX04
– Jacques BAPST
FlowPane [2]
FlowPane [4]
§ L'ajout des composants enfants dans un conteneur FlowPane
s'effectue en invoquant getChildren().add(node) ou addAll(n, …)
§ Exemple (code de la méthode start())
§ Quelques propriétés importantes du conteneur FlowPane :
: Espacement horizontal entre les composants ou colonnes
(peut aussi être passé au constructeur)
• vgap
: Espacement vertical entre les composants ou lignes
(peut aussi être passé au constructeur)
• padding
: Espacement autour du conteneur (marge)
• alignment
: Alignement global des composants dans le conteneur
• rowValignment
: Alignement vertical dans les lignes (si horizontal-pane)
• columnHalignment : Alignement horizontal dans les colonnes (si vertical-pane)
• prefWrapLength
: Détermine la largeur préférée (si horizontal-pane) ou
la hauteur préférée (si vertical-pane)
• orientation
: Orientation du FlowPane (peut aussi être passé au
constructeur)
• hgap
IHM-1 – FX04
– Jacques BAPST
30
31
primaryStage.setTitle("FlowPane (horizontal)");
root = new FlowPane();
root.getChildren().add(btnA);
root.getChildren().add(lblB);
root.getChildren().add(btnC);
root.getChildren().add(lblD);
root.getChildren().add(btnE);
root.getChildren().add(lblF);
root.getChildren().add(btnG);
// Ajout
// des
// composants
root.setPadding(new Insets(5));
root.setHgap(10);
root.setVgap(15);
// Marge extérieure
// Espacement horiz. entre composants
// Espacement vertical entre lignes
root.setPrefWrapLength(250);
// Largeur préférée du conteneur
root.setRowValignment(VPos.BOTTOM); // Alignement vertical dans lignes
primaryStage.setScene(new Scene(root));
primaryStage.show();
IHM-1 – FX04
– Jacques BAPST
32
TilePane [1]
TilePane [3]
§ Le layout TilePane place les composants dans une grille alimentée
soit horizontalement (par ligne, de gauche à droite) soit verticalement
(par colonne, de haut en bas).
§ Suite des propriétés :
§ Un des paramètres du constructeur (de type Orientation) détermine
s'il s'agit d'un TilePane horizontal (par défaut) ou vertical.
§ On définit pour la grille un certain nombre de colonnes (propriété
prefColumns) si l'orientation est horizontale ou un certain nombre
de lignes (propriété prefRows) si l'orientation est verticale.
• prefTileWidth
: Détermine la largeur préférée des tuiles (cellules).
Change la valeur calculée par défaut (USE_COMPUTED_SIZE).
• prefTileHeight
: Détermine la hauteur préférée des tuiles (cellules).
Change la valeur calculée par défaut (USE_COMPUTED_SIZE).
Remarque :
§ Toutes les cellules de cette grille (les tuiles) ont la même taille qui
correspond à la plus grande largeur préférée et à la plus grande
hauteur préférée parmi les composants placés dans ce conteneur.
Les propriétés prefColumns et prefRows ne reflètent pas
nécessairement le nombre de colonnes ou le nombre de lignes
actuels de la grille.
Ces propriétés servent uniquement à calculer la taille préférée
du conteneur. Si la taille du conteneur change, le nombre de
lignes et de colonnes sera adapté à l'espace à disposition
(wrapping automatique des tuiles).
§ Le conteneur TilePane est très proche du conteneur FlowPane. La
différence principale réside dans le fait que toutes les cellules ont
obligatoirement la même taille (ce qui n'est pas le cas pour FlowPane).
IHM-1 – FX04
– Jacques BAPST
33
IHM-1 – FX04
– Jacques BAPST
TilePane [2]
TilePane [4]
§ Quelques propriétés importantes du conteneur TilePane :
§ L'ajout des composants enfants dans un conteneur TilePane
s'effectue en invoquant l'une des deux méthodes :
: Espacement horizontal entre les composants ou colonnes
(peut aussi être passé au constructeur).
• vgap
: Espacement vertical entre les composants ou lignes
(peut aussi être passé au constructeur).
• padding
: Espacement autour du conteneur (marge).
• alignment
: Alignement global de la grille dans le conteneur
(par défaut TOP_LEFT).
• tileAlignment : Alignement des composants à l'intérieur de chaque tuile
(par défaut CENTER). Peut être redéfini individuellement
(par la propriété alignment de chaque composant).
• prefColumns
: Nombre préféré de colonnes (par défaut 5) si horizontal.
Détermine la largeur préférée du conteneur.
• prefRows
: Nombre préféré de lignes (par défaut 5) si vertical.
Détermine la hauteur préférée du conteneur.
• hgap
IHM-1 – FX04
– Jacques BAPST
35
• getChildren().add(node)
• getChildren().addAll(node1, node2, node3, …)
§ Exemple avec des libellés (Label) et des boutons (Button)
• La police de caractères du bouton "__Echo__" a été agrandie et
détermine la taille de toutes les tuiles (cellules)
34
IHM-1 – FX04
– Jacques BAPST
36
BorderPane [1]
BorderPane [3]
§ Le conteneur BorderPane permet de placer les composants enfants
dans cinq zones : Top, Bottom, Left, Right et Center.
§ Certaines zones peuvent être laissées libres (sans composant). Elles
n'occupent alors aucune place.
§ Un seul objet Node (composant, conteneur, …) peut être placé dans
chacun de ces emplacements.
§ Si les composants n'occupent pas tout l'espace disponible dans leur
zone, leur point d'ancrage par défaut (alignement) respectera la
disposition suivante :
IHM-1 – FX04
37
– Jacques BAPST
BorderPane [2]
IHM-1 – FX04
– Jacques BAPST
39
BorderPane [4]
§ Les composants placés dans les zones Top et Bottom :
setTop()
setBottom()
• Gardent leur hauteur préférée
• Sont éventuellement agrandis horizontalement jusqu'à leur largeur
maximale ou réduit à leur taille minimale en fonction de la largeur du
conteneur.
§ Les composants placés dans les zones Left et Right :
setLeft()
setRight()
• Gardent leur largeur préférée
• Sont éventuellement agrandis verticalement jusqu'à leur hauteur
maximale ou réduit à leur taille minimale en fonction de la hauteur
restante entre les (éventuelles) zones Top et Bottom du conteneur.
§ Le composant placé dans la zone Center :
§ Des méthodes statiques de BorderPane peuvent être invoquées
pour appliquer des contraintes de positionnement :
• alignment() : permet de modifier l'alignement par défaut du
composant passé en paramètre (point d'ancrage)
• margin()
: fixe une marge (objet de type Insets) autour du
composant passé en paramètre (par défaut Insets.EMPTY)
BorderPane.setAlignment(btnTop,
Pos.CENTER);
BorderPane.setAlignment(btnRight, Pos.BOTTOM_CENTER);
BorderPane.setMargin(btnBottom, new Insets(10));
setCenter()
• Est éventuellement agrandi (jusqu'à sa taille maximale) ou réduit (à sa
taille minimale) dans les deux directions (largeur et hauteur) pour
occuper l'espace qui reste au centre du conteneur.
Remarque : Par défaut, la taille maximale des
boutons (Button) est égale à leur taille
préférée. Pour qu'ils s'agrandissent, il
faut modifier la propriété maxWidth :
btnT.setMaxWidth(Double.MAX_VALUE);
IHM-1 – FX04
– Jacques BAPST
38
IHM-1 – FX04
– Jacques BAPST
40
BorderPane [5]
AnchorPane [1]
§ Le conteneur BorderPane est fréquemment utilisé comme
conteneur racine du graphe de scène car il correspond à une division
assez classique de la fenêtre principale d'une application (barre de
titre, barre d'état, zone d'options, zone principale, etc.).
§ Le conteneur AnchorPane permet de positionner (ancrer) les
composants enfants à une certaine distance des côtés du conteneur
(Top, Bottom, Left et Right).
§ Pour placer plusieurs composants dans les zones du BorderPane, il
faut y ajouter des nœuds de type conteneur et ajouter ensuite les
composants dans ces conteneurs imbriqués.
§ Plusieurs composants peuvent être ancrés à un même côté.
§ Il est donc très fréquent d'imbriquer plusieurs conteneurs pour
obtenir la disposition désirée des composants de l'interface.
– Jacques BAPST
41
BorderPane et HBox
private
private
private
private
private
private
BorderPane
HBox
Label
Button
Button
Button
root
btnPanel
lblTitle
btnSave
btnQuit
btnCancel
=
=
=
=
=
=
new
new
new
new
new
new
§ Ce conteneur ressemble par certains aspects à BorderPane mais il y
a quelques différences essentielles :
• Le conteneur AnchorPane n'est pas divisé en zones, les composants sont
simplement liés (ancrés) par rapport aux bords du conteneur.
• Un composant peut être ancré à plusieurs bords et même à des bords
opposés (left et right par exemple). Dans ce cas, le composant pourra
être étiré ou comprimé pour respecter les contraintes d'ancrage.
• Plusieurs composants peuvent être ancrés à un même bord. Il pourront
dans ce cas, être partiellement ou totalement superposés.
§ Le graphe de scène représente donc un
arbre d'imbrication dont la hauteur
(nombre de niveaux) dépend du nombre
de composants et de la complexité de
la structure de l'interface graphique.
IHM-1 – FX04
§ Un composant peut être ancré, simultanément, à plusieurs côtés.
IHM-1 – FX04
– Jacques BAPST
43
AnchorPane [2]
§ Par défaut (sans contrainte), les composants sont ancrés en haut et
à gauche (Top - Left).
BorderPane();
HBox(10);
Label("App Title");
Button("Save");
Button("Quit");
Button("Cancel");
§ Exemple de positionnement avec les contraintes représentées par
des flèches :
lblTitle.setFont(Font.font("SansSerif", FontWeight.BOLD, 24));
lblTitle.setTextFill(Color.BLUE);
root.setTop(lblTitle);
//--- Build Buttons Panel
btnPanel.getChildren().add(btnSave);
btnPanel.getChildren().add(btnQuit);
btnPanel.getChildren().add(btnCancel);
btnPanel.setAlignment(Pos.CENTER);
root.setBottom(btnPanel);
BorderPane.setMargin(lblTitle, new Insets(10, 0, 10, 0));
BorderPane.setMargin(btnPanel, new Insets(10, 0, 10, 0));
BorderPane.setAlignment(lblTitle, Pos.CENTER);
primaryStage.setScene(new Scene(root));
IHM-1 – FX04
– Jacques BAPST
42
IHM-1 – FX04
– Jacques BAPST
44
AnchorPane [3]
AnchorPane [5]
§ L'ajout des composants enfants dans un conteneur AnchorPane
s'effectue en invoquant l'une des deux méthodes :
• getChildren().add(node)
• getChildren().addAll(node1, node2, node3, …)
§ Pour définir les contraintes d'ancrage, il faut invoquer des méthodes
statiques de AnchorPane :
• topAnchor()
: définit la distance (double) par rapport au haut
• bottomAnchor() : définit la distance (double) par rapport au bas
: définit la distance (double) par rapport au côté gauche
§ Exemple de contraintes d'ancrage :
AnchorPane.setTopAnchor
(btnOk,
10.0);
AnchorPane.setRightAnchor (btnOk,
0.0);
AnchorPane.setBottomAnchor(btnQuit, 15.0);
IHM-1 – FX04
– Jacques BAPST
= new AnchorPane();
private
private
private
private
=
=
=
=
Button
Button
Button
Button
btnA
btnB
btnC
btnD
new
new
new
new
Button("Alpha");
Button("Bravo");
Button("Charlie");
Button("Delta");
primaryStage.setTitle("Test AnchorPane");
root.getChildren().addAll(btnA, btnB, btnC, btnD);
//--- Contraintes
AnchorPane.setRightAnchor (btnB,
AnchorPane.setTopAnchor
(btnC,
AnchorPane.setLeftAnchor (btnC,
AnchorPane.setBottomAnchor(btnD,
AnchorPane.setLeftAnchor (btnD,
AnchorPane.setRightAnchor (btnD,
• rightAnchor() : définit la distance (double) par rapport au côté droit
• leftAnchor()
private AnchorPane root
20.0);
80.0);
60.0);
20.0);
40.0);
40.0);
primaryStage.setScene(new Scene(root, 250, 250));
primaryStage.show();
45
IHM-1 – FX04
– Jacques BAPST
47
AnchorPane [4]
StackPane [1]
§ Les distances définies dans les contraintes d'ancrage sont données
par rapport aux côtés du conteneur. Si le conteneur possède une
bordure (setBorder()) et/ou un padding (setPadding()), les
dimensions de ces éléments seront pris en compte et s'ajouteront
à la distance donnée en contrainte.
§ Le conteneur StackPane empile les composants enfants les uns au
dessus des autres dans l'ordre d'insertion : les premiers "au fond",
les derniers "au-dessus" (back-to-front).
§ Si nécessaire, les composants enfants sont redimensionnés pour
s'adapter à la taille du conteneur (la valeur de la propriété maxSize est
respectée, mais pas celle de minSize !).
§ Le layout AnchorPane respecte, si possible, la taille préférée des
composants enfants.
§ Cependant, selon les contraintes données, et si les composants
enfants sont redimensionnables (isResizable()), leur taille devra
être adaptée pour respecter les contraintes données. Dans ce cas, le
layout AnchorPane ne tiendra pas compte des tailles minimales et
maximales des composants lors de leur redimensionnement.
§ Si des composants se superposent, c'est l'ordre des ajouts dans le
conteneur qui déterminera leur visibilité (le dernier ajouté sera audessus).
IHM-1 – FX04
– Jacques BAPST
46
§ Si les composants enfants n'occupent pas toute la place disponible,
ils seront alignés selon la valeur de la propriété alignment du
conteneur (CENTER par défaut).
• Cet alignement par défaut peut être modifié par les contraintes
d'alignement spécifiques des composants enfants (voir page suivante)
§ L'ajout des composants enfants dans un conteneur de type
StackPane s'effectue en invoquant l'une des deux méthodes :
• getChildren().add(node)
• getChildren().addAll(node1, node2, node3, …)
IHM-1 – FX04
– Jacques BAPST
48
StackPane [2]
GridPane [1]
§ Des méthodes statiques de StackPane peuvent être invoquées pour
appliquer des contraintes de positionnement aux composants :
§ Le conteneur GridPane permet de disposer les composants enfants
dans une grille flexible (arrangement en lignes et en colonnes), un peu à
la manière d'une table HTML.
• alignment() : permet de modifier l'alignement par défaut du
composant passé en paramètre
• margin()
: fixe une marge (objet de type Insets) autour du
composant passé en paramètre (par défaut Insets.EMPTY)
StackPane.setAlignment(icon, Pos.CENTER_LEFT);
StackPane.setAlignment(label, Pos.CENTER_RIGHT);
§ Le conteneur StackPane peut être utile pour créer un composant
complexe à partir d'éléments existants en les superposant (placer du
texte au dessus d'une forme ou d'une image, combiner des éléments
graphiques, etc.).
– Jacques BAPST
§ La zone occupée par un composant peut s'étendre (span) sur
plusieurs lignes et/ou sur plusieurs colonnes.
§ Le nombre de lignes et de colonnes de la grille est déterminé
automatiquement par les endroits où sont placés les composants.
StackPane.setMargin(label, new Insets(5));
IHM-1 – FX04
§ La grille peut être irrégulière, la hauteur des lignes et la largeur des
colonnes de la grille ne sont pas nécessairement uniformes.
49
§ Par défaut, la hauteur de chaque ligne est déterminée par la hauteur
préférée du composant le plus haut qui s'y trouve.
§ Par défaut, la largeur de chaque colonne est déterminée par la
largeur préférée du composant le plus large qui s'y trouve.
IHM-1 – FX04
StackPane [3]
GridPane [2]
§ Exemple (état initial et après redimensionnement de la fenêtre) :
§ Exemple simple avec illustration
du découpage (structure de la
grille).
private StackPane
root
= new StackPane();
private Label
private Button
private Rectangle
lblA
btnS
rect
= new Label("This is a label");
= new Button("This is a button");
= new Rectangle(80, 80, Color.YELLOW);
Column 0
primaryStage.setTitle("Test StackPane");
Padding
51
– Jacques BAPST
Column 1
HGap
StackPane.setAlignment(lblA, Pos.CENTER_LEFT);
StackPane.setAlignment(btnS, Pos.CENTER_RIGHT);
StackPane.setMargin(btnS, new Insets(5));
Spanning (2 columns)
Alignment (center)
root.getChildren().addAll(rect, lblA, btnS);
Margin (bottom)
primaryStage.setScene(new Scene(root, 200, 100));
primaryStage.show();
Row 0
Row 1
VGap
Row 2
Padding
Alignment
(right)
IHM-1 – FX04
– Jacques BAPST
50
IHM-1 – FX04
– Jacques BAPST
52
GridPane [3]
GridPane [5]
§ Pour placer les composants dans un conteneur GridPane , on utilise
la méthode add() qui permet de passer en paramètre le composant
ainsi que les contraintes principales de placement :
§ Il est possible de placer plusieurs composants dans une même
cellule de la grille.
• Indice de la colonne et de la ligne (numérotation commence à zéro)
• Nombre de colonnes et de lignes de spanning (par défaut : 1)
• Par exemple, pour l'interface de la page précédente :
root.add(lblTitle,
0, 0, 2, 1);
root.add(lblUsername, 0, 1);
ð
• Position : Modifier les propriétés columnIndex et rowIndex
• Spanning : Modifier les propriétés columnSpan et rowSpan
// Title (2 cols spanning)
root.add(tfdUsername, 1, 1);
// Username text-field
// Password label
// Password text-field
§ La taille des lignes et des colonnes de la grille peut être gérée
• Par des contraintes de lignes et de colonnes qui s'appliquent pour tous
les composants placés dans la ligne ou la colonne concernée
• Par des contraintes individuelles, appliquées aux composants placés
dans la grille
• Les contraintes individuelles sont prioritaires sur celles appliquées
globalement aux lignes et aux colonnes.
§ Les composants peuvent être ajoutés dans n'importe quel ordre.
§ On peut ajouter dans un GridPane n'importe quel objet de type
Node, donc également des conteneurs (ð imbrication).
IHM-1 – FX04
– Jacques BAPST
Les composants seront empilés, le dernier ajouté sera "au-dessus"
§ Les contraintes de placement des composants peuvent être
modifiées dynamiquement (durant le cours du programme) :
// Username label
root.add(lblPassword, 0, 2);
root.add(pwfPassowrd, 1, 2);
• Ils s'afficheront comme dans un conteneur StackPane
53
IHM-1 – FX04
– Jacques BAPST
GridPane [4]
GridPane [6]
§ Quelques propriétés importantes du conteneur GridPane :
§ Les contraintes globales de lignes/colonnes sont définies dans des
objets de type :
• hgap
: Espacement horizontal entre les colonnes
• vgap
: Espacement vertical entre les lignes
• alignment
: Alignement de la grille dans le conteneur
(si elle n'occupe pas tout l'espace)
• padding
: Espacement autour de la grille (marge)
55
: Pour les lignes
• ColumnConstraints : Pour les colonnes
• RowConstraints
§ Les contraintes globales sont ensuite associées aux lignes/colonnes
du GridPane en les ajoutant dans une liste, avec les méthodes :
• gridLinesVisible : Affichage des lignes de construction de la grille.
• getRowConstraints.add(row_constraint)
• getColumnConstraints.add(column_constraint)
Très utile pour la mise au point (debugging) de
l'interface.
• L'ordre des ajouts correspond à l'ordre des lignes/colonnes
§ Exemple :
ColumnConstraints ctCol0 = new ColumnConstraints(50, 100, 200,
Priority.ALWAYS,
HPos.CENTER,
true
);
root.getColumnConstraints().add(ctCol0);
IHM-1 – FX04
– Jacques BAPST
54
IHM-1 – FX04
– Jacques BAPST
56
GridPane [7]
GridPane [9]
§ Les contraintes de lignes RowConstraints possèdent les propriétés
suivantes :
§ On peut également appliquer des contraintes individuelles aux
composants placés dans un GridPane. Ces contraintes sont
prioritaires sur les contraintes de lignes et colonnes.
:
• prefHeight
:
• maxHeight
:
• percentHeight :
Hauteur minimale souhaitée pour la ligne
Hauteur préférée (idéale) pour la ligne
Hauteur maximale souhaitée pour la ligne
Hauteur de la ligne en pourcent de la hauteur de la
grille (est prioritaire sur min-, pref- et maxHeight)
: Alignement par défaut des composants dans la ligne
(de type énuméré VPos : TOP, CENTER, BOTTOM, BASELINE)
: Priorité d'agrandissement vertical
(de type énuméré Priority : ALWAYS, SOMETIMES, NEVER)
• minHeight
• valignment
• vgrow
: Booléen indiquant si le composant doit s'agrandir
(true) jusqu'à sa hauteur maximale ou alors garder sa
hauteur préférée (false). Par défaut : true.
• fillHeight
IHM-1 – FX04
– Jacques BAPST
57
GridPane [8]
:
• prefWidth
:
• maxWidth
:
• percentWidth :
• halignment
• hgrow
• fillWidth
IHM-1 – FX04
: Alignement horizontal du composant passé en
paramètre (HPos : LEFT, CENTER, RIGHT)
• valignement : Alignement vertical du composant passé en
paramètre (VPos : TOP, CENTER, BOTTOM, BASELINE)
• hgrow
: Priorité d'agrandissement horizontal
(Priority : ALWAYS, SOMETIMES, NEVER)
• vgrow
: Priorité d'agrandissement vertical
(Priority : ALWAYS, SOMETIMES, NEVER)
• margin
: Marge autour du composant (de type Insets)
• halignment
IHM-1 – FX04
– Jacques BAPST
59
GridPane et HBox [1]
§ Les contraintes de colonnes ColumnConstraints possèdent les
propriétés suivantes :
• minWidth
§ Les contraintes sont appliquées en invoquant des méthodes
statiques de GridPane qui permettent de gérer les propriétés
suivantes :
Largeur minimale souhaitée de la colonne
Largeur préférée (idéale) de la colonne
Largeur maximale souhaitée de la colonne
Largeur de la colonne en pourcent de la largeur de la
grille (est prioritaire sur min-, pref- et maxWidth)
: Alignement par défaut des composants dans la colonne
(de type énuméré HPos : LEFT, CENTER, RIGHT)
: Priorité d'agrandissement horizontal
(de type énuméré Priority : ALWAYS, SOMETIMES, NEVER)
: Booléen indiquant si le composant doit s'agrandir (true)
jusqu'à sa largeur maximale ou alors garder sa largeur
préférée (false). Par défaut : true.
– Jacques BAPST
58
private GridPane
private HBox
root
btnPanel
= new GridPane();
= new HBox(12);
private
private
private
private
private
private
private
lblTitle
lblUsername
tfdUsername
lblPassword
btnLogin
btnCancel
pwfPassword
=
=
=
=
=
=
=
Label
Label
TextField
Label
Button
Button
PasswordField
new
new
new
new
new
new
new
Label("JafaFX Course Login");
Label("Username or email:");
TextField();
Label("Password:");
Button("Login");
Button("Cancel");
PasswordField();
primaryStage.setTitle("Login Panel");
//--- Title
lblTitle.setFont(Font.font("System", FontWeight.BOLD, 20));
lblTitle.setTextFill(Color.rgb(80, 80, 180));
root.add(lblTitle,
0, 0, 2, 1);
GridPane.setHalignment(lblTitle, HPos.CENTER);
GridPane.setMargin(lblTitle, new Insets(0, 0, 10,0));
IHM-1 – FX04
– Jacques BAPST
60
GridPane et HBox [2]
Résumé des conteneurs [1]
//--- Username (label and text-field)
tfdUsername.setPrefColumnCount(20);
root.add(lblUsername, 0, 1);
root.add(tfdUsername, 1, 1);
GridPane.setHalignment(lblUsername, HPos.RIGHT);
//--- Password (label and text-field)
pwfPassword.setPrefColumnCount(12);
root.add(lblPassword, 0, 2);
root.add(pwfPassword, 1, 2);
GridPane.setHalignment(lblPassword, HPos.RIGHT);
GridPane.setFillWidth(pwfPassword, false);
//--- Button panel
btnPanel.getChildren().add(btnLogin);
btnPanel.getChildren().add(btnCancel);
btnPanel.setAlignment(Pos.CENTER_RIGHT);
root.add(btnPanel, 1, 3);
GridPane.setMargin(btnPanel, new Insets(10, 0, 0,0));
Classe
Description
HBox, VBox
Place les composants horizontalement (sur une ligne) ou
verticalement (dans une colonne).
FlowPane
(horizontal)
Place les composants horizontalement sur une ligne et passe à la
ligne suivante s'il n'y a plus assez de place dans le conteneur (linewrapping).
FlowPane
(vertical)
Place les composants verticalement (de haut en bas), en colonne et
passe à la colonne suivante s'il n'y a plus assez de place dans le
conteneur (column-wrapping).
TilePane
(horizontal)
Place les composants dans une grille dont les cellules (les tuiles) ont
toutes la même taille. Les composants sont ajoutés horizontalement,
ligne par ligne.
TilePane
(horizontal)
Place les composants dans une grille dont les cellules (les tuiles) ont
toutes la même taille. Les composants sont ajoutés verticalement,
colonne par colonne.
setGridLinesVisible(true)
IHM-1 – FX04
– Jacques BAPST
61
GridPane et HBox [3]
IHM-1 – FX04
//--- GridPane properties
root.setAlignment(Pos.CENTER);
root.setPadding(new Insets(20));
root.setHgap(10);
root.setVgap(15);
Classe
Description
BorderPane
Dispose de cinq emplacements pour placer les composants : Top,
Bottom, Left, Right, Center.
AnchorPane
Place les composants en respectant une contrainte de distance par
rapport à un ou plusieurs bords du conteneur.
StackPane
Place les composants les uns au dessus des autres (empilement).
Le dernier ajouté est placé au-dessus des autres.
GridPane
Place les composants dans une grille potentiellement irrégulière (par
défaut, la taille des lignes et des colonnes est déterminée par le plus
grand composant qui y est placé.
Les composants sont ajoutés en donnant l'indice de la colonne et de
la ligne (la numérotation commence à zéro).
La zone d'un composant n'est pas limitée à une seule cellule, elle
peut s'étendre sur plusieurs colonnes et plusieurs lignes (spanning).
Un composant peut s'agrandir pour occuper toute sa zone.
primaryStage.setMinWidth(300);
primaryStage.setMinHeight(200);
root.setGridLinesVisible(true);
// Uncomment to display grid lines
primaryStage.setScene(new Scene(root));
primaryStage.show();
IHM-1 – FX04
– Jacques BAPST
63
Résumé des conteneurs [2]
//--- Column global constraints
ColumnConstraints ctCol0 = new ColumnConstraints(); // No constraint
ColumnConstraints ctCol1 = new ColumnConstraints(50, 200, 400,
Priority.ALWAYS,
HPos.LEFT,
true);
root.getColumnConstraints().add(ctCol0);
root.getColumnConstraints().add(ctCol1);
//
– Jacques BAPST
62
IHM-1 – FX04
– Jacques BAPST
64
Suppression de composants
§ Pour supprimer un ou plusieurs composants d'un conteneur, il faut
invoquer la méthode remove(node) ou removeAll(node1, node2, …)
sur la liste des composants du conteneur (retournée par la méthode
getChildren()).
§ Si l'on souhaite remplacer un composant par un autre, il faut
supprimer l'ancien composant et ajouter ensuite le nouveau.
§ Après une modification dynamique du graphe de scène (ajout ou
suppression de composants), il est souvent utile d'invoquer la méthode
sizeToScene() pour forcer le redimensionnement de la fenêtre
principale (stage) en prenant en compte les changements effectués.
. . .
root.getChildren().remove(btnSave);
root.getChildren().add(cbxMail);
// Replace button 'Save'
// by checkbox 'Mail'
primaryStage.sizeToScene();
// Resize window (pack)
. . .
IHM-1 – FX04
– Jacques BAPST
65
Composants – Controls [2]
§ Les composants ont tous pour classe parente Control qui est une
sous-classe de Node.
Cours IHM-1
JavaFX
§ Une version simplifiée des dépendances entre les différentes classes
est illustrée par le diagramme suivant :
5 - Composants de base
Création et utilisation
Jacques BAPST
[email protected]
...
IHM-1 – FX05
– Jacques BAPST
3
Composants – Controls [1]
Composants – Controls [3]
§ La librairie JavaFX offre un ensemble de composants (kit de
développement) pour créer les interfaces utilisateurs graphiques.
§ Certains composants comme ScrollPane ou SplitPane jouent, en
partie, un rôle de conteneur mais, formellement, ils ne font pas
partie de cette famille (ils héritent de Control et non de Pane).
§ Ces composants d'interface sont fréquemment nommés controls
dans la documentation en anglais (parfois widgets).
§ Dans ce cours, nous utiliserons généralement le terme composant
pour parler des éléments qui servent à afficher des informations ou
permettre à l'utilisateur d'interagir avec l'application.
• Libellés, icônes, boutons, champs-texte, menus, cases à cocher, etc.
– Jacques BAPST
§ Dans ce chapitre, nous présenterons quelques composant simples et
décrirons la manière de les créer et de les utiliser.
§ Une fois que l'on a compris le principe de fonctionnement, il est plus
facile de consulter la documentation officielle et de découvrir les
propriétés et les comportements des composants offerts par la
librairie JavaFX (les mêmes principes de base sont appliqués partout).
§ Bien qu'ils constituent les deux des nœuds (node) du graphe de
scène, les composants sont à distinguer des conteneurs (layoutpanes) qui servent à disposer les composants et qui ne sont pas
directement visibles dans l'interface (les bordures et les couleurs
d'arrière-plan permettent cependant de révéler leur présence).
IHM-1 – FX05
§ On fait parfois la distinction entre composants simples (labels,
champs texte, boutons, …) et composants complexes (tables, arbres,
media-player, navigateur, …).
§ Le cours ne décrira donc pas en détail l'ensemble des composants.
Le support de cours ne constitue pas un manuel de référence et il
faut, en complément, consulter la documentation disponible.
2
IHM-1 – FX05
– Jacques BAPST
4
Composants avec libellés [1]
Composants avec libellés [3]
§ De nombreux composants affichent et gèrent des textes (libellés,
boutons, cases à cocher, etc.).
graphic
§ Les comportements communs de ces composants sont gérés par la
classe parente Labeled.
contentDisplay
graphicTextGap
Certains composants qui héritent
aussi de Labeled ne sont pas
mentionnés dans ce diagramme.
Par exemple Cell ou TitledPane.
mnemonicParsing
textOverrun
labelPadding *
ellipsisString
*
IHM-1 – FX05
– Jacques BAPST
5
Autre composant (type Node) qui accompagne le texte.
Généralement un graphique, une image ou une icône.
Position du composant additionnel (graphic) par rapport au texte.
Type énuméré ContentDisplay (LEFT, RIGHT, TOP, BOTTOM,
TEXT_ONLY, GRAPHIC_ONLY).
Espacement entre le texte et le composant additionnel (graphic).
Type Double.
Active le parsing des mnémoniques dans le texte (le caractère qui
suit le caractère '_'). Type Boolean.
Comportement si le texte est trop long pour être affiché.
Type énuméré OverrunStyle (ELLISPSIS, CLIP, …).
Définit l'espace autour du texte (et du graphique éventuel).
Type Insets.
Chaîne de caractères utilisée lorsque le texte est tronqué
(ellipsis). Par défaut : "…"
property : Cette couleur est utilisée pour les propriétés en lecture seule (read-only)
IHM-1 – FX05
– Jacques BAPST
7
Composants avec libellés [2]
Label [1]
§ Les textes de ces composants peuvent être accompagnés d'un autre
composant, généralement un graphique, une image ou une icône.
§ Le composant Label représente un libellé (= un texte non éditable).
§ Quelques propriétés communes aux composants Labeled :
text
font
textFill
underline
alignment
wrapText
textAlignment
lineSpacing
IHM-1 – FX05
Texte affiché (String).
Police de caractères (famille, style, taille, …), type Font.
Couleur du texte, uniforme ou avec gradient (type Paint).
Indique si le texte doit être souligné (type Boolean).
Alignement général du texte (et du graphique éventuel) dans la
zone (type Pos). Valable seulement si texte sur une seule ligne.
Booléen qui définit si le texte passe à la ligne suivante lorsqu'il
atteint la limite de la zone. Le caractère ' \n' peut également être
inséré pour forcer un retour à la ligne (inconditionnel).
Alignement des lignes si le texte est multiligne.
Type énuméré TextAlignment (LEFT, RIGHT, CENTER, JUSTIFY).
Espacement des lignes pour les textes multilignes. Type Double.
– Jacques BAPST
6
§ Les constructeurs permettent de définir le contenu du texte et de
l'éventuel composant additionnel (graphic).
• new Label("Hello");
• new Label("Warning", warningIcon);
§ L'essentiel des fonctionnalités sont héritées de Labeled. Une seule
propriété additionnelle se trouve dans Label :
• setLabelFor : Permet de définir un (autre) composant (Node) auquel
le libellé est associé (utile pour définir un mnémonique).
Remarque : Les objets de type Text possèdent certaines similitudes avec les
composants de type Label mais Text n'est pas une sous-classe
de Control. Cette classe fait partie de la famille des graphiques
(sous-classe de Shape) et possède donc des propriétés et des
fonctionnalités un peu différentes.
IHM-1 – FX05
– Jacques BAPST
8
Label [2]
Button [2]
§ Quelques propriétés du composant Button :
private HBox
root = new HBox(20);
private Label
private Label
private Label
lblA = new Label("Hello");
lblB = new Label("Big and colored");
lblC = new Label("A Multiline\nText\nLabel");
armed
primaryStage.setTitle("Label Test");
onAction
root.setAlignment(Pos.CENTER);
root.setPadding(new Insets(10));
root.getChildren().add(lblA);
lblB.setFont(Font.font("SansSerif", FontWeight.BOLD, 20));
lblB.setTextFill(Color.rgb(180, 50, 50));
lblB.setGraphic(new Rectangle(50, 5, Color.ORANGE));
lblB.setContentDisplay(ContentDisplay.BOTTOM);
root.getChildren().add(lblB);
root.getChildren().add(lblC);
cancelButton
defaultButton
primaryStage.setScene(new Scene(root));
primaryStage.show();
IHM-1 – FX05
– Jacques BAPST
9
Booléen qui indique si le bouton est "armé" et prêt à déclencher
une action (par exemple souris placée sur le bouton et touche
gauche pressée).
Détermine l'événement à générer lorsque l'action du bouton est
déclenchée (par ex. lorsque la touche de la souris a été relâchée).
Type EventHandler<ActionEvent>.
Booléen qui indique si le bouton est un bouton Cancel c'est-àdire que l'action du bouton doit être déclenchée lorsque
l'utilisateur presse sur la touche Escape (VK_ESC).
Cette propriété ne doit être appliquée qu'à un seul bouton de
l'interface.
Booléen qui indique si le bouton est un bouton par défaut c'està-dire que l'action du bouton doit être déclenchée lorsque
l'utilisateur presse sur la touche Enter (VK_ENTER).
Cette propriété ne doit être appliquée qu'à un seul bouton de
l'interface.
IHM-1 – FX05
– Jacques BAPST
Button [1]
Button [3]
§ Le composant Button représente un bouton permettant à
l'utilisateur de déclencher une action.
§ La manière de traiter l'événement généré par le bouton sera
expliquée dans un prochain chapitre consacré à la gestion des
événements et à l'écriture de contrôleurs qui se chargeront
d'exécuter du code associé aux différents éléments actifs de
l'interface.
§ La classe parente ButtonBase rassemble les propriétés communes à
différents composants qui se comportent comme des boutons :
Button, CheckBox, Hyperlink, MenuButton, ToggleButton.
§ Les constructeurs permettent de définir le contenu du texte et de
l'éventuel composant additionnel (graphic).
• new Button("Ok");
• new Button("Save", saveIcon);
§ Par héritage, toutes les propriétés qui ont été mentionnées pour les
composants avec libellés (sous-classes de Labeled) sont naturellement
applicables pour le composant Button.
11
§ Pour ajouter une image à un bouton (où à n'importe quel autre
composant de type Labeled), on peut utiliser la classe ImageView
(év. associée à la classe Image) qui permet de représenter une image
stockée dans une ressource locale (fichier de type gif, jpeg, png, …)
ou en donnant l'URL d'une image sur un serveur web (par exemple :
http://www.myblog.ch/images/logo.png).
§ La classe ImageView permet également de redimensionner les
images et d'en afficher qu'une partie (viewport).
§ Dans l'exemple qui suit, un des boutons (btnLogin) est affiché en
associant une image au texte du libellé.
IHM-1 – FX05
– Jacques BAPST
10
IHM-1 – FX05
– Jacques BAPST
12
Button [4]
Saisie de textes [2]
§ Quelques propriétés de la classe TextInputControl :
private static final String FLOGO = "/resources/EIA_FR.jpg";
private
private
private
private
private
VBox
Button
Button
Button
Button
root
btnOk
btnLogin
btnSave
btnMulti
=
=
=
=
=
new
new
new
new
new
VBox(10);
Button("OK");
Button("Login");
Button("Save");
Button("A Multiline\nRight-Justified\nText");
root.setAlignment(Pos.CENTER);
root.setPadding(new Insets(20));
text
editable
font
length
caretPosition
root.getChildren().add(btnOk);
btnLogin.setTextFill(Color.BLUE);
btnLogin.setFont(Font.font(null, FontWeight.BOLD, 14));
btnLogin.setGraphic(new ImageView(FLOGO));
btnLogin.setContentDisplay(ContentDisplay.TOP);
root.getChildren().add(btnLogin);
promptText
btnSave.setDefaultButton(true);
root.getChildren().add(btnSave);
selectedText
btnMulti.setTextAlignment(TextAlignment.RIGHT);
root.getChildren().add(btnMulti);
anchor
IHM-1 – FX05
textFormatter
selection
– Jacques BAPST
13
Le texte contenu dans le composant (String).
Le texte peut être édité par l'utilisateur (Boolean).
Police de caractères du texte (Font).
Longueur du texte (Integer).
Position courante du curseur / point d'insertion (caret).
Texte affiché si aucun texte n'a été défini ou saisi par l'utilisateur
(String). Ce texte n'est pas affiché lorsque le composant
possède le focus (avec le curseur qui clignote dans le champ).
Ce texte peut éventuellement remplacer un libellé ou une bulle
d'aide pour le champ texte.
Formateur de texte associé au composant (TextFormatter<?>).
Texte sélectionné (String).
Indices (de...à) de la zone sélectionnée (IndexRange).
Point d'ancrage (début) de la sélection (Integer).
IHM-1 – FX05
– Jacques BAPST
Saisie de textes [1]
Saisie de textes [3]
§ La classe abstraite TextInputControl est la classe parente de
différents composants qui permettent à l'utilisateur de saisir des
textes. Il s'agit notamment des composants d'interface TextField,
PasswordField et TextArea
§ Quelques méthodes de la classe TextInputControl :
§ La classe TextInputControl définit les propriétés de base et les
fonctionnalités communes aux composants qui offrent une saisie
de texte et notamment :
•
•
•
•
– Jacques BAPST
Efface le texte (vide le champ).
copy/cut/paste() Transfert du texte dans ou depuis le clipboard.
positionCaret()
Positionne le curseur à une position donnée.
forward()
backward()
nextWord()
Déplace d'un caractère le curseur (caret).
Déplace le curseur (caret) au début du prochain mot.
insertText()
Insère une chaîne de caractères dans le texte.
appendText()
Ajoute une chaîne de caractères à la fin du texte.
deleteText()
Supprime une partie du texte (de...à).
deleteNextChar() Efface le prochain caractère.
replaceText()
Remplace une partie du texte par un autre.
selectAll()
Sélectionne l'ensemble du texte.
deselect()
Annule la sélection courante du texte.
La sélection de texte
L'édition de texte
La gestion du curseur à l'intérieur du texte (caret)
Le formatage du texte
IHM-1 – FX05
clear()
15
14
IHM-1 – FX05
– Jacques BAPST
16
TextField [1]
TextFormatter [1]
§ Le composant TextField représente un champ texte d'une seule
ligne qui est éditable par défaut mais qui peut également être utilisé
pour afficher du texte.
§ On peut associer un formateur de texte à tous les composants qui
héritent de TextInputControl (propriété TextFormatter).
§ Le composant n'intègre pas de libellé. Il faut utiliser un composant
de type Label si l'on veut lui associer un libellé.
§ En plus des propriétés héritées (notamment de TextInputControl), le
composant TextField possède les propriétés suivantes :
Alignement du texte dans le champ (Pos).
Nombre de colonnes du champ texte; permet de déterminer la
prefColumnCount largeur préférée du composant. La valeur par défaut est définie
par la constante DEFAULT_PREF_COLUMN_COUNT (12).
Détermine l'événement à générer lorsque l'action du champ
onAction
texte est déclenchée, en général lorsque l'utilisateur presse sur
la touche Enter (EventHandler<ActionEvent>).
alignment
§ Ce formateur est un composant de type TextFormatter<V> qui
permet de définir :
• Un convertisseur permettant de convertir le texte du composant en une
valeur d'un autre type (par exemple un type numérique, int, double, …).
• Un filtre permettant d'intercepter et de modifier les caractères saisis par
l'utilisateur pendant l'édition du texte (n'accepter que les chiffres par ex.).
§ Le formateur peut définir un filtre ou un convertisseur ou les deux.
§ Le filtre et le convertisseur sont transmis dans le constructeur du
formateur qui possède les surcharges suivantes :
TextFormatter(StringConverter<V> valueConverter)
TextFormatter(StringConverter<V> valueConverter, V defaultValue)
TextFormatter(UnaryOperator<TextFormatter.Change> filter)
TextFormatter(StringConverter<V> valueConverter, V defaultValue,
UnaryOperator<TextFormatter.Change> filter)
IHM-1 – FX05
– Jacques BAPST
17
TextField [2]
private
private
private
private
private
HBox
Label
Label
TextField
TextField
root
lblName
lblMobile
tfdName
tfdMobile
– Jacques BAPST
19
TextFormatter [2]
=
=
=
=
=
new
new
new
new
new
§ Le paramètre générique V du formateur (TextFormatter<V>) définit le
type de la propriété value. On ne peut utiliser cette propriété que si
l'on a défini un convertisseur dans le formateur.
HBox(5);
Label("Name");
Label("Mobile");
TextField();
TextField();
§ Le convertisseur est un objet de type StringConverter<V> qui doit
implémenter les méthodes de conversions entre les propriétés
text (String) et value (V) :
primaryStage.setTitle("TextField Test");
root.setAlignment(Pos.CENTER);
root.getChildren().add(lblName);
tfdName.setPrefColumnCount(12);
tfdName.setPromptText("First and Last-Name");
root.getChildren().add(tfdName);
• fromString() : text ® value
• toString()
: value ® text
§ Il existe des implémentations prédéfinies de convertisseurs pour
certains types courants :
root.getChildren().add(lblMobile);
HBox.setMargin(lblMobile, new Insets(0,0,0,10));
tfdMobile.setPrefColumnCount(8);
tfdMobile.setPromptText("Mobile Tel Nr");
root.getChildren().add(tfdMobile);
• BooleanStringConverter, DoubleStringConverter, IntegerStringConverter,
NumberStringConverter, DateTimeStringConverter, . . .
§ Si l'on souhaite gérer le format d'affichage et/ou traiter certaines
erreurs, il est préférable de redéfinir les méthodes de conversion.
root.setPadding(new Insets(10));
primaryStage.setScene(new Scene(root));
primaryStage.show();
IHM-1 – FX05
IHM-1 – FX05
– Jacques BAPST
18
IHM-1 – FX05
– Jacques BAPST
20
TextFormatter [3]
TextFormatter [5]
§ Exemple de convertisseur associé à un champ texte.
§ Le filtre que l'on peut greffer à un formateur est un objet de type
UnaryOperator<TextFormatter.Change> :
//------------------------ TextField Formatter --------------------------------TextFormatter<Double> tFmt = new TextFormatter<Double>(
new StringConverter<Double>() {
@Override //--- Convert text (String) to Double (NaN if not possible)
public Double fromString(String text) {
try {
return Double.parseDouble(text);
}
catch (NumberFormatException e) {
return Double.NaN;
}
}
• Change
• L'ajout de texte
(isAdded())
• Le remplacement de texte (isReplaced())
• La suppression de texte
(isDeleted())
§ Les opérations disponibles sont des opérations de bas niveau qui
permettent d'intervenir lors de la frappe des caractères dans le
champ mais qui nécessitent plus de travail pour créer des filtres plus
complexes (adresse e-mail ou numéro de téléphone valide, etc.) .
}
);
tfdPrice.setTextFormatter(tFmt);
21
– Jacques BAPST
: classe interne de TextFormatter représentant
l'état des changements effectués
§ La classe contient de nombreuses méthodes permettant de réagir
aux changements effectués dans le texte lors de
@Override //--- Convert Double to String and format (2 decimals)
public String toString(Double value) {
return String.format("%.2f", value);
}
IHM-1 – FX05
• UnaryOperator<T> : interface fonctionnelle avec la méthode abstraite
Change apply(Change c)
IHM-1 – FX05
– Jacques BAPST
TextFormatter [4]
TextFormatter [6]
§ Résumé du fonctionnement du convertisseur.
§ Exemple de filtre qui n'accepte que des chiffres ou '.' ou '-'.
• Exemple avec un champ texte (TextField) prévu pour gérer une valeur
numérique (double).
TextFormatter<Double>
"56"
Propriété text
• Expression régulière (regex) utilisée pour rejeter les autres caractères
//---------- TextField formatter accepting [0..9] or dot or dash --------------TextFormatter<String> tFmtNb = new TextFormatter<String>(change -> {
change.setText(change.getText().replaceAll("[^0-9.-]", ""));
return change;
});
tfdTemperature.setTextFormatter(tFmtNb);
Ÿ Converter
(String « Double)
Ÿ Filter
56.0
Propriété value
String
Double
TextField tfd = new …
Double v = fmt.getValue();
String txt = tfd.getText();
fmt.setvalue(newVal);
TextFormatter<Double> fmt = new …
tfd.setTextFormatter(fmt);
IHM-1 – FX05
23
– Jacques BAPST
§ Filtre qui n'accepte que 4 caractères (sans espaces)
TextFormatter<String> tFmt4 = new TextFormatter<String>(change -> {
String content = change.getControlNewText(); // New text if changes accepted
if (content.length()>4 || change.getText().equals(' ')) {
change.setText("");
// Rejected --> no change
}
return change;
});
tfdCode.setTextFormatter(tFmt4);
22
IHM-1 – FX05
– Jacques BAPST
24
Label - TextField - Button [1]
Label - TextField - Button [3]
§ L'exemple qui suit illustre une utilisation des composants Label,
TextField et Button qui sont assemblés pour créer un panneau
de login simple.
. . .
//--- Print username/password on button click
btnLogin.setOnAction(event ->
System.out.println("Login: " + tfdUser.getText()
+ " / " + tfdPass.getText()));
§ La propriété disable (héritée de Node) permet d'activer ou de
désactiver un composant de l'interface. Un composant désactivé
reste visible mais n'est plus actif (il est généralement 'grisé').
§ Une liaison (binding) est effectuée entre la propriété text (contenu
du champ texte) et la propriété disable en utilisant des opérations
intermédiaires (isEmpty() et or()). Voir code surligné.
primaryStage.setScene(new Scene(root));
primaryStage.setTitle("Login Panel");
primaryStage.show();
}
§ De cette manière, le champ Password est désactivé tant que le
champ Username est vide. D'autre part, le bouton n'est activé que
si les deux champs texte sont remplis.
§ Une manière simple et élégante de gérer l'interaction et d'éviter des
traitements d'erreurs.
IHM-1 – FX05
– Jacques BAPST
25
Label - TextField - Button [2]
private
private
private
private
private
private
GridPane
Label
Label
TextField
PasswordField
Button
root
lblUser
lblPass
tfdUser
tfdPass
btnLogin
=
=
=
=
=
=
new
new
new
new
new
new
Disabled
IHM-1 – FX05
– Jacques BAPST
27
PasswordField
§ Le composant PasswordField est une sous-classe de TextField et
permet de saisir un texte sans que celui-ci soit affiché dans le champ
texte (il est remplacé par des caractères de substitution).
GridPane();
Label("Username:");
Label("Password:");
TextField();
PasswordField();
Button("Login");
§ C'est un composant qui sert principalement à saisir des mots-depasse ou d'autres informations à caractère confidentiel.
public void start(Stage primaryStage) {
tfdPass.disableProperty().bind(tfdUser.textProperty().isEmpty());
btnLogin.disableProperty().bind(tfdUser.textProperty().isEmpty()
.or(tfdPass.textProperty().isEmpty()));
root.setHgap(6);
root.setVgap(12);
root.setPadding(new Insets(15));
root.add(lblUser, 0, 0);
root.add(tfdUser, 1, 0);
root.add(lblPass, 0, 1);
root.add(tfdPass, 1, 1);
root.add(btnLogin, 0, 2, 2, 1);
GridPane.setHalignment(btnLogin, HPos.CENTER);
GridPane.setMargin(btnLogin, new Insets(10, 0, 0, 0));
Disabled
§ Le composant PasswordField ne comporte ni propriété, ni
méthode supplémentaires par rapport au composant TextField.
Il s'utilise donc strictement de la même manière.
§ Les méthodes cut() et copy() sont sans effet pour ce composant
(ainsi, l'utilisateur ne pourra pas récupérer le mot de passe par copier /
coller).
. . .
IHM-1 – FX05
– Jacques BAPST
26
IHM-1 – FX05
– Jacques BAPST
28
TextArea [1]
TextArea [3]
§ Le composant TextArea permet d'afficher et de saisir du texte dans
un champ multiligne (une zone de texte).
§ Le texte peut être renvoyé à la ligne automatiquement (wrapping) et
des barres de défilement (scrollbar) horizontales et/ou verticales
sont ajoutées automatiquement si la taille du composant ne permet
pas d'afficher l'entier du texte (lignes ou colonnes tronquées).
§ Tous les caractères du texte possèdent les mêmes attributs (police,
style, taille, couleur, …).
§ Si le texte doit pouvoir être formaté de manière plus riche, il faut
utiliser le composant HTMLEditor qui permet d'éditer le texte en
appliquant certains formatages sur des parties sélectives du texte.
private VBox
private TextArea
private Button
root = new VBox(10);
txaA = new TextArea();
btnB = new Button("Print");
primaryStage.setTitle("TextArea Test");
root.setAlignment(Pos.CENTER);
root.setPadding(new Insets(10));
txaA.setWrapText(true);
txaA.setPrefColumnCount(14);
txaA.setPrefRowCount(8);
root.getChildren().add(txaA);
root.getChildren().add(btnB);
btnB.setOnAction(event -> {
System.out.println(txaA.getText());
});
primaryStage.setScene(new Scene(root));
primaryStage.show();
IHM-1 – FX05
– Jacques BAPST
29
IHM-1 – FX05
– Jacques BAPST
TextArea [2]
ToggleButton [1]
§ Quelques propriétés de la classe TextArea :
§ Le composant ToggleButton représente un bouton bistable. Il
comporte donc deux états : un clic le met à l'état sélectionné (on),
un nouveau clic le remet à l'état désélectionné (off).
Nombre de colonnes de la zone de texte; permet de déterminer
prefColumnCount la largeur préférée du composant. La valeur par défaut est
définie par la constante DEFAULT_PREF_COLUMN_COUNT (40).
prefRowCount
wrapText
scrollLeft
scrollTop
IHM-1 – FX05
Nombre de lignes de la zone de texte; permet de déterminer la
hauteur préférée du composant. La valeur par défaut est définie
par la constante DEFAULT_PREF_ROW_COUNT (10).
Booléen qui définit si le texte passe à la ligne suivante lorsqu'il
atteint la limite de la zone. Le caractère '\n' peut également être
inséré pour forcer un retour à la ligne (inconditionnel).
Par défaut : false
Détermine de combien de pixels le texte est scrollé
horizontalement (Double).
Détermine de combien de pixels le texte est scrollé
verticalement (Double).
– Jacques BAPST
30
31
§ Visuellement, il se présente généralement de la même manière
qu'un composant Button et, comme lui, hérite de ButtonBase.
§ On peut former des groupes de ToggleButton en associant les
boutons concernés à un ToggleGroup.
• ToggleGroup groupA = new ToggleGroup();
• tButton.setToggleGroup(groupA);
§ Dans un tel groupe, un seul bouton peut être sélectionné. Si on en
sélectionne un, celui qui était préalablement sélectionné sera
automatiquement désélectionné.
§ Dans un groupe, il est possible qu'aucun bouton ne soit sélectionné
(contrairement au composant RadioButton).
IHM-1 – FX05
– Jacques BAPST
32
ToggleButton [2]
RadioButton [1]
§ Par défaut, les boutons ToggleButton ne sont pas enregistrés dans
un groupe (plusieurs boutons peuvent donc être simultanément à
l'état sélectionné).
§ Le composant RadioButton est une sous-classe de ToggleButton
et représente une option que l'utilisateur peut sélectionner
(généralement parmi un groupe d'options).
§ Les constructeurs de la classe permettent de définir le contenu du
texte et d'un éventuel composant additionnel (graphic), comme pour
le bouton ordinaire.
§ L'utilisation du composant RadioButton doit être réservée à la
situation où l'utilisateur doit choisir une seule option parmi
plusieurs (même si le composant n'impose pas cette sémantique).
• new ToggleButton("Sound");
• new ToggleButton("Sound", speakerImage);
§ On placera donc systématiquement tous les RadioButton dans des
groupes de type ToggleGroup afin d'obtenir ce comportement
(sélection mutuellement exclusive).
§ Quelques propriétés de la classe ToggleButton :
selected
toggleGroup
Indique si le bouton est sélectionné ou non (Boolean).
Enregistre le groupe auquel appartient le bouton (ToggleGroup).
IHM-1 – FX05
– Jacques BAPST
33
ToggleButton [3]
private
private
private
private
HBox
ToggleButton
ToggleButton
ToggleButton
root
tbnGif
tbnJpg
tbnPng
=
=
=
=
new
new
new
new
§ Par défaut aucun bouton radio n'est sélectionné au départ mais dès
que l'un d'eux est sélectionné on ne peut plus revenir dans cet état
initial "sans sélection" (contrairement au composant ToggleButton).
IHM-1 – FX05
– Jacques BAPST
35
RadioButton [2]
§ Les constructeurs permettent de définir le contenu du texte et d'un
éventuel composant additionnel (graphic), comme pour le bouton
ordinaire.
HBox(10);
ToggleButton("GIF");
ToggleButton("JPEG");
ToggleButton("PNG");
• new RadioButton("Down");
• new RadioButton("Up", upArrowImage);
primaryStage.setTitle("ToggleButton Test");
root.setAlignment(Pos.CENTER);
root.setPadding(new Insets(20));
§ Le composant RadioButton ne possède pas de propriétés
spécifiques (elles sont héritées de ToggleButton).
ToggleGroup tg = new ToggleGroup();
tbnGif.setToggleGroup(tg);
tbnJpg.setToggleGroup(tg);
tbnPng.setToggleGroup(tg);
§ Pour rappel, deux propriétés importantes :
selected
root.getChildren().add(tbnGif);
root.getChildren().add(tbnJpg);
root.getChildren().add(tbnPng);
primaryStage.setScene(new Scene(root));
primaryStage.show();
IHM-1 – FX05
• ToggleGroup grpLanguage = new ToggleGroup();
• rbnEnlish.setToggleGroup(grpLanguage);
Avec le style Modena, la différence
visuelle entre un bouton sélectionné
et un non-sélectionné est faible.
– Jacques BAPST
34
toggleGroup
IHM-1 – FX05
Indique si le bouton radio est sélectionné ou non (Boolean).
Enregistre le groupe auquel appartient le bouton radio
(ToggleGroup).
– Jacques BAPST
36
RadioButton [3]
private
private
private
private
VBox
RadioButton
RadioButton
RadioButton
root
rbnGif
rbnJpg
rbnPng
=
=
=
=
new
new
new
new
CheckBox [2]
§ La classe dispose des constructeurs suivants :
VBox(10);
RadioButton("Red");
RadioButton("Green");
RadioButton("Blue");
• new CheckBox();
• new CheckBox("Mute");
primaryStage.setTitle("RadioButton Test");
§ Si l'on veut ajouter un graphique, une icône ou autre, il faut utiliser
la méthode setGraphic() (héritée de Labeled).
root.setPadding(new Insets(10, 75, 10, 75));
§ Quelques propriétés de la classe CheckBox :
ToggleGroup tg = new ToggleGroup();
rbnGif.setToggleGroup(tg);
rbnJpg.setToggleGroup(tg);
rbnPng.setToggleGroup(tg);
Indique si la case à cocher est sélectionnée (pour autant
que getIndeterminate==false) (Boolean).
Indique si l'état de la case à cocher est indéterminé
indeterminate
(Boolean).
Autorise l'état indéterminé (Boolean).
Si true : Trois états possibles
allowIndeterminate
(désélectionné, sélectionné et indéterminé).
Si false : Seulement deux états possibles
(désélectionné et sélectionné). Valeur par défaut.
selected
root.getChildren().add(rbnGif);
root.getChildren().add(rbnJpg);
root.getChildren().add(rbnPng);
primaryStage.setScene(new Scene(root));
primaryStage.show();
IHM-1 – FX05
– Jacques BAPST
37
CheckBox [1]
IHM-1 – FX05
– Jacques BAPST
39
CheckBox [3]
§ Le composant CheckBox représente une case à cocher que
l'utilisateur peut sélectionner ou désélectionner (en cliquant dessus
par exemple).
§ Ce composant est typiquement utilisé lorsque l'utilisateur peut
choisir parmi plusieurs options qui peuvent être simultanément
activées.
private
private
private
private
private
private
VBox
TitledPane
CheckBox
CheckBox
CheckBox
CheckBox
vbx
root
chbHci
chbAlgo
chbMl
chbCp
=
=
=
=
=
=
new
new
new
new
new
new
VBox(10);
TitledPane("Elective Courses", vbx);
CheckBox("Human-Computer Interaction");
CheckBox("Algorithms and Data-Structure");
CheckBox("Machine Learning");
CheckBox("Concurrent Programming");
primaryStage.setTitle("CheckBox Test");
§ La case à cocher peut potentiellement se trouver dans trois états
différents. Dans ce cas, chaque clic fera passer le composant à l'état
suivant :
• Désélectionné
• Sélectionné
• Indéterminé / non-défini
root.setCollapsible(false);
vbx.setPadding(new Insets(15));
TitledPane :
un panneau
avec un titre
chbHci.setAllowIndeterminate(true);
chbAlgo.setAllowIndeterminate(true);
chbMl.setAllowIndeterminate(true);
chbCp.setAllowIndeterminate(true);
vbx.getChildren().add(chbHci);
vbx.getChildren().add(chbAlgo);
vbx.getChildren().add(chbMl);
vbx.getChildren().add(chbCp);
primaryStage.setScene(new Scene(root));
primaryStage.show();
IHM-1 – FX05
– Jacques BAPST
38
IHM-1 – FX05
– Jacques BAPST
40
Hyperlink [1]
Hyperlink [3]
§ Le composant Hyperlink se présente comme un lien hypertexte
HTML dans une page web. Il agit comme un bouton lorsqu'on clique
sur le texte associé et peut déclencher n'importe quelle action
(comme un bouton).
§ Dans l'exemple ci-après, les textes des liens (captions), les URL ainsi
que les composants Hyperlink sont enregistrés dans des tableaux
parallèles (liés par l'indice).
§ Ce composant est une sous-classe de ButtonBase et de Labeled. Il
peut donc contenir un autre composant (graphic), typiquement une
image ou un graphique.
§ Les constructeurs permettent de définir le contenu du texte et d'un
éventuel composant additionnel (graphic).
• new Hyperlink("www.myblog.ch");
• new Hyperlink("Print", printerIcon);
private String[] tCaption = {"FX-Experience",
"JFxtras",
"GUI Garage"};
private String[] tUrl
= {"http://fxexperience.com",
"http://jfxtras.org",
"http://www.guigarage.com"};
private VBox
root
= new VBox();
private HBox
lnkPanel = new HBox(20);
private Hyperlink[] tLinks
= new Hyperlink[tUrl.length];
§ Le composant Hyperlink ne comporte qu'une propriété spécifique :
visited
Indique si le lien a déjà été visité, c'est-à-dire cliqué (Boolean).
IHM-1 – FX05
– Jacques BAPST
41
Hyperlink [2]
IHM-1 – FX05
Hyperlink [4]
§ Les trois états "non-visité", "durant le clic" et "visité" sont
généralement représentés de manière distincte (couleur, souligné, …)
en fonction du style de l'interface.
§ Si l'on souhaite que le lien hypertexte affiche le contenu d'une page
web, on peut utiliser le composant WebView qui contient un moteur
de rendu de pages web de type WebEngine (basé sur le projet opensource WebKit).
primaryStage.setTitle("Hyperlink Test");
lnkPanel.setAlignment(Pos.CENTER);
lnkPanel.setPadding(new Insets(10));
WebView
browser = new WebView();
WebEngine wEngine = browser.getEngine();
lnkPanel.getChildren().add(tLinks[i]);
String url = tUrl[i];
tLinks[i].setOnAction(event -> {
wEngine.load(url);
});
de paramétrages possibles. Leur fonctionnement n'est pas décrit en
détail ici.
§ Si l'on se contente d'une utilisation basique, le code à produire n'est
pas spécialement complexe.
§ L'exemple qui suit illustre l'utilisation de liens hypertextes pour
afficher le contenu de pages web.
– Jacques BAPST
Création du
navigateur
for (int i=0; i<tUrl.length; i++) {
tLinks[i] = new Hyperlink(tCaption[i]);
§ WebView et WebEngine sont des composants riches avec beaucoup
IHM-1 – FX05
43
– Jacques BAPST
Association de
l'URL des pages
}
root.getChildren().add(lnkPanel);
root.getChildren().add(browser);
primaryStage.setScene(new Scene(root));
primaryStage.show();
42
IHM-1 – FX05
– Jacques BAPST
44
Hyperlink [5]
Tooltip [1]
§ Résultat à l'exécution de l'application :
§ Le composant Tooltip permet d'afficher une bulle d'aide ou bulle
d'information lorsque l'utilisateur s'arrête sur un des éléments de
l'interface avec le curseur de la souris (ou autre dispositif de pointage).
• Écran initial (liens hypertextes au haut de la fenêtre)
• Résultat après avoir cliqué sur le lien FX-Experience
§ Ce composant est très courant et permet souvent de faciliter la
compréhension de l'interface et le guidage de l'utilisateur.
§ Le constructeur permet de définir le contenu du texte (qui peut aussi
changer dynamiquement avec setText()).
• new Tooltip("Enter your password");
§ Le texte du tooltip peut être affiché sur plusieurs lignes (avec retour
à la ligne imposés ou wrapping automatique).
§ La propriété graphic permet d'ajouter au texte un composant
additionnel (icône, graphique, …) dont le positionnement peut être
précisé.
IHM-1 – FX05
– Jacques BAPST
45
IHM-1 – FX05
– Jacques BAPST
47
Hyperlink [6]
Tooltip [2]
§ Et si l'on clique sur chacun des deux autres liens, on atteint les pages
associées.
§ La fenêtre de l'application est redimensionnable et on peut naviguer
dans les pages web (on dispose d'un véritable navigateur, mais sans
certaines commodités : bouton back, barre d'adresse, onglets, etc.).
§ Un tooltip peut être ajouté à n'importe quel composant UI standard
(sous-classe de Control) avec la méthode :
• setToolTip(tooltip);
§ Pour les autres éléments d'un graphe de scène (conteneur, graphique,
chart, …), un tooltip peut également être ajouté en utilisant la
méthode statique :
• Tooltip.install(node, tooltip);
§ Un même composant Tooltip peut être associé à plusieurs
composants différents (c'est un composant partageable).
§ Si plusieurs composants se superposent dans l'interface
(partiellement ou totalement), l'activation des bulles d'aide n'est pas
clairement définie.
IHM-1 – FX05
– Jacques BAPST
46
IHM-1 – FX05
– Jacques BAPST
48
Tooltip [3]
§ Quelques propriétés de la classe Tooltip :
text
textAlignment
font
wrapText
graphic
graphicTextGap
contentDisplay
textOverrun
activated
Texte à afficher dans la bulle (String). Des retours à la ligne
('\n') peuvent être insérés.
Alignement du texte (si multiligne) (TextAlignment).
Police de caractères du texte (Font).
Booléen qui indique si des retours à la ligne automatiques
doivent intervenir lorsque la place manque.
Autre composant (type Node) qui accompagne le texte.
Généralement un graphique, une image ou une icône.
Espace entre le graphique et le texte (Double).
Positionnement du graphique relativement au texte
(ContentDisplay).
Spécifie le comportement si le texte dépasse la place
allouée (OverrunStyle).
Booléen qui passe à true dès que le curseur s'arrête sur le
composant associé (tooltip pas nécessairement déjà affiché).
IHM-1 – FX05
– Jacques BAPST
49
Tooltip [4]
private
private
private
private
private
private
Rectangle
Button
Button
Tooltip
Tooltip
Tooltip
rect
btnLogin
btnSave
ttpRect
ttpLogin
ttpSave
=
=
=
=
=
=
new
new
new
new
new
new
Rectangle(180, 15, Color.ORANGE);
Button("Login");
Button("Save");
Tooltip("Click to change the color");
Tooltip("Open a new session");
Tooltip("Save state on cloud");
. . .
ttpRect.setGraphic(new ImageView(ICON));
Tooltip.install(rect, ttpRect);
root.getChildren().add(rect);
btnLogin.setTooltip(ttpLogin);
root.getChildren().add(btnLogin);
btnSave.setTooltip(ttpSave);
root.getChildren().add(btnSave);
. . .
IHM-1 – FX05
– Jacques BAPST
50
Architecture MVC [1]
§ Il existe différentes manières de structurer le code des applications
interactives (celles qui comportent une interface utilisateur).
Cours IHM-1
JavaFX
§ Une des architectures, communément utilisée, et qui comporte de
nombreuses variantes, est connue sous l'acronyme MVC qui signifie
Model - View - Controller.
6 - Architecture MVC
Gestion des événements
§ Dans cette architecture on divise le code des applications en entités
distinctes (modèles, vues et contrôleurs) qui communiquent entre
elles au moyen de divers mécanismes (invocation de méthodes,
génération et réception d'événements, etc.).
§ Cette architecture (ou modèle de conception, design pattern) a été
introduite avec le langage Smalltalk-80 dans le but de simplifier le
développement ainsi que la maintenance des applications, en
répartissant et en découplant les activités dans différents soussystèmes (plus ou moins) indépendants.
Jacques BAPST
[email protected]
IHM-1 – FX06
– Jacques BAPST
3
Architecture MVC [2]
§ Le principe de base de l'architecture MVC est relativement simple,
on divise le code du système interactif en trois parties distinctes :
• Le ou les modèles (Models) qui se chargent de la gestion des données
(accès, transformations, calculs, etc.). Le modèle enregistre
(directement ou indirectement) l'état du système et le tient à jour.
• Les vues (Views) qui comprennent tout ce qui touche à l'interface
utilisateur (composants, fenêtres, boîtes de dialogue) et qui a pour
tâche de présenter les informations (visualisation). Les vues participent
aussi à la détection de certaines actions de l'utilisateur (clic sur un
bouton, déplacement d'un curseur, geste swipe, saisie d'un texte, …).
• Les contrôleurs (Controllers) qui sont chargés de réagir aux actions
de l'utilisateur (clavier, souris, gestes) et à d'autres événements internes
(activités en tâches de fond, timer) et externes (réseau, serveur).
Architecture MVC
Structure d'une application
§ Une application peut également comporter du code qui n'est pas
directement affecté à l'une de ces trois parties (librairies générales,
classes utilitaires, etc.).
IHM-1 – FX06
– Jacques BAPST
2
IHM-1 – FX06
– Jacques BAPST
4
Interactions MVC [1]
MVC – Modèle
§ Un exemple de communication entre les éléments (MVC synchrone).
§ Le modèle (Model) est responsable de la gestion des données qui
caractérisent l'état du système et son évolution.
§ Dans certaines situations (simples) le modèle peut contenir luimême les données mais, la plupart du temps, il agit comme un
intermédiaire (proxy) et gère l'accès aux données qui sont stockées
dans une base de données, un serveur d'informations, le cloud, …
La vue peut consulter
les données du modèle.
Model
View
La vue détermine quels
événements sont passés
au contrôleur.
Le contrôleur peut
consulter et mettre à
jour le modèle en fonction
des événements.
Controller
Le contrôleur peut demander
la mise à jour des éléments
affichés.
§ Le modèle est souvent défini par une ou plusieurs interfaces Java qui
permettent de s'abstraire de la façon dont les données (les objets
métier) sont réellement stockées (notion de DAO Data Access Object).
§ Il offre également les méthodes et fonctions permettant de gérer,
transformer et manipuler ces données.
§ Les informations gérées par le modèle doivent être indépendantes
de la manière dont elles seront affichées. Le modèle doit pouvoir
exister indépendamment de la représentation visuelle des données.
Flux de données
Événements
IHM-1 – FX06
5
– Jacques BAPST
IHM-1 – FX06
– Jacques BAPST
Interactions MVC [1]
MVC – Vue
§ Lorsqu'un utilisateur interagit avec une interface, les différents
éléments de l'architecture MVC interviennent pour interpréter et
traiter l'événement.
§ La vue (View) est chargée de la représentation visuelle des
informations en faisant appel à des écrans, des fenêtres, des
composants, des conteneurs (layout), des boîtes de dialogue, etc.
Interface
utilisateur
Vue
§ Plusieurs vues différentes peuvent être basées sur le même modèle
(plusieurs représentations possibles d'un même jeu de données).
Résultats
Stockage
Traitements
Décisions
Action
interprétée
Utilisateur
IHM-1 – FX06
Mise à jour
de l'interface
– Jacques BAPST
§ La vue intercepte certaines actions de l'utilisateur et les transmet au
contrôleur pour qu'il les traite (souris, clavier, gestes, …).
§ La mise à jour de la vue peut être déclenchée par un contrôleur ou
par un événement signalant un changement intervenu dans les
données du modèle par exemple (mode asynchrone).
Opérations
(fonctions)
Contrôleur
7
Modèle
6
§ La représentation visuelle des informations affichées peut dépendre
du Look-and-Feel adopté (ou imposé) et peut varier d'une
plateforme à l'autre. L'utilisateur peut parfois modifier lui même le
thème de présentation des informations.
IHM-1 – FX06
– Jacques BAPST
8
Variantes de l'architecture MVC [1]
§ Le contrôleur (Controller) est chargé de réagir aux différentes
actions de l'utilisateur ou à d'autres événements qui peuvent
survenir.
§ Il existe de nombreuses déclinaisons de l'architecture MVC ainsi que
des variantes dont les plus connues sont :
§ Dans les applications simples, le contrôleur gère la synchronisation
entre la vue et le modèle (rôle de chef d'orchestre).
§ Le contrôleur est informé des événements qui doivent être traités et
sait d'où ils proviennent.
< < <
§ La plupart des actions étant interceptées (ou en lien) avec la vue, il
existe un couplage assez fort entre la vue et le contrôleur.
§ Le contrôleur communique généralement avec le modèle et avec la
vue. C'est le sens des transferts et le mode de communication qui
caractérisent différentes variantes de l'architecture MVC.
IHM-1 – FX06
– Jacques BAPST
Compléme nt
§ Le contrôleur définit le comportement de l'application et sa logique
(comment elle réagit aux sollicitations, business logic).
> > >
MVC – Contrôleur
9
• MVP
: Model - View - Presenter
• MVVM : Model - View - View-Model
§ Dans ces variantes le modèle et la vue sont définis de manière quasi
identique. C'est le rôle du contrôleur et sa manière de communiquer
avec les autres parties qui distinguent ces variantes de l'architecture
MVC classique.
Ces différentes variantes
d'architecture ne pourront
pas être explorées plus en
détail dans le cadre de ce
cours.
Source: tomyrhymond.wordpress.com/2011/09/16/mvc-mvp-and-mvvm
IHM-1 – FX06
– Jacques BAPST
11
Structure d'une application [1]
§ Une application JavaFX qui respecte l'architecture MVC comprendra
généralement différentes classes et ressources :
• Le modèle sera fréquemment représenté par une ou plusieurs classes
qui implémentent généralement une interface permettant de s'abstraire
des techniques de stockage des données.
• Les vues seront soit codées en Java ou déclarées en FXML. Des feuilles
de styles CSS pourront également être définies pour décrire le rendu.
• Les contrôleurs pourront prendre différentes formes :
Gestion des événements
Ils peuvent être représentés par des classes qui traitent chacune un
événement particulier ou qui traitent plusieurs événements en relation
(menu ou groupe de boutons par exemple)
ð Si le code est très court, ils peuvent parfois être inclus dans les vues, sous
forme de classes locales anonymes ou d'expressions lambda.
• La classe principale (celle qui comprend la méthode main()) peut faire
ð
l'objet d'une classe séparée ou être intégrée à la classe de la fenêtre
principale (vue principale).
• D'autres classes utilitaires peuvent venir compléter l'application.
IHM-1 – FX06
– Jacques BAPST
10
IHM-1 – FX06
– Jacques BAPST
12
Programmation événementielle [1]
Programmation événementielle [3]
§ La programmation des applications avec interfaces graphiques est
généralement basée sur un paradigme nommé programmation
événementielle (Event Programming).
§ En programmation événementielle, on prépare les actions (code) à
exécuter en les associant aux événements que l'on souhaite traiter
(enregistrement des callback) et on attend que le processus de
surveillance nous avertisse en exécutant le code prévu.
§ Avec la programmation événementielle, ce sont les événements
(généralement déclenchés par l'utilisateur, mais aussi par le système) qui
pilotent l'application. Ce mode non directif convient bien à la gestion
des interfaces graphiques où l'utilisateur à une grande liberté
d'action (l'interface est au service de l'utilisateur et non l'inverse).
§ La programmation événementielle nécessite qu'un processus (en
tâche de fond) surveille constamment les actions de l'utilisateur
susceptibles de déclencher des événements qui pourront être
ensuite traités (ou non) par l'application (contrôleurs).
IHM-1 – FX06
– Jacques BAPST
EventProg {
Initialize();
CreateGUI();
RegisterCallback();
StartEventLoop();
Après initialisation, on crée l'interface
graphique puis on associe du code à
chaque événement que l'on souhaite
traiter (RegisterCallback).
// Thread
________________________________________________________________________________
Event Loop Thread
§ Dans la programmation impérative (séquence d'instructions), c'est
le programme qui dirige les opérations (par exemple, il demande à
l'utilisateur d'entrer des valeurs, calcule et affiche un résultat, etc.).
Callback1()
code1;
// Button clicked
Callback2()
code2;
// Key pressed
Callback3()
code3
// Window resized
. . .
. . .
// Pinch gesture
Lorsque les événements enregistrés
se produisent, le code associé est
automatiquement exécuté par le
processus de surveillance qui tourne
en tâche de fond Event Thread.
}
13
IHM-1 – FX06
– Jacques BAPST
Programmation événementielle [2]
Événement [1]
§ En programmation séquentielle, une interface utilisateur (en lignes
de commandes) pourrait être codée selon le pseudo-code suivant qui
illustre le principe.
§ Un événement (event) constitue une notification qui signale que
quelque chose s'est passé (un fait, un acte digne d'intérêt).
SequentialProg {
Initialize();
Loop {
cmd = readCommand();
Switch (cmd) {
Case: command1
process_cmd1();
Case: command2
process_cmd2();
Case: command3
process_cmd3();
Case: . . .
. . .
}
}
}
IHM-1 – FX06
15
§ Un événement peut être provoqué par :
• Une action de l'utilisateur
Un clic avec la souris
La pression sur une touche du clavier
ð Le déplacement d'une fenêtre
ð Un geste sur un écran tactile
ð ...
ð
Une boucle sans fin répète le cycle
suivant :
- Demande à l'utilisateur de saisir
une commande (prompt)
- Attend que la prochaine
commande soit entrée au
clavier (+décodage)
- Selon la commande entrée, du
code spécifique est exécuté.
ð
• Un changement détecté par le système
Une valeur a changé (propriété)
Un timer est arrivé à échéance
ð Un processus a terminé un calcul
ð Une information est arrivée par le réseau
ð ...
ð
ð
Lorsque le traitement est terminé,
le cycle recommence (on attend la
prochaine commande).
– Jacques BAPST
14
IHM-1 – FX06
– Jacques BAPST
16
Événement [2]
Gestion des événement [1]
§ En JavaFX les événements sont représentés par des objets de la
classe Event ou, plus généralement, d'une de ses sous-classes.
§ Le traitement des événements implique les étapes suivantes :
• La sélection de la cible (Target) de l'événement
Événement clavier ® le composant qui possède le focus
Événement souris ® le composant sur lequel se trouve le curseur
® le composant au centre de la position initiale
ð Gestes continus
ð
§ De nombreux événements sont prédéfinis (MouseEvent, KeyEvent,
DragEvent, ScrollEvent, …) mais il est également possible de créer
ses propres événements en créant des sous-classes de Event.
ð
Si plusieurs composant se trouvent à un emplacement donné c'est celui qui est
"au-dessus" qui est considéré comme la cible.
§ Chaque objet de type "événement" comprend (au moins) les
informations suivantes :
• La détermination de la chaîne de traitement des événements
• Le type de l'événement (EventType consultable avec getEventType())
Le type permet de classifier les événements à l'intérieur d'une même classe
(par exemple, la classe KeyEvent englobe KEY_PRESSED, KEY_RELEASED, KEY_TYPED)
• La source de l'événement (Object consultable avec getSource())
ð Objet qui est à l'origine de l'événement selon la position dans la chaîne de
traitement des événements (event dispatch chain).
• La cible de l'événement (EventTarget consultable avec getTarget())
ð Composant cible de l'événement (indépendamment de la position dans la
chaîne de traitement des événements (event dispatch chain)
ð
IHM-1 – FX06
– Jacques BAPST
17
(Event Dispatch Chain : chemin des événements dans le graphe de scène)
ð Le chemin part de la racine (Stage) et va jusqu'au composant cible en
parcourant tous les nœuds intermédiaires
• Le traitement des filtres d'événement (Event Filter)
ð
Exécute le code des filtres en suivant le chemin descendant, de la racine
(Stage) jusqu'au composant cible
• Le traitement des gestionnaires d'événement (Event Handler)
ð
Exécute le code des gestionnaires d'événement en suivant le chemin
montant, du composant cible à la racine (Stage)
IHM-1 – FX06
– Jacques BAPST
Types d'événements
Gestion des événement [2]
§ Chaque événement est d'un certain type (objet de type EventType).
§ Un exemple d'application avec son graphe de scène.
§ Chaque type d'événement possède un nom (getName()) et un type
parent (getSuperType()).
§ Si l'utilisateur clique sur le bouton Insert, un événement de type
Action va être déclenché et va se propager le long du chemin
correspondant à la chaîne de traitement (Event Dispatch Chain).
§ Les types d'événement forment donc une hiérarchie.
• Par exemple si on presse une touche le nom de l'événement est
KEY_PRESSED et le type parent est KeyEvent.ANY.
Stage
§ A la racine, on a
Scene
Event.ANY
(= EventType.ROOT)
BorderPane
Label
La figure représente une partie
seulement de la hiérarchie des
types d'événements.
IHM-1 – FX06
19
– Jacques BAPST
18
IHM-1 – FX06
TextArea
HBox
Button
Button
Button
Insert
Delete
Quit
– Jacques BAPST
20
Gestion des événement [3]
Gestion des événements [5]
§ L'événement se propage d'abord vers le bas, depuis le nœud racine
(Stage) jusqu'à la cible (Target) - c'est-à-dire le bouton cliqué - et les
filtres (Event Filter) éventuellement enregistrés sont exécutés (dans
l'ordre de passage).
§ Pour gérer un événement (exécuter des instructions), il faut créer un
récepteur d'événement (Event Listener), appelé aussi écouteur
d'événement, et l'enregistrer sur les nœuds du graphe de scène où
l'on souhaite intercepter l'événement et effectuer un traitement.
Stage
§ Un récepteur d'événement peut être enregistré comme filtre ou
comme gestionnaire d'événement. La différence principale entre
les deux réside dans le moment où le code est exécuté :
Event Capturing Phase
Scene
• Les filtres (filters) sont exécutés dans la phase descendante de la chaîne
de traitement des événements (avant les gestionnaires)
• Les gestionnaires (handlers) sont exécutés dans la phase montante de la
chaîne de traitement des événements (après les filtres)
BorderPane
Label
TextArea
HBox
Button
Button
Button
Insert
Delete
Quit
IHM-1 – FX06
– Jacques BAPST
§ Les filtres, comme les gestionnaires d'événements, sont des objets
qui doivent implémenter l'interface fonctionnelle (générique)
EventHandler<T extends Event> qui impose l'unique méthode
handle(T event) qui se charge de traiter l'événement.
21
IHM-1 – FX06
– Jacques BAPST
23
Gestion des événement [4]
Gestion des événements [6]
§ L'événement remonte ensuite depuis la cible jusqu'à la racine et les
gestionnaires d'événements (Event Listener) éventuellement
enregistrés sont exécutés (dans l'ordre de passage).
§ Pour enregistrer un récepteur d'événement sur un nœud du graphe
de scène, on peut :
Stage
Event Bubbling Phase
• Utiliser la méthode addEventHandler() que possèdent tous les nœuds
(les sous-classes de Node) et qui permet d'enregistrer un gestionnaire
d'événement
Scene
• Utiliser une des méthodes utilitaires (convenience methods) dont
disposent certains composants et qui permettent d'enregistrer un
gestionnaire d'événement en tant que propriété du composant.
La plupart des composants disposent de méthodes nommées selon le
schéma setOnEventType(EventHandler), par exemple :
BorderPane
Label
IHM-1 – FX06
TextArea
• Utiliser la méthode addEventFilter() que possèdent tous les nœuds
(les sous-classes de Node) et qui permet d'enregistrer un filtre
HBox
Button
Button
Button
Insert
Delete
Quit
– Jacques BAPST
22
IHM-1 – FX06
ð
setOnAction(Handler)
ð
setOnKeyTyped(Handler)
– Jacques BAPST
24
Gestion des événements [7]
Event Handling [1]
§ Par défaut, les événements se propagent donc le long de la chaîne
de traitement (Event Dispatch Chain) en traversant le graphe de
scène de la racine jusqu'au composant cible et retour.
private
private
private
private
private
private
private
§ Sur chaque nœud du graphe de scène peuvent être enregistrés
• un ou plusieurs filtres
• un ou plusieurs gestionnaires d'événements
qui se chargeront de traiter différents types d'événements avant de
les propager au nœud suivant en parcourant la chaîne de traitement.
§ Cependant, chaque récepteur d'événement (filtre ou gestionnaire) peut
interrompre la chaîne de traitement en consommant l'événement,
c’est-à-dire en invoquant la méthode consume().
§ Si un récepteur d'événement appelle la méthode consume(), la
propagation de l'événement s'interrompt et les autres récepteurs
(qui suivent dans la chaîne de traitement) ne seront plus activés.
IHM-1 – FX06
– Jacques BAPST
25
=
=
=
=
=
=
=
new
new
new
new
new
new
new
BorderPane();
HBox(10);
Label("Event Handling");
TextArea();
Button("Insert");
Button("Delete");
Button("Quit");
primaryStage.setTitle("Event Handling");
root.setPadding(new Insets(10));
//--- Title
lblTitle.setFont(Font.font("System", FontWeight.BOLD, 20));
lblTitle.setTextFill(Color.DARKGREEN);
BorderPane.setAlignment(lblTitle, Pos.CENTER);
BorderPane.setMargin(lblTitle, new Insets(0, 0, 10, 0));
root.setTop(lblTitle);
IHM-1 – FX06
27
– Jacques BAPST
Event Handling [2]
§ Si un nœud du graphe de scène possède plusieurs récepteurs
d'événements enregistrés, l'ordre d'activation de ces récepteurs
sera basé sur la hiérarchie des types d'événement :
//--- Button Panel
btnPanel.getChildren().add(btnInsert);
btnPanel.getChildren().add(btnDelete);
btnPanel.getChildren().add(btnQuit);
btnPanel.setAlignment(Pos.CENTER_RIGHT);
btnPanel.setPadding(new Insets(10, 0, 0, 0));
root.setBottom(btnPanel);
• Un récepteur pour un type spécifique sera toujours exécuté avant un
récepteur pour un type plus générique
• Par exemple un filtre enregistré pour MouseEvent.MOUSE_PRESSED sera
exécuté avant un filtre pour MouseEvent.ANY qui sera exécuté avant un
filtre pour InputEvent.ANY
• L'ordre d'exécution des
récepteurs pour des types
de même niveau n'est
pas défini
• La consommation d'un
événement n'interrompt
pas le traitement des autres
récepteurs enregistrés sur le
même nœud
– Jacques BAPST
root
btnPanel
lblTitle
txaMsg
btnInsert
btnDelete
btnQuit
//--- Text-Area
txaMsg.setWrapText(true);
txaMsg.setPrefColumnCount(15);
txaMsg.setPrefRowCount(10);
root.setCenter(txaMsg);
Gestion des événements [8]
IHM-1 – FX06
BorderPane
HBox
Label
TextArea
Button
Button
Button
Code de l'interface sans la
gestion des événements.
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.show();
26
IHM-1 – FX06
– Jacques BAPST
28
Event Handling [3]
Event Handling [5]
§ Pour traiter les événements des boutons, on peut créer une classe
contrôleur qui implémente EventHandler et effectue les opérations
souhaitées dans la méthode handle().
§ Une autre manière de faire consiste à créer le contrôleur sous la
forme d'une classe locale anonyme. Par exemple, pour le bouton
Delete :
. . .
public class InsertButtonController implements EventHandler<ActionEvent> {
private TextArea tArea;
//--- Constructeur --------------------------------public InsertButtonController(TextArea tArea) {
this.tArea = tArea;
}
//--- Code exécuté lorsque l'événement survient ---@Override
public void handle(ActionEvent event) {
tArea.appendText("A");
}
Si on veut agir sur des
composants de la vue,
il faut transmettre les
références nécessaires.
Si le code est plus complexe,
on invoquera de préférence
une méthode de la vue.
}
IHM-1 – FX06
– Jacques BAPST
29
Event Handling [4]
//--- Button Events Handling
btnDelete.addEventHandler(ActionEvent.ACTION,
new EventHandler<ActionEvent>() {
@Override
public void handle(ActionEvent event) {
txaMsg.deletePreviousChar();
}
});
. . .
§ A chaque clic sur le bouton Delete, le
gestionnaire d'événement sera exécuté
et un caractère sera supprimé dans le
composant TextArea.
IHM-1 – FX06
– Jacques BAPST
31
Event Handling [5]
§ Dans la vue, il faut ensuite créer une instance de ce contrôleur et
l'enregistrer comme gestionnaire d'événement (type ACTION) sur le
bouton concerné en invoquant la méthode addEventHandler().
§ Une troisième possibilité pour traiter les événements des boutons,
est d'utiliser la méthode setOnAction() et passer en paramètre
une expression lambda implémentant la méthode handle() de
l'interface EventHandler.
. . .
//--- Button Events Handling
InsertButtonController insertCtrl = new InsertButtonController(txaMsg);
§ Par exemple pour traiter les trois boutons de l'interface :
//--- Button Events Handling
btnInsert.setOnAction(event -> {
btnInsert.addEventHandler(ActionEvent.ACTION, insertCtrl);
. . .
txaMsg.appendText("A");
});
§ A chaque clic sur le bouton Insert, le
gestionnaire d'événement sera exécuté
et un caractère 'A' sera ajouté dans le
composant TextArea.
btnDelete.setOnAction(event -> {
txaMsg.deletePreviousChar();
});
btnQuit.setOnAction(event ->
{
Platform.exit();
});
IHM-1 – FX06
– Jacques BAPST
30
IHM-1 – FX06
– Jacques BAPST
32
Classe 'contrôleur'
Méthodes setOn…() [2]
§ Dans la variante MVC synchrone, il est fréquent que la classe du
contrôleur reçoive dans son constructeur les références du modèle
et de la vue.
public class ButtonController implements EventHandler<ActionEvent> {
private AppModel model;
private MainView view;
Références du
modèle et de la vue.
Action de l'utilisateur
Événement
Texte modifié (durant la saisie)
InputMethodEvent
Bouton cliqué
ComboBox ouverte ou fermée
Une des options d'un menu contextuel activée ActionEvent
Option de menu activée
Pression sur Enter dans un champ texte
//--- Constructeur ------------------------------------------public ButtonController(AppModel model, MainView view) {
this.model = model;
this.view = view;
}
Élément (Item) d'une liste,
ListView.
… d'une table ou
TableColumn.
//--- Code exécuté lorsque l'événement survient -------------@Override
public void handle(ActionEvent event) {
Le contrôleur accède
int newVal = model.getInfo();
aux données du modèle
view.updateInfo(newVal);
et met à jour la vue.
}
Erreur survenue dans le media-player
Menu est affiché (déroulé) ou masqué (enroulé)
Fenêtre popup masquée
Onglet sélectionné ou fermé
Fenêtre affichée, fermée, masquée
– Jacques BAPST
33
Méthodes setOn…() [1]
IHM-1 – FX06
Événement
IHM-1 – FX06
EditEvent
MediaErrorEvent
Event
Event
Event
WindowEvent
ButtonBase
ComboBoxBase
ContextMenu
MenuItem
TextField
ListView
TableColumn
TreeView
MediaView
Menu
PopupWindow
Tab
Window
– Jacques BAPST
35
Fantas'TIP [1]
§ Liste des principales actions associées à des méthodes utilitaires qui
permettent d'enregistrer des gestionnaires d'événements (il faut
rechercher les méthodes setOnEventType() dans la classe).
Action de l'utilisateur
Pression sur une touche du clavier
Déplacement de la souris ou pression sur une
de ses touches
Glisser-déposer avec la souris (Drag-and-Drop)
Glisser-déposer propre à la plateforme (geste
par exemple)
Composant "scrollé"
Geste de rotation
Geste de balayage/défilement (swipe)
Un composant est touché
Geste de zoom
Activation du menu contextuel
CellEditEvent
TreeView.
… d'un arbre a été édité
}
IHM-1 – FX06
EditEvent
Dans classe
Node, Scene
KeyEvent
Dans classe
Node, Scene
MouseEvent
Node, Scene
MouseDragEvent
Node, Scene
DragEvent
Node, Scene
ScrollEvent
Node, Scene
RotateEvent
Node, Scene
§ Exemple d'application pour illustrer différentes manières de réaliser
le découpage du code en exploitant plusieurs des techniques qui
sont à disposition (classes EventHandler, expressions lambda, binding de
propriétés, …), tout en respectant les principes de l'architecture MVC.
§ L'application Fantas'TIP est un utilitaire qui calcule le pourboire à
prévoir et le montant par personne, en fonction du montant de la
note, du pourcentage octroyé et du nombre de convives.
Node, Scene
TouchEvent
Node, Scene
ZoomEvent
Node, Scene
ContextMenuEvent Node, Scene
SwipeEvent
– Jacques BAPST
34
IHM-1 – FX06
– Jacques BAPST
36
Fantas'TIP [2]
Fantas'TIP [4]
§ Quatre variantes de cette application ont été créées :
//---------------------------------------------------------------------------@Override
public void setTipPercent(int percent) {
if (percent < 0) throw new IllegalArgumentException("Percent < 0");
this.tipPercent = percent;
• Variante 1 : Avec un contrôleur réalisé sous forme de classe 'ordinaire'
• Variante 2 : Avec un contrôleur réalisé sous forme d'expression lambda
• Variante 3 : Avec un contrôleur réalisé sous forme de liaisons de hautniveau (high-level binding) entre les données d'entrée
(saisies par l'utilisateur) et les données de sortie (calculées)
• Variante 4 : Avec un contrôleur réalisé sous forme de liaisons de basniveau (low-level binding) entre les données d'entrée
(saisies par l'utilisateur) et les données de sortie (calculées)
}
//---------------------------------------------------------------------------@Override
public void setNbPeople(int nbPeople) {
if (nbPeople <= 0) throw new IllegalArgumentException("Nb people <= 0");
this.nbPeople = nbPeople;
}
//---------------------------------------------------------------------------@Override
public double getTipPerPerson() {
return bill * tipPercent / 100.0 / nbPeople;
}
§ Quelques extraits de code (les éléments importants) figurent dans les
pages qui suivent.
//---------------------------------------------------------------------------@Override
public double getTotalPerPerson() {
return bill/nbPeople + getTipPerPerson();
}
• L'intégralité du code source des applications est disponible sur la page :
http://jacques.bapst.home.hefr.ch/ihm1/src/chap06_FantasTIP
}
• Les applications (exécutables) sont disponibles sur la page :
http://jacques.bapst.home.hefr.ch/ihm1/applic/fantastip
IHM-1 – FX06
– Jacques BAPST
37
IHM-1 – FX06
Fantas'TIP [3]
§ Variante 1 : classe contrôleur (du bouton 'Calculate')
• Interface du modèle
• Classe implémentant cette interface
public class FantasTipController implements EventHandler<ActionEvent> {
private IFantasTipModel model;
private FantasTipView
view;
//---------------------------------------------------------------------------// Constructor receives model and view references
//---------------------------------------------------------------------------public FantasTipController(IFantasTipModel model, FantasTipView
view) {
this.model = model;
this.view = view;
}
public interface IFantasTipModel {
void
setBill(double amount);
void
setTipPercent(int percent);
void
setNbPeople(int nbPeople);
double getTipPerPerson();
double getTotalPerPerson();
//---------------------------------------------------------------------------// Method executed when 'Calculate' button is pressed
//---------------------------------------------------------------------------@Override
public void handle(ActionEvent event) {
//--- Get values from view (check for errors) and update model data
try {
double bill
= view.getBillValue();
int
tipPercent = view.getTipPercentValue();
int
nbPeople
= view.getNbPeopleValue();
}
public class FantasTipModel implements IFantasTipModel {
bill
tipPercent
nbPeople
39
Fantas'TIP [5]
§ Variante 1 : modèle de l'application
private double
private int
private int
– Jacques BAPST
= 0;
= 0;
= 1;
//---------------------------------------------------------------------------public FantasTipModel() {
}
model.setBill(bill);
model.setTipPercent(tipPercent);
model.setNbPeople(nbPeople);
}
catch (IllegalStateException e) {
return;
}
//---------------------------------------------------------------------------@Override
public void setBill(double amount) {
if (amount < 0) throw new IllegalArgumentException("Amount < 0");
this.bill = amount;
}
// Errors in some input values
//--- Update view with values from model output data
view.updateTipPerPerson(model.getTipPerPerson());
view.updateTotalPerPerson(model.getTotalPerPerson());
}
}
IHM-1 – FX06
– Jacques BAPST
38
IHM-1 – FX06
– Jacques BAPST
40
Fantas'TIP [6]
Fantas'TIP [8]
§ Variante 1 : création d'une instance du contrôleur et association au
bouton Calculate.
• Ces activités sont effectuées dans le code de la vue (la méthode privée
createController() est appelée dans la méthode start()).
. . .
//---------------------------------------------------------------------------// Create button controller instance and associate it to button
//---------------------------------------------------------------------------private void createController() {
controller = new FantasTipController(model, this);
btnCalc.setOnAction(controller);
}
. . .
IHM-1 – FX06
41
– Jacques BAPST
Fantas'TIP [7]
• L'expression lambda est écrite dans le code de la vue (la méthode
createController() est appelée dans la méthode start()).
. . .
//---------------------------------------------------------------------------// Create button controller with lambda expression
//---------------------------------------------------------------------------private void createController() {
btnCalc.setOnAction(event -> {
try {
double bill
= getBillValue();
int
tipPercent = getTipPercentValue();
int
nbPeople
= getNbPeopleValue();
model.setBill(bill);
model.setTipPercent(tipPercent);
model.setNbPeople(nbPeople);
// Errors in some input values
Même si le code du
contrôleur est écrit
dans le fichier source
de la vue, l'expression
lambda constitue bien
un contrôleur qui sera
représenté par une
instance d'une classe
anonyme qui
implémente l'interface
EventHandler.
//--- Update view with values from model output data
updateTipPerPerson(model.getTipPerPerson());
updateTotalPerPerson(model.getTotalPerPerson());
});
IHM-1 – FX06
– Jacques BAPST
43
– Jacques BAPST
§ Variante 3 : le contrôleur est constitué par les liaisons (bindings)
créées entre les propriétés des composants d'entrée et ceux de
sortie.
§ Dans cette variante, on crée des liaisons dites de haut-niveau (highlevel bindings) car les opérations sont effectuées par des invocations
de méthodes en cascade. Cet enchaînement d'appels est rendu
possible par l'utilisation d'un modèle de conception appelé fluent
interface pattern ou fluent API.
• Les opérations disponibles sont limitées mais suffisantes pour les calculs
nécessaires dans l'application proposée.
• Ex : result = a.multiply(b).add(c.multiply(d));
§ Des méthodes statiques de la classe Bindings peuvent également
être utilisées pour effectuer des opérations de haut-niveau entre des
propriétés.
• Ex :
}
IHM-1 – FX06
• Les données de sortie sont automatiquement mises à jour lorsqu'on
change les données d'entrée (durant la saisie des champs texte).
Fantas'TIP [9]
§ Variante 2 : le contrôleur est créé sous forme d'expression lambda.
}
catch (IllegalStateException e) {
return;
}
§ Dans les variantes 3 et 4, les vues possèdent des liaisons (par
binding) entre les données d'entrées et les données de sortie.
L'interface n'a donc plus besoin de bouton pour déclencher le calcul.
42
IHM-1 – FX06
result = Bindings.add(Bindings.multiply(a, b),
Bindings.multiply(c, d));
– Jacques BAPST
44
Fantas'TIP [10]
Fantas'TIP [12]
§ Variante 3 : méthode createViewModelBindings() appelée dans la
méthode start() pour créer les liaisons entre les propriétés.
. . .
private void createViewModelBindings() {
//--- Bind view text data with model number data (converter needed)
tfdBill.textProperty().bindBidirectional(model.getBillPty(),
dsConverter());
tfdTipPct.textProperty().bindBidirectional(model.getTipPercentPty(),
isConverter());
tfdNbPple.textProperty().bindBidirectional(model.getNbPeoplePty(),
isConverter());
§ Variante 4 : le contrôleur est constitué par les liaisons (bindings) de
bas-niveau (low-level bindings) entre les propriétés des composants
d'entrée et ceux de sortie.
§ Pour créer des liaisons de bas-niveau, on redéfinit les méthodes
computeValue() de liaisons existantes (on crée des sous-classes de
IntegerBinding, DoubleBinding, StringBinding, etc.).
• On dispose dans ce cas de tout le potentiel des instructions et des
librairies à disposition pour effectuer les calculs qui lient les propriétés
(aussi complexes soient-ils).
• Ne pas oublier de définir toutes les propriétés dont dépend la liaison en
invoquant la méthode parente super.bind(p1, p2, …)
//--- Bind 'output' model properties to calculated properties
model.getTipPerPersonPty().bind(model.getBillPty()
.multiply(model.getTipPercentPty())
.divide(100)
.divide(model.getNbPeoplePty()));
model.getTotalPerPersonPty().bind((model.getBillPty()
.divide(model.getNbPeoplePty())
.add(model.getTipPerPersonPty())));
//--- Bind TextField properties to model properties converted to String
tfdRTip.textProperty().bind(model.getTipPerPersonPty().asString());
tfdRTotal.textProperty().bind((model.getTotalPerPersonPty().asString()));
}
IHM-1 – FX06
– Jacques BAPST
45
IHM-1 – FX06
Fantas'TIP [11]
– Jacques BAPST
47
Fantas'TIP [13]
§ Variante 3 : méthode dsConverter() utilisée pour convertir une
propriété de type double en String et inversement.
§ Variante 4 : méthode dblTipPerPersonBinding() qui retourne une
spécialisation de DoubleBinding (qui calcule le pourboire par convive).
. . .
. . .
//---------------------------------------------------------------------------// Number(Double) <--> String Converter
//---------------------------------------------------------------------------private NumberStringConverter dsConverter() {
return new NumberStringConverter() {
@Override
public Number fromString(String value) {
try {
return Double.parseDouble(value);
}
catch (NumberFormatException e) {
return Double.NaN;
}
}
//---------------------------------------------------------------------------// Low-level binding (calculate TipPerPerson from 'input' properties)
//---------------------------------------------------------------------------private DoubleBinding dblTipPerPersonBinding() {
DoubleBinding dblBinding = new DoubleBinding() {
{
super.bind(model.getBillPty(),
model.getTipPercentPty(),
model.getNbPeoplePty());
}
@Override
protected double computeValue() {
double tipPP = model.getBillPty().get() *
model.getTipPercentPty().get()/100.0 /
model.getNbPeoplePty().get();
return tipPP;
}
@Override
public String toString() {
return super.toString();
}
};
};
return dblBinding;
}
}
IHM-1 – FX06
– Jacques BAPST
46
IHM-1 – FX06
– Jacques BAPST
48
Fantas'TIP [14]
§ Variante 4 : méthode strTipPerPersonBinding() qui retourne une
spécialisation de StringBinding (qui convertit et formate la valeur).
. . .
//---------------------------------------------------------------------------// Low-level binding (convert TipPerPerson to formatted string)
//---------------------------------------------------------------------------private StringBinding strTipPerPersonBinding() {
StringBinding strBinding = new StringBinding() {
{
super.bind(model.getTipPerPersonPty());
}
@Override
protected String computeValue() {
double tipPP = model.getTipPerPersonPty().get();
if (tipPP < 0) return "n/a";
String fmtRes = String.format("%.2f", tipPP);
return fmtRes;
}
};
return strBinding;
}
IHM-1 – FX06
– Jacques BAPST
49
Fantas'TIP [15]
§ Variante 4 : méthode createViewModelBindings() qui effectue
l'ensemble des liaisons entre les différentes propriétés.
. . .
//---------------------------------------------------------------------------// Create all bindings (view-model-view)
//---------------------------------------------------------------------------private void createViewModelBindings() {
//--- Bind view text data with model number data (converter needed)
tfdBill.textProperty().bindBidirectional(model.getBillPty(),
dsConverter());
tfdTipPct.textProperty().bindBidirectional(model.getTipPercentPty(),
isConverter());
tfdNbPple.textProperty().bindBidirectional(model.getNbPeoplePty(),
isConverter());
//--- Bind 'output' model properties to low-level calculated properties
model.getTipPerPersonPty().bind(dblTipPerPersonBinding());
model.getTotalPerPersonPty().bind(dblTotalPerPersonBinding());
//--- Bind TextField properties to model properties converted to String
tfdRTip.textProperty().bind(strTipPerPersonBinding());
tfdRTotal.textProperty().bind(strTotalPerPersonBinding());
}
IHM-1 – FX06
– Jacques BAPST
50
Fichiers FXML [1]
§ Au centre de l'approche déclarative, se trouvent les fichiers FXML.
Cours IHM-1
JavaFX
§ Un fichier FXML est un fichier au format XML dont la syntaxe est
conçue pour décrire l'interface (la vue) avec ses composants, ses
conteneurs, sa disposition, …
• Le fichier FXML décrit le "quoi" mais pas le "comment"
7 - FXML
SceneBuilder
§ A l'exécution, le fichier FXML sera chargé par l'application (classe
FXMLLoader) et un objet Java sera créé (généralement la racine est un
conteneur) avec les éléments que le fichier décrit (les composants,
conteneurs, graphiques, …).
Jacques BAPST
§ Il est possible de créer les fichiers FXML avec un éditeur de texte
mais, plus généralement, on utilise un outil graphique (SceneBuilder)
qui permet de concevoir l'interface de manière conviviale et de
générer automatiquement le fichier FXML correspondant.
• Un fichier FXML constitue une forme particulière de sérialisation
d'objets, utilisée spécifiquement pour décrire les interfaces
[email protected]
IHM-1 – FX07
– Jacques BAPST
I/F procédurales « déclaratives
Fichiers FXML [2]
§ Avec JavaFX, les interfaces peuvent être créées de deux manières :
§ Les objets créés par le chargement de fichiers FXML peuvent être
assignés à la racine d'un graphe de scène ou représenter un des
nœuds dans un graphe de scène créé de manière procédurale.
• Procédurale : en écrivant du code Java qui fait appel aux API de la
plateforme et qui utilise les composants/conteneurs à
disposition (classes et interfaces)
• Déclarative : en décrivant l'interface dans un fichier au format FXML
qui sera ensuite chargé dynamiquement dans
l'application
§ Une fois chargés, les nœuds issus de fichiers FXML sont totalement
équivalents à ceux créés de manière procédurale. Les mêmes
opérations et manipulations peuvent leur être appliquées.
§ Les premiers chapitres du cours ont décrit les bases de la technique
procédurale (programmatique) permettant de créer des interfaces.
§ Le présent chapitre abordera la deuxième possibilité de créer les
interfaces (les vues) en utilisant notamment l'outil SceneBuilder qui
permet, de manière interactive, de créer les fichiers FXML.
§ SceneBuilder est une application qui doit être installée et, pour une
utilisation avec Eclipse, le plugin e(fx)clipe est recommandé.
IHM-1 – FX07
– Jacques BAPST
3
2
§ Le langage FXML n'est pas associé à un schéma XML mais la
structure de sa syntaxe correspond à celle des API JavaFX :
• Les classes JavaFX (conteneurs, composants) peuvent être utilisées
comme éléments dans la syntaxe XML
• Les propriétés des composants correspondent à leurs attributs
§ Même si certaines possibilités existent (en lien notamment avec du
code JavaScript) on conseille généralement d'utiliser les fichiers FXML
exclusivement pour décrire les interfaces, et d'effectuer tous les
traitements (activité des contrôleurs) dans le code Java.
IHM-1 – FX07
– Jacques BAPST
4
SceneBuilder [1]
Say Hello [1]
§ L'outil graphique SceneBuilder permet de concevoir l'interface de
manière interactive (WYSIWYG) en assemblant les conteneurs et les
composants et en définissant leurs propriétés.
§ Un exemple d'application très simple :
§ Le mode de fonctionnement de cet utilitaire est assez classique avec
une zone d'édition centrale, entourée d'un certain nombre d'outils :
palettes de conteneurs, de composants, de menus, de graphiques,
vue de la structure hiérarchique de l'interface, inspecteurs de
propriétés, de layout, etc.
• Un conteneur BorderPane
• Deux composants : Label et Button
• Quelques adaptations de propriétés (taille, couleur, marge, …)
§ Graphe de scène et apparence finale de l'application
Stage
§ L'emploi de cet outil n'est pas décrit en détail dans ce support de
cours, il faut se référer à la documentation disponible. Son utilisation
est cependant assez intuitive, pour autant que les éléments affichés
soient connus (notamment les caractéristiques et propriétés principales
des conteneurs et des composants).
Scene
BorderPane
Malgré l'outil graphique, on n'échappe donc pas à une compréhension
minimale des API (composants, conteneurs, propriétés, …).
IHM-1 – FX07
– Jacques BAPST
5
SceneBuilder [2]
TOP
BOTTOM
Label
Button
IHM-1 – FX07
– Jacques BAPST
7
Say Hello [2]
§ Aperçu de l'écran principal :
§ Variante procédurale avec les API JavaFX :
BorderPane root = new BorderPane();
root.setPrefWidth(250);
root.setPrefHeight(80);
root.setStyle("-fx-background-color: #FFFCAA");
root.setPadding(new Insets(10, 5, 10, 5));
Label lblTitle = new Label();
lblTitle.setText("Titre");
lblTitle.setTextFill(Color.web("#0022CC"));
lblTitle.setFont(Font.font("SansSerif", FontWeight.BOLD, 20));
BorderPane.setAlignment(lblTitle, Pos.CENTER);
root.setTop(lblTitle);
Button btnSayHello = new Button();
btnSayHello.setText("Say Hello");
BorderPane.setAlignment(btnSayHello, Pos.CENTER);
root.setBottom(btnSayHello);
btnSayHello.setOnAction(event -> {
lblTitle.setText("H e l l o
!");
lblTitle.setTextFill(Color.FUCHSIA);
});
IHM-1 – FX07
– Jacques BAPST
6
IHM-1 – FX07
– Jacques BAPST
8
Say Hello [3]
Say Hello [5]
§ La même application a été créée de manière déclarative et la vue est
donc décrite dans un fichier FXML qui a été créé avec SceneBuilder.
§ Le fichier XML comporte tout d'abord une partie déclarative (en-tête
et importations nécessaires à la création dynamique de l'objet Java).
§ Ensuite on trouve la description de la structure de l'interface (graphe
de scène avec conteneurs, composants et propriétés).
§ La méthode start() de la classe principale peut charger le fichier
FXML en invoquant la méthode statique FXMLLoader.load(url) qui
prend en paramètre l'URL de la ressource à charger.
§ La méthode getResource(name) de la classe Class permet de
trouver (par le classloader) l'URL d'une ressource à partir de son nom.
• Référence relative au package courant par défaut ("views/Login.fxml")
• Référence absolue si '/' initial dans le nom ("/resources/log/Error.fxml")
(attention: le caractère '/' est aussi utilisé même s'il s'agit de packages et sous-packages)
<?xml version="1.0" encoding="UTF-8"?>
<?import
<?import
<?import
<?import
<?import
javafx.geometry.*?>
javafx.scene.text.*?>
javafx.scene.control.*?>
java.lang.*?>
javafx.scene.layout.*?>
<BorderPane
. . .
. . .
public void start(Stage primaryStage) throws Exception {
//--- Chargement du fichier FXML
BorderPane root = FXMLLoader.load(getClass().getResource("SayHello.fxml"));
Racine du graphe
de scène.
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.setTitle("SayHello FXML");
primaryStage.show();
>
Conteneurs et composants
du graphe de scène.
}
</BorderPane>
IHM-1 – FX07
– Jacques BAPST
9
IHM-1 – FX07
Say Hello [4]
11
Say Hello [6]
§ Partie principale du fichier FXML créé avec SceneBuilder
<BorderPane prefHeight="80.0" prefWidth="250.0"
style="-fx-background-color: #FFFCAA;"
xmlns=http://javafx.com/javafx/8 xmlns:fx=http://javafx.com/fxml/1
fx:controller="supp_cours.chap07.SayHelloController">
<top>
<Label id="title" fx:id="title" text="Titre" textFill="#0022cc"
BorderPane.alignment="CENTER">
<font>
<Font name="SansSerif Bold" size="20.0" />
</font>
</Label>
</top>
<bottom>
<Button fx:id="btnHello" onAction="#handleButtonAction" text="Say Hello"
BorderPane.alignment="CENTER" />
</bottom>
<padding>
<Insets bottom="10.0" left="5.0" right="5.0" top="10.0" />
</padding>
</BorderPane>
IHM-1 – FX07
– Jacques BAPST
– Jacques BAPST
10
§ Dans la variante déclarative, une classe séparée joue le rôle de
contrôleur pour traiter l'action du clic sur le bouton.
§ Le principe de fonctionnement et le rôle des annotations seront
expliqués dans les pages qui suivent.
public class SayHelloController {
@FXML
private Button btnHello;
// Object injected by FXMLLoader (fx:id="btnHello")
@FXML
private Label
// Object injected by FXMLLoader (fx:id="title")
title;
@FXML
private void handleButtonAction(ActionEvent event) {
title.setText("H e l l o
!");
title.setTextFill(Color.FUCHSIA);
}
}
IHM-1 – FX07
– Jacques BAPST
12
Say Hello [7]
Interprétation des fichiers FXML [2]
§ Les deux variantes de l'application (avec interface créée de manière
procédurale et déclarative) fonctionnent de manière strictement
identique.
• Un clic sur le bouton modifie le texte et la couleur du composant Label.
Variante procédurale
§ Pour les propriétés qui ne peuvent pas facilement être représentées
par une chaîne de caractères, un élément est imbriqué (plutôt que de
déclarer des attributs).
Par exemple, si l'on considère l'élément
<Label id="title" fx:id="title" text="Titre" textFill="#0022cc"
BorderPane.alignment="CENTER">
<font>
<Font name="SansSerif Bold" size="20.0" />
</font>
</Label>
Variante déclarative
on constate que la propriété Font est codée comme un élément
imbriqué dans l'élément Label.
§ Pour les propriétés de type liste (par exemple children), les éléments
de la liste sont simplement imbriqués et répétés dans l'élément
représentant la liste (par exemple, les composants enfants seront listés
entre les balises <children> et </children>).
IHM-1 – FX07
– Jacques BAPST
13
IHM-1 – FX07
– Jacques BAPST
Interprétation des fichiers FXML [1]
Liens FXML « programme [1]
§ Lors du chargement du fichier FXML, son contenu est interprété et
des objets Java correspondants sont créés.
§ Le lien entre les composants décrits dans le fichier FXML et le
programme est établi par les attributs fx:id :
§ Par exemple, l'élément
15
<Label id="title" fx:id="title" text="Titre" textFill="#0022cc" ...
<BorderPane prefHeight="80.0" prefWidth="250.0" . . .
§ L'attribut fx:id fonctionne en lien avec l'annotation @FXML que l'on
peut utiliser dans les contrôleurs, et qui va indiquer au système que
le composant avec le nom fx:id pourra être injecté dans l'objet
correspondant de la classe contrôleur.
sera interprété comme
BorderPane rootPane = new BorderPane();
rootPane.setPrefHeight(80.0);
rootPane.setPrefWidth(250.0);
. . .
public class SayHelloController {
§ Quand un attribut commence par le nom d'une classe suivi d'un
point et d'un identificateur, par exemple
@FXML
private Button btnHello;
<TextField GridPane.columnIndex="3" . . .
l'attribut sera interprété comme une invocation de méthode statique
@FXML
private Label
title;
// fx:id="title"
// Object injected by FXMLLoader
TextField tfd = new TextField();
GridPane.setColumnIndex(tfd, 3);
. . .
IHM-1 – FX07
– Jacques BAPST
14
IHM-1 – FX07
– Jacques BAPST
16
Liens FXML « programme [2]
Liens FXML « programme [4]
§ La classe qui joue le rôle de contrôleur pour une interface déclarée
en FXML doit être annoncée dans l'élément racine, en utilisant
l'attribut fx:controller :
§ Dans les classes qui agissent comme "contrôleurs", on peut définir
une méthode initialize() (qui doit être annotée avec @FXML) pour
effectuer certaines initialisations.
<BorderPane prefHeight="80.0" prefWidth="250.0"
style="-fx-background-color: #FFFCAA;"
xmlns=http://javafx.com/javafx/8
xmlns:fx=http://javafx.com/fxml/1
fx:controller="supp_cours.chap07.SayHelloController">
. . .
_________________________________________________________________________________________________________
§ Cette méthode est automatiquement invoquée après le chargement
du fichier FXML.
§ Elle peut être utile pour initialiser certains composants, en faisant
par exemple appel au modèle.
. . .
@FXML
private void initialize() {
cbbCountry.getItems().addAll("Allemagne", "Angleterre", "Belgique",
"Espagne",
"France",
"Italie",
"Pays-Bas", "Portugal",
"Suisse");
Attention : Les attributs qui définissent les namespaces xmlns=… ainsi que
xmlns:fx=… sont utilisés par l'environnement JavaFX (FXMLLoader,
SceneBuilder, etc.).
Ils ne doivent donc pas être modifiés !
lstProducts.getItems().addAll(model.getProducts());
. . .
}
IHM-1 – FX07
– Jacques BAPST
17
IHM-1 – FX07
– Jacques BAPST
19
Liens FXML « programme [3]
Liens FXML « programme [5]
§ Pour les composants actifs déclarés dans une interface en FXML, on
peut indiquer la méthode du contrôleur qui doit être invoquée en
utilisant l'attribut fx:onEvent="#methodName" :
§ Dans l'éditeur SceneBuilder, ces différents attributs de liaison avec
le programme doivent être indiqués dans les champs prévus.
<Button fx:id="btnHello" onAction="#handleButtonAction"
text="Say Hello" BorderPane.alignment="CENTER" />
• Attribut fx:id et attribut fx:controller
. . .
§ Dans la classe contrôleur, ces méthodes devront (comme les
composants associés) être annotées avec @FXML.
. . .
@FXML
private void handleButtonAction(ActionEvent event) {
title.setText("H e l l o
!");
title.setTextFill(Color.FUCHSIA);
}
• Attribut fx:on…
IHM-1 – FX07
– Jacques BAPST
18
IHM-1 – FX07
Le bloc Controller est situé en
bas, à gauche de l'écran principal.
– Jacques BAPST
20
Liens FXML « programme [6]
Liens FXML « programme [8]
§ L'attribut id ne doit pas être confondu avec l'attribut fx:id.
§ Si le contrôleur d'une interface déclarée avec FXML ne possède pas
de constructeur par défaut, il faut créer le contrôleur et l'associer
dans le code du programme, avant le chargement du fichier FXML.
<Label id="title" fx:id="title" text="Titre" textFill="#0022cc" ...
§ L'attribut id définit un sélecteur CSS de type Id qui permet d'associer
des règles de style aux composants portant cet Id.
• Exemple dans un fichier CSS :
. . .
<BorderPane stylesheets="@SayHello.css" . . . >
§ Dans ce cas, il n'est pas nécessaire de déclarer le contrôleur dans le
fichier FXML (fx:controller="...").
§ L'éditeur SceneBuilder permet (dans l'inspecteur de propriétés) de
créer l'attribut id et de définir le fichier CSS associé (Stylesheets).
– Jacques BAPST
21
Liens FXML « programme [7]
§ Il est possible d'accéder au contrôleur associé au fichier FXML en
créant un chargeur (loader) pour ce fichier (plutôt que d'utiliser la
méthode statique FXMLLoader.load()) .
§ Cela peut être utile pour avoir accès au contrôleur, par exemple pour
lui communiquer la référence du modèle de l'application :
public void start(Stage primaryStage) throws Exception {
//--- Chargement du fichier FXML et recherche du contrôleur associé
FXMLLoader loader = new FXMLLoader(getClass().getResource("SayHello.fxml"));
BorderPane
root = loader.load();
SayHelloController ctrl = loader.getController();
ctrl.setModel(model);
Scene scene = new Scene(root);
. . .
§ Dans ce cas, on utilisera la méthode d'instance load() pour charger
le fichier FXML et obtenir la référence de la racine du graphe de scène.
IHM-1 – FX07
– Jacques BAPST
public void start(Stage primaryStage) throws Exception {
//--- Chargement du fichier FXML et association du contrôleur
FXMLLoader loader = new FXMLLoader(getClass().getResource("SayHello.fxml"));
loader.setController(new SayHelloController("a param"));
BorderPane root = loader.load();
#title {
-fx-font-size: 24pt;
}
§ Dans le fichier FXML, une feuille de style (fichier CSS) peut être
associé à un composant avec l'attribut stylesheets="@CSS_File"
IHM-1 – FX07
§ Le code suivant illustre la manière de le faire :
22
IHM-1 – FX07
– Jacques BAPST
23
Menu [1]
§ Les menus sont des éléments de l'interface permettant à l'utilisateur
de choisir des options qui pourront déclencher des actions et/ou
changer l'état de certaines propriétés de l'application.
Cours IHM-1
JavaFX
§ Le principe du menu est (comme au restaurant) que l'utilisateur
puisse voir et parcourir la liste des options avant de se décider.
8 - Menus
Choix / Sélection
§ Dans les applications, les menus peuvent prendre différentes
formes. Parmi les plus classiques, on trouve :
• Les menu déroulants (drop-down menu)
ð
ð
Des en-têtes de menus sont placés dans un conteneur sous forme de barre
Un clic sur ces en-têtes "déroule" le menu et fait apparaître, dans une
fenêtre popup, les options de ce menu.
• Les menu contextuels (popup menu)
Jacques BAPST
Menus affichés en réaction à un événement, généralement une action de la
souris (clic-droit), l'activation d'une touche ou un geste.
ð Les options du menu qui sont affichées dépendent de l'endroit où l'on a
cliqué (contexte).
ð
[email protected]
IHM-1 – FX08
– Jacques BAPST
3
Menu [2]
§ Les options des menus peuvent elles-mêmes ouvrir d'autres menus.
On parle alors de sous-menus qui peuvent s'ouvrir en cascade (on
peut avoir plusieurs niveaux de sous-menus).
Menus déroulants
Menus contextuels
Mnémoniques / Accélérateurs
IHM-1 – FX08
– Jacques BAPST
§ Dans la librairie JavaFX, un certain nombre de composants sont
dédiés aux menus et sont utilisés pour les construire :
•
•
•
•
•
•
•
•
2
MenuBar
Menu
MenuItem
CheckMenuItem
RadioMenuItem
CustomMenuItem
SeparatorMenuItem
Contextmenu
IHM-1 – FX08
– Jacques BAPST
4
Menu [3]
Barre de menu [1]
§ Les différents composants qui interviennent dans la gestion des
menus déroulants sont illustrés dans l'exemple suivant :
§ La barre de menus, représentée par le composant MenuBar, est un
conteneur permettant de rassembler les en-têtes des menus.
§ Le composant MenuBar doit être placé dans un conteneur de
l'interface (par exemple en haut d'un BorderPane).
Menu
§ La création d'une barre de menus s'effectue à l'aide des instructions
suivantes :
MenuBar
CheckMenuItem
SeparatorMenuItem
Menu
• Création de la barre de menus :
MenuBar mBar = new MenuBar();
• L'ajout des menus (composants de type Menu) dans la barre peut se faire
de manière individuelle (en séquence) ou collective :
MenuItem
mBar.getMenus().add(mnuFile);
mBar.getMenus().addAll(mnuFile, mnuOptions, mnuHelp);
RadioMenuItem
§ La propriété useSystemMenuBar permet d'indiquer que le mécanisme
standard de menu de la plateforme cible doit être utilisé (par exemple
sur MacOS).
IHM-1 – FX08
– Jacques BAPST
5
IHM-1 – FX08
– Jacques BAPST
Menu [4]
Menu déroulant [1]
§ Pour les menus contextuels, ce sont pratiquement les mêmes
composants qui sont utilisés à la simple différence que :
§ Le composant Menu représente un conteneur d'éléments de menu
(options) et peut se présenter "enroulé" (en-tête) ou "déroulé"
(fenêtre popup) lorsqu'on clique dessus.
• Le composant MenuBar n'est plus nécessaire.
• A la place, c'est le composant ContextMenu qui est utilisé pour
rassembler les différentes options du menu contextuel.
7
§ Il peut être ajouté à une barre de menus (MenuBar) ou être inséré
comme sous-menu dans un autre composant de type Menu.
§ L'affichage du composant sera déclenché par une action de
l'utilisateur (en général un clic-droit sur un conteneur ou composant).
§ Lorsque le composant Menu représente un sous-menu, il est
accompagné d'un indicateur visuel (généralement une flèche dirigée
vers la droite) qui le distingue d'un élément terminal (option de menu).
§ Un menu contextuel
peut également
contenir des sousmenus en cascade.
§ Création et alimentation d'un objet de type Menu :
• Création d'un menu :
Menu mnuFile = new Menu("File");
• L'ajout des options de menus (MenuItem, CheckMenuItem, …) dans le menu
peut se faire de manière individuelle (en séquence) ou collective :
mnuFile.getItems().add(mniSave);
mnuFile.getItems().addAll(mniSave, mniSaveAs, mniQuit);
IHM-1 – FX08
– Jacques BAPST
6
IHM-1 – FX08
– Jacques BAPST
8
Menu déroulant [2]
Options de menu [2]
§ Un sous-menu est donc simplement créé en ajoutant un composant
de type Menu dans un autre composant du même type :
§ Pour le traitement de l'action déclenchée par le composant
MenuItem on peut utiliser la méthode utilitaire setOnAction(…).
• Création d'un menu et d'un sous-menu :
§ Exemple :
Menu mnuOptions = new Menu("Options");
Menu mnuPrefs
= new Menu("Preferences");
. . .
//--- Traitement de l'action des options de menu
mniQuit.setOnAction(event -> {
Platform.exit();
});
• L'ajout du menu comme sous-menu :
mnuOptions.getItems().add(mnuPrefs);
§ Le nombre de niveaux d'imbrication des sous-menus n'est pas limité
mais on ne dépasse généralement pas trois niveaux (selon le contexte
applicatif, des exceptions sont possibles).
IHM-1 – FX08
9
– Jacques BAPST
mniAbout.setOnAction(event -> {
System.out.println("About MenuItem Activated");
});
. . .
IHM-1 – FX08
– Jacques BAPST
Options de menu [1]
Options de menu [3]
§ Un menu déroulant ou contextuel peut contenir différents éléments
(appelés options de menu) permettant à l'utilisateur de déclencher
une action ou d'activer/désactiver l'élément.
§ Comme option de menu, on peut aussi utiliser le composant
CheckMenuItem qui agit comme une case à cocher placée dans un
menu déroulant ou contextuel (l'aspect visuel dépend du thème).
§ L'option de base est représentée par le composant MenuItem qui
agit, du point de vue de l'utilisateur, comme un bouton.
§ La propriété selected indique si l'option est sélectionnée ou non.
§ Lors de la création d'une option de menu, on peut lui assigner un
texte et optionnellement un composant complémentaire (graphic)
qui ne devrait pas dépasser 16x16 pixels.
• Création d'une option de menu :
MenuItem
mniSave
= new MenuItem("Save");
ImageView
image
= new ImageView(. . .);
MenuItem
mniPrefs
= new MenuItem("Preferences", image);
11
§ La propriété graphic permet d'ajouter au libellé un composant
complémentaire (généralement une icône ou graphique de petite taille).
• Création d'une option de menu de type case à cocher :
CheckMenuItem
cmiSound
= new CheckMenuItem("Sound");
ImageView
image
= new ImageView(. . .);
CheckMenuItem
cmiNotify = new CheckMenuItem("Notify", image);
• Ajout d'un graphique :
cmiSound.setGraphic(new Rectangle(8, 8, Color.GREEN));
• Ajout d'un graphique (icône) :
mniSave.setGraphic(new Circle(0, 0, 5, Color.RED));
IHM-1 – FX08
– Jacques BAPST
10
IHM-1 – FX08
– Jacques BAPST
12
Options de menu [4]
Options de menu [6]
§ Comme pour le composant MenuItem, on peut traiter l'événement
du composant CheckMenuItem en invoquant la méthode utilitaire
setOnAction(…).
§ Le composant RadioMenuItem ne devrait jamais être utilisé seul
mais faire partie d'un groupe (comme pour les boutons radios).
§ Parfois, on ne réagit pas directement au clic sur une telle option
mais on prend en compte l'état (sélectionné ou non-sélectionné)
dans le traitement d'autres événements (ceux déclenchés par des
boutons ou des options de menu de type MenuItem par exemple).
§ Il est également possible de lier (binding) la propriété selected de
ce composant à une propriété du modèle. Ainsi, le modèle de
l'application reflète de manière synchrone l'état de cette propriété
sans qu'il soit nécessaire de gérer des événements lors des
changements dans la sélection.
IHM-1 – FX08
– Jacques BAPST
13
§ Toutes les options qui font partie du même groupe doivent être
placées dans un objet commun de type ToggleGroup (toggleGroup
est une propriété du composant RadioMenuItem).
ToggleGroup tgGroup = new ToggleGroup();
rmiDock.setToggleGroup(tgGroup);
rmiFloat.setToggleGroup(tgGroup);
§ Pour le traitement des événements, toutes les indications et
remarques formulées pour le composant CheckMenuItem (voir pages
précédentes) s'appliquent également au composant RadioMenuItem.
IHM-1 – FX08
– Jacques BAPST
Options de menu [5]
Options de menu [7]
§ Le composant RadioMenuItem qui est une sous-classe de MenuItem
peut également être utilisé comme option dans un menu. Il agit
comme un bouton radio placé dans un menu déroulant et permet la
sélection d'une option parmi plusieurs.
§ Le composant CustomMenuItem permet de traiter n'importe quel
nœud (Node) d'un graphe de scène comme un élément de menu.
§ Pour avoir ce comportement de sélection mutuellement exclusive, le
composant doit être placé dans un ToggleGroup (comme c'est le cas
pour le RadioButton).
§ La propriété selected indique si l'option est sélectionnée ou non.
• Création d'une option de menu de type radio :
RadioMenuItem
rmiDock
= new RadioMenuItem("Docking");
image
= new ImageView(. . .);
RadioMenuItem
rmiFloat = new RadioMenuItem("Floating", image);
IHM-1 – FX08
– Jacques BAPST
§ Exemple d'un curseur (Slider) placé dans un menu :
Slider slrSpeed = new Slider(0, 100, 30);
CustomMenuItem cmiSlider = new CustomMenuItem(slrSpeed);
cmiSlider.setHideOnClick(false);
mnuOptions.getItems().add(cmiSlider);
§ La propriété hideOnClick permet d'indiquer
si le composant et toutes les autres options
visibles doivent être masquées lorsqu'on
clique sur cet élément (true par défaut).
§ La propriété graphic permet d'ajouter au libellé un composant
complémentaire (généralement un graphique de petite taille).
ImageView
15
§ Les événements du composant inséré dans
le menu doivent naturellement être gérés
si nécessaire.
14
IHM-1 – FX08
– Jacques BAPST
16
Options de menu [8]
Menu contextuel [2]
§ Il est possible de grouper visuellement les options de menu en
séparant les différents groupes d'options par une ligne horizontale.
§ L'association des menus contextuels aux composants peut se faire
de deux manières.
• Pour les composants qui sont des sous-classes de Control, le plus
simple est d'utiliser la propriété contextMenu.
§ Le composant SeparatorMenuItem (qui est une sous-classe de
CustomMenuItem) permet de créer un tel séparateur.
§ Pour chaque séparateur, il faut créer un nouvel objet (on ne peut pas
ajouter plusieurs fois un même séparateur).
• Pour les autres composants et notamment les conteneurs (sous-classes
de Pane), il faut gérer l'événement provoqué par le clic-droit de la souris
et utiliser la méthode show() pour afficher le menu (à la position du clic).
private SeparatorMenuItem sep1
= new SeparatorMenuItem();
private SeparatorMenuItem sep1
= new SeparatorMenuItem();
mnuPrefs.getItems().addAll(mniFont, mniSize, sep1,
rmiDock, rmiFloat, sep2,
. . .
);
IHM-1 – FX08
– Jacques BAPST
ContextMenu ctxMenuIm = new ContextMenu();
ctxMenuIm.getItems().addAll(mniCopy, mniSave, mniResize);
lblImage.setContextMenu(ctxMenuIm);
root.setOnMousePressed(new EventHandler<MouseEvent>() {
@Override
public void handle(MouseEvent ev) {
if (ev.isSecondaryButtonDown()) {
ctxMenuIm.show(root, ev.getScreenX(), ev.getScreenY());
}
}
});
17
IHM-1 – FX08
– Jacques BAPST
Menu contextuel [1]
Raccourcis clavier
§ Les menus contextuels (popup menu) se construisent de la même
manière que les menus déroulants mais les différents options du
menu sont assemblées dans un composant ContextMenu (au lieu de
créer des en-têtes de menus et de les ajouter dans un MenuBar).
§ Il est souvent souhaitable que certaines actions que l'on peut
déclencher avec la souris puissent être également déclenchées à
l'aide du clavier.
§ Instructions pour gérer un menu contextuel :
• Création du menu contextuel :
ContextMenu ctxMenuIm
§ C'est le rôle des raccourcis clavier qui existent sous deux formes
principales :
• Les mnémoniques
= new ContextMenu();
ctxMenuIm.getItems().add(mniWeb);
ctxMenuIm.getItems().addAll(mniCopy, mniSave, mniResize);
§ Des sous-menus peuvent également être créés dans un menu
contextuel en imbriquant des composants de type Menu (de la même
manière que pour les menus déroulants).
– Jacques BAPST
Activation du composant en pressant sur une touche spécifique qui dépend
de la plateforme (touche Alt sur Windows) associée à un autre caractère
(généralement alphanumérique).
ð Les mnémoniques sont associés à des composants qui possède un libellé et
le caractère associé au mnémonique est généralement souligné.
ð
• L'ajout des options de menus peut se faire de manière individuelle
(en séquence) ou collective :
IHM-1 – FX08
19
18
• Les accélérateurs
ð
Activation de l'action associée à une option de menu par une combinaison
unique de touches du clavier.
IHM-1 – FX08
– Jacques BAPST
20
Mnémonique
Accélérateur [2]
§ Des mnémoniques peuvent être associés à tous les composants
possédant un libellé (sous-classes de Labeled) ainsi qu'aux menus et
aux différentes options de menus.
§ Pour les touches spéciales (Enter, Home, Page_Up, …), la classe
KeyCode définit des constantes énumérées représentant les
différentes touches du clavier. Le nom de ces constantes peut
généralement être utilisé dans la chaîne de caractères passée à la
méthode keyCombination().
§ Pour ces composants, la propriété booléenne mnemonicParsing
indique si le caractère souligné '_' dans le libellé du composant
doit être considéré comme l'annonce d'un mnémonique pour le
caractère suivant.
Menu
mnuFile
Menu
mnuOptions = new Menu("_Options");
Button btnOk
§ La classe KeyCodeCombination peut
également être utilisée pour créer des
combinaisons de touches (voir exemple).
= new Menu("_File");
§ Exemples de création d'accélérateurs
sur des options de menu :
= new Button("O_K");
mniSave.setAccelerator(KeyCombination.keyCombination("Ctrl+S"));
• La propriété mnemonicParsing est à true par
défaut pour la plupart des composants.
mniAbout.setAccelerator(KeyCombination.keyCombination("Shift+Ctrl+A"));
mniQuit.setAccelerator(KeyCombination.keyCombination("Ctrl+Home"));
mnuOpt.setAccelerator(KeyCombination.keyCombination("Alt+Shift+Ctrl+O"));
mniSize.setAccelerator(new KeyCodeCombination(
KeyCode.S, KeyCombination.ALT_DOWN, KeyCombination.SHIFT_DOWN));
IHM-1 – FX08
– Jacques BAPST
21
IHM-1 – FX08
– Jacques BAPST
23
Accélérateur [1]
§ Des accélérateurs peuvent être associés à tous les composants de
type Menu (c'est rarement utile) et aux différentes options de menus
MenuItem, CheckMenuItem, …
§ Pour ces composants, la propriété accelerator, qui est de type
KeyCombination indique la combinaison de touches qui permet de
déclencher l'action associée.
§ Il existe différentes manières de créer des objets de type
KeyCombination. Une des plus simples est d'utiliser la méthode
statique keyCombination("key-pattern") qui prend en paramètre
une chaîne de caractères qui sera interprétée.
MenuButton / SplitMenuButton
ChoiceBox / ComboBox
ListView / Spinner
• Cette chaîne de caractères comprendra un ou plusieurs modificateurs
et un caractère séparés par le symbole plus '+'
• Modificateurs possibles : Shift, Ctrl, Alt, Meta, Shortcut
• Exemples : "Shift+A", "Ctrl+S", "Alt+Ctrl+M"
IHM-1 – FX08
– Jacques BAPST
22
IHM-1 – FX08
– Jacques BAPST
24
MenuButton
ChoiceBox [1]
§ Le composant MenuButton est un bouton qui affiche un menu popup
lorsqu'il est cliqué (à la manière d'un menu contextuel).
§ Le composant ChoiceBox permet de présenter à l'utilisateur une
liste d'éléments dans laquelle il peut en sélectionner un.
§ C'est une sous-classe de ButtonBase
et son API est très proche de celle du
composant Menu.
§ Le composant se présente au départ sous la forme d'un bouton et,
lorsqu'on clique dessus, les éléments sont affichés sous la forme
d'une liste déroulante.
§ La propriété popupSide permet de
définir de quel côté doit s'ouvrir le
menu popup.
§ L'élément actuellement sélectionné est affiché en libellé lorsque la
liste est fermée (enroulée).
§ Ce composant est à réserver pour la sélection de listes relativement
courtes car il n'y a pas de scrolling possible.
§ Exemple (extrait du code) :
§ Par défaut, il n'y a pas d'élément sélectionné au départ.
MenuButton mbnOptions = new MenuButton("Options");
mnuSubOpt.getItems().addAll(mniSkin, mniSize, mniColor); // Sous-menu
mbnOptions.setPopupSide(Side.LEFT);
bnOptions.getItems().addAll(mniSave, mniQuit, mnuSubOpt, mniFont);
IHM-1 – FX08
– Jacques BAPST
25
§ Le type du composant est générique ChoiceBox<T>, T étant le type
des éléments de la liste.
IHM-1 – FX08
– Jacques BAPST
SplitMenuButton
ChoiceBox [2]
§ Le composant SplitMenuButton est une sorte de combinaison du
composant Button et du composant
MenuButton (dont il est une sous-classe).
§ Quelques propriétés du composant ChoiceBox :
Liste des éléments à afficher. Un objet de type
ObservableList<T> que l'on peut également passer au
items
constructeur. La liste des éléments peut être modifiée
dynamiquement (ajout, suppression, …).
Modèle de sélection des éléments de la liste.
Type SingleSelectionModel<T>.
selectionModel
On ne change généralement pas le modèle de sélection par
défaut.
value
Valeur de l'élément sélectionné. Type <T>.
showing
Booléen qui indique si la liste des éléments est affichée ou non.
Convertisseur qui indique comment l'élément est représenté.
Ce convertisseur (StringConverter<T>) est utilisé pour convertir
converter
les éléments de type T en String (et inversement). Par défaut,
c'est la méthode toString() de l'élément qui est utilisée.
§ La zone du bouton est divisée en 2 parties :
• Une zone avec un libellé qui agit comme un
bouton ordinaire en déclenchant une action
lorsqu'on clique sur cette partie (code
associé à la méthode setOnAction(…)).
• Une zone de menu qui peut être activée en
cliquant dessus ou en utilisant les touches
"curseurs" du clavier et qui fonctionne
comme le composant MenuButton en
ouvrant un menu dans une fenêtre popup.
§ La dernière sélection qui a été effectuée
dans le menu n'est pas automatiquement
associée à l'action du bouton.
IHM-1 – FX08
– Jacques BAPST
27
26
IHM-1 – FX08
– Jacques BAPST
28
ChoiceBox [3]
ComboBox [1]
§ Exemple avec une liste de chaînes de caractères (choix d'une langue).
§ Le composant ComboBox<T> est assez similaire au composant
ChoiceBox et permet de présenter à l'utilisateur une liste d'éléments
(de type T) dans laquelle il pourra en sélectionner un.
ChoiceBox<String> cbxLang = new ChoiceBox<String>();
cbxLang.getItems().addAll("Deutsch", "English", "Français", "Italiano");
cbxLang.getSelectionModel().select(1); // ou cbxLang.setValue("English");
root.getChildren().add(cbxLang);
§ Une des différences réside dans le fait que ComboBox permet de
limiter le nombre de choix affichés et offre automatiquement une
barre de défilement (ascenseur) pour naviguer dans la liste.
§ Une autre différence est qu'un ComboBox peut être éditable
(l'utilisateur peut saisir une valeur qui ne figure pas dans la liste).
§ ComboBox est une sous-classe de ComboBoxBase qui offre des
propriétés et fonctionnalités de base au composant.
§ La propriété onAction permet d'enregistrer un gestionnaire
d'événement qui sera exécuté lorsque la valeur sélectionnée change.
§ La fonction de saisie automatique (autocomplétion) n'est pas
disponible par défaut et doit être codée si elle est souhaitée.
IHM-1 – FX08
– Jacques BAPST
29
IHM-1 – FX08
– Jacques BAPST
ChoiceBox [4]
ComboBox [2]
§ Si l'on souhaite effectuer une action lors d'un changement de
sélection, il faut enregistrer un gestionnaire d'événement sur le
modèle de sélection (propriété selectedItem ou selectedIndex).
§ Quelques propriétés du composant ComboBox :
cbxLang.getSelectionModel().selectedItemProperty()
.addListener(new ChangeListener<String>() {
@Override
public void changed(ObservableValue<? extends String> ov,
String s1, String s2)
{
System.out.println(s1+", "+s2); // Display old and new values
System.out.println(cbxLang.getSelectionModel().getSelectedIndex());
}
});
• Identique mais avec une expression lambda :
cbxLang.getSelectionModel().selectedItemProperty()
.addListener((ov, s1, s2) -> {
System.out.println(s1+", "+s2); // Display old and new values
System.out.println(cbxLang.getSelectionModel().getSelectedIndex());
});
IHM-1 – FX08
– Jacques BAPST
30
31
Liste des éléments à afficher. Un objet de type
ObservableList<T> que l'on peut également passer au
items
constructeur. La liste des éléments peut être modifiée
dynamiquement (ajout, suppression, …).
Modèle de sélection des éléments SingleSelectionModel<T>
selectionModel
(un seul élément peut être sélectionné).
value
Valeur de l'élément sélectionné. Type <T>.
showing
Booléen qui indique si la liste des éléments est affichée ou non.
Convertisseur qui indique comment l'élément est représenté et,
s'il est éditable, comment traduire la chaîne de caractères en
converter
objet de type T.
Ce convertisseur (StringConverter<T>) est donc utilisé pour
convertir les éléments de type T en String et inversement.
Booléen qui indique si le ComboBox est éditable, c'est-à-dire que
editable
l'utilisateur peut saisir une valeur qui n'est pas dans la liste des
éléments.
IHM-1 – FX08
– Jacques BAPST
32
ComboBox [3]
ListView [1]
Nombre maximal de lignes affichées dans la liste déroulante
visibleRowCount (Integer). Si ce nombre est dépassé, une barre de défilement
permet à l'utilisateur de parcourir la liste (scrolling)
Traitement de l'événement déclenché lors du changement de la
onAction
valeur sélectionnée (value).
onShowing
onShown
onHiding
onHidden
promptText
placeholder
Traitement des événements associés à l'ouverture et à la
fermeture du menu déroulant (juste avant et juste après
l'ouverture et la fermeture de la fenêtre popup).
Texte affiché si le composant n'est pas éditable et qu'aucun
élément n'est sélectionné.
Par exemple à l'état initial ou après annulation de la sélection
(setValue(null);).
Composant (de type Node) à afficher si la liste des éléments est
vide.
IHM-1 – FX08
– Jacques BAPST
33
ComboBox [4]
§ Le composant ListView<T> permet d'afficher un ensemble
d'éléments (de type T) sous la forme d'une liste scrollable.
§ Les éléments de la liste peuvent être affichés verticalement (par
défaut) ou horizontalement.
§ L'utilisateur peut sélectionner les éléments (sélection simple par
défaut, mais sélection multiple possible) ou interagir avec eux.
§ Comme pour les ComboBox, les éléments sont enregistrés en interne
dans une collection de type ObservableList (modèle du composant)
qui informe le composant ListView des changements intervenus.
§ Les éléments de la liste peuvent potentiellement être édités par
l'utilisateur (pour cela, la propriété cellFactory doit avoir été redéfinie).
§ Le nombre d'éléments affichés dépend de la taille préférée du
composant (prefWidth, prefHeight). Si nécessaire des barres de
défilement (ascenseurs) apparaîtront pour permettre le scrolling.
IHM-1 – FX08
– Jacques BAPST
35
ListView [2]
§ Le peuplement de la liste peut être effectué en ajoutant les
éléments avec les méthodes add() ou addAll() de la propriété
items (comme dans les exemples pour ChoiceBox et ComboBox).
ComboBox<String> cbbCountry = new ComboBox<String>();
cbbCountry.getItems().addAll("Allemagne", "Angleterre", "Belgique",
"Espagne",
"France",
"Italie",
"Pays-Bas", "Portugal",
"Suisse");
§ Une autre manière de faire (courante) est d'utiliser des méthodes
statiques de la classe FXCollections pour créer des objets de type
ObservableList<T> qui seront assignés au composant ListView
avec la méthode setItems() ou alors passés en paramètre à son
constructeur.
cbbCountry.setPromptText("Select a Country...");
cbbCountry.setVisibleRowCount(5);
// Max 5 éléments visibles
cbbCountry.setOnAction(event -> {
System.out.println(cbbCountry.getValue());
});
ListView<String> livCountry = new ListView<String>();
ObservableList<String> countries = FXCollections.observableArrayList(
"Allemagne", "Angleterre", "Belgique",
"Espagne",
"France",
"Italie",
"Pays-Bas", "Portugal",
"Suisse"
);
livCountry.setItems(countries);
IHM-1 – FX08
– Jacques BAPST
34
IHM-1 – FX08
– Jacques BAPST
36
ListView [3]
ListView [5]
§ Quelques propriétés du composant ListView :
§ Méthodes pour gérer la visibilité des éléments de ListView :
Liste des éléments à afficher dans la liste. Un objet de type
ObservableList<T> que l'on peut également passer au
items
constructeur. La liste des éléments peut être modifiée
dynamiquement (ajout, suppression, …).
Modèle de sélection des éléments MultipleSelectionModel<T>.
selectionModel - Unique : setSelectionMode(SelectionMode.SINGLE)
- Multiple : setSelectionMode(SelectionMode.MULTIPLE)
orientation
Orientation de la liste. Type Orientation.
Booléen qui indique si la ListView est éditable. Cela implique que
editable
les éléments (ListCell) doivent également être éditables ce qui
nécessite de redéfinir la propriété cellFactory.
Composant (de type Node) à afficher si la liste des éléments est
placeholder
vide.
Fait défiler la liste (scroll), si nécessaire, pour que l'élément
correspondant à l'indice passé en paramètre soit visible.
Fait défiler la liste (scroll), si nécessaire, pour que l'élément
scrollTo(object)
correspondant à l'objet passé en paramètre soit visible.
scrollTo(index)
§ Les éléments de la liste peuvent être quelconques et sont
représentés par des objets de type ListCell.
§ Il existe quelques sous-classes prédéfinies de ListCell qui
permettent de créer facilement des listes éditables dont les
éléments correspondent à des composants connus.
• CheckBoxListCell
: Éléments de type CheckBox
• ChoiceBoxListCell : Éléments de type ChoiceBox
: Éléments de type ComboBox
• TextFieldListCell : Éléments de type TextField
• ComboBoxListCell
IHM-1 – FX08
– Jacques BAPST
37
ListView [4]
fixedCellSize
cellFactory
focusModel
onScrollTo
onEditStart
onEditCommit
onEditCancel
IHM-1 – FX08
– Jacques BAPST
39
ListView [6]
Valeur Double qui définit une hauteur (ou largeur si horizontal)
fixe pour tous les éléments de la liste (calculée sinon pour chacun
des éléments). Accélère l'affichage pour les longues listes.
Définition d'une fabrique d'éléments (factory) permettant de
personnaliser totalement les éléments de la liste.
Type Callback<ListView<T>,ListCell<T>>
Modèle permettant de connaître l'élément de la liste qui possède
le focus (ou son index).
Définition de l'action à exécuter lorsque la méthode scrollTo()
a été invoquée.
Définition des actions à exécuter lors des différentes phases de
l'édition des éléments (s'ils sont éditables).
§ Exemple d'utilisation d'un composant ListView pour permettre à
l'utilisateur de sélectionner des pays (sélection multiple) et affichage
sur la console de la sélection courante (à chaque changement).
ListView<String> livCountry = new ListView<String>();
ObservableList<String> countries = FXCollections.observableArrayList(
"Allemagne", "Angleterre", "Belgique",
"Espagne",
"France",
"Italie",
"Pays-Bas", "Portugal",
"Suisse"
);
livCountry.setItems(countries);
livCountry.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
livCountry.setFixedCellSize(22);
livCountry.setPrefSize(200, 180);
livCountry.setOnMouseClicked(event -> {
System.out.println(livCountry.getSelectionModel().getSelectedItems());
});
IHM-1 – FX08
– Jacques BAPST
38
IHM-1 – FX08
– Jacques BAPST
40
ListView [7]
ListView [9]
§ Exécution de l'application :
§ L'utilisateur peut cliquer sur un des éléments de la liste et faire
apparaître un composant qui s'apparente à un ComboBox qui lui
permet de choisir dans la liste déroulante.
• La valeur sélectionnée dans la liste déroulante devient la valeur de
l'élément de la ListView.
• Affichage sur la console
[Angleterre]
[Angleterre, Belgique]
[Angleterre, Belgique, Italie]
[Espagne]
IHM-1 – FX08
41
– Jacques BAPST
IHM-1 – FX08
– Jacques BAPST
43
ListView [8]
Spinner [1]
§ Exemple d'utilisation d'un composant ListView en plaçant une liste
déroulante (ComboBox) comme élément éditable de la liste.
§ Le composant Spinner<T> permet à l'utilisateur de sélectionner une
valeur dans une liste d'éléments ordonnés (de type T).
ObservableList<String> candidates = FXCollections.observableArrayList();
ObservableList<String> top10
= FXCollections.observableArrayList();
ListView<String> listView = new ListView<String>(top10);
listView.setPrefSize(180, 235);
listView.setEditable(true);
§ Deux boutons permettent à l'utilisateur de faire défiler les valeurs
successives de la liste dans l'ordre croissant et décroissant.
§ Les valeurs sélectionnables sont définies dans le modèle du
composant qui est de type SpinnerValueFactory<T>.
Éléments de la
ComboBox
candidates.addAll("Mommy", "Interstallar", "Gone Girl", "Her",
"Boyhood", "Le sel de la terre", "Dragon 2",
"Night Call", "Les combattants", "The Raid 2",
"12 Years a Slave", "Pride", "Timbuktu", "R",
"Winter Sleep", "Philomena", "Nebraska"
);
for (int i = 1; i <= 10; i++) {
top10.add("Select Position " + i + "...");
}
• Trois modèles sont proposés par défaut (classes internes) :
ð
ð
ð
// Valeurs initiales
ComboBox comme ListCell
listView.setItems(top10);
listView.setCellFactory(ComboBoxListCell.forListView(candidates));
IHM-1 – FX08
§ Seule la valeur de l'élément sélectionné est affichée dans un champ
texte interne (TextField) nommé éditeur (propriété Editor).
– Jacques BAPST
42
IntegerSpinnerValueFactory
DoubleSpinnerValueFactory
ListSpinnerValueFactory
§ Il est possible de rendre la zone de texte éditable. L'utilisateur doit
alors saisir des valeurs valides (en fonction du modèle interne choisi)
sinon la valeur sera adaptée [min, max] ou une exception sera générée.
IHM-1 – FX08
– Jacques BAPST
44
Spinner [2]
§ Le défilement des valeurs peut être rendu circulaire (wrapping).
§ L'emplacement des boutons de défilement est configurable en
changeant le style CSS associé à la classe du composant.
• En plus du style par défaut (right vertical), cinq styles sont définis par
des constantes de la classe Spinner.
Spinner<Integer>
Spinner<Double>
sprI
sprD
= new Spinner<>(-20, 20, 0, 5);
= new Spinner<>(1.0, 6.0, 4.0, 0.25);
sprI.getStyleClass().add(Spinner.STYLE_CLASS_SPLIT_ARROWS_VERTICAL);
sprD.getStyleClass().add(Spinner.STYLE_CLASS_SPLIT_ARROWS_HORIZONTAL);
IHM-1 – FX08
– Jacques BAPST
45
Spinner [3]
§ Différents constructeurs permettent de créer le composant Spinner
en définissant simultanément le type et les valeurs enregistrées dans
le modèle interne.
§ Pour les modèles numériques, les valeurs minimales, maximales,
initiales ainsi que les incréments (step) peuvent être définis.
§ Exemples :
Spinner<Integer> sprTemp
= new Spinner<>(-20, 20, 0, 5);
Spinner<Double>
sprGrades = new Spinner<>(1.0, 6.0, 4.0, 0.25);
Spinner<String>
sprDays
= new Spinner<>(
FXCollections.observableArrayList(
"Monday",
"Tuesday",
Modèle de type
"Wednesday",
ListSpinnerValueFactory
"Thursday",
"Friday",
"Saturday",
"Sunday"
));
IHM-1 – FX08
– Jacques BAPST
46
Panneaux spécialisés [2]
Cours IHM-1
JavaFX
§ Suite des panneaux spécialisés :
• Pagination
• TitledPane
9 - Panneaux spécialisés
• Accordion
Scroll-, Split-, Tab-, … Panes
• ToolBar
: Panneau d'accès à plusieurs 'pages' contenant chacune
un composant. Accès direct par des boutons.
: Panneau contenant un en-tête (titre) et un composant qui
peut être ouvert ou fermé en cliquant sur le titre.
: Panneau contenant plusieurs TitledPanes dont un seul
peut être ouvert à la fois.
: Barre horizontale ou verticale contenant des composants
'outils' (boutons ou autres). Un bouton-menu est affiché
si tous les composants ne peuvent pas être affichés.
§ Ces composants sont sommairement expliqués dans ce chapitre. Il
faut cependant consulter la documentation pour connaître
l'ensemble de leurs caractéristiques et de leurs fonctionnalités.
Jacques BAPST
[email protected]
IHM-1 – FX09
– Jacques BAPST
3
Panneaux spécialisés [1]
ScrollPane [1]
§ Un certain nombre de composants ne font pas partie de la famille
des Layout-Panes (les sous-classes de Pane) mais jouent en partie un
rôle de conteneur.
§ Le composant ScrollPane permet d'afficher tout ou partie d'un
autre composant et en offrant la possibilité d'afficher des barres de
défilement pour déplacer la partie visible (scrolling) du composant
'observé'.
§ Parmi ces panneaux spécialisés (une désignation non-officielle), on
peut mentionner les composants suivants :
• ScrollPane
• SplitPane
• TabPane
IHM-1 – FX09
§ C'est donc une sorte de fenêtre (viewport) sur un autre composant.
: Panneau permettant le scrolling d'un autre composant
à l'aide de barres de défilement (scrollbar) ou à l'aide de
la souris (panning).
: Panneau divisé en plusieurs volets redimensionnables et
contenant chacun un composant. Le panneau peut être
divisé horizontalement ou verticalement.
: Panneau à onglets. Un composant est assigné à chaque
onglet. Chaque onglet possède un titre. Un seul onglet est
visible à la fois (il est sélectionnable par l'utilisateur).
– Jacques BAPST
2
§ Il est également possible de permettre à l'utilisateur de se déplacer
avec la souris ou autre dispositif de pointage (panning).
§ Un seul composant peut être observé au travers d'un ScrollPane,
cependant, le composant peut naturellement être un conteneur ou
n'importe quel nœud (Node) d'un graphe de scène.
§ On peut définir les règles d'affichage (policies) des barres de
défilement horizontales et verticales ("jamais", "toujours" et "si
nécessaire").
IHM-1 – FX09
– Jacques BAPST
4
ScrollPane [2]
ScrollPane [4]
§ Quelques propriétés du composant ScrollPane :
§ Exemple d'utilisation d'un ScrollPane pour visualiser une image.
Le composant 'observé' au travers du scroll-pane (Node).
Booléen qui indique si l'utilisateur peut déplacer le
pannable
composant observé (le viewport) avec la souris.
Par défaut : false.
Largeur préférée de la fenêtre d'observation (viewport)
prefViewportWidth
(Double).
Hauteur préférée de la fenêtre d'observation (viewport)
prefViewportHeight
(Double).
Valeurs minimales, maximales et courantes de la position
hmin, hmax, hvalue
des barres de défilement horizontale et verticale (Double).
vmin, vmax, vvalue
Par défaut min=0, max=1.
Règles d'affichage des barres de défilement horizontale et
hbarPolicy
verticale (ScrollPane.ScrollBarPolicy). Les valeurs
vbarPolicy
possibles sont : NEVER, ALWAYS, AS_NEEDED.
content
IHM-1 – FX09
– Jacques BAPST
5
ScrollPane [3]
fitToWidth
fitToHeight
viewportBounds
private static final String IMG = "/resources/Monument_Valley.jpg";
private ScrollPane root
= new ScrollPane();
private Image
image = new Image(getClass().getResourceAsStream(IMG));
private ImageView imagev = new ImageView(image);
. . .
root.setContent(imagev);
root.setPannable(true);
root.setPrefViewportWidth(300);
root.setPrefViewportHeight(200);
primaryStage.setScene(new Scene(root));
primaryStage.show();
. . .
L'utilisateur peut aussi
déplacer l'image avec
la souris (panning)
IHM-1 – FX09
– Jacques BAPST
7
SplitPane [1]
Si le composant 'observé' est redimensionnable (resizable),
et que cette propriété booléenne est true, le composant
sera redimensionné pour que sa largeur corresponde à la
largeur de la fenêtre d'observation (largeur du viewport).
Si le composant 'observé' est redimensionnable (resizable),
et que cette propriété booléenne est true, le composant
sera redimensionné pour que sa hauteur corresponde à la
hauteur de la fenêtre d'observation (hauteur du viewport).
Limites actuelles du viewport (Bounds). Caractéristiques du
rectangle qui correspond à la fenêtre d'observation du
composant.
§ Le composant SplitPane est un panneau divisé horizontalement ou
verticalement en sous-panneaux contenant chacun un composant.
§ Les barres de division entre les sous-panneaux (dividers) peuvent
être déplacées par l'utilisateur ce qui va provoquer (dans certaines
limites) le redimensionnement des deux panneaux voisins.
§ Le nombre de sous-panneaux n'est pas limité.
§ Pour permettre le déplacement des dividers, la taille des composants
placés dans un SplitPane doit donc pouvoir être adaptée. C'est
pourquoi on place généralement des conteneurs (layout-panes) dans
les sous-panneaux car ils sont redimensionnables.
§ Les sous-panneaux sont ajoutés en invoquant une des méthodes :
• getItems.add(node);
• getItems.add(index, node);
• getItems.addAll(node1, node2, …);
IHM-1 – FX09
– Jacques BAPST
6
IHM-1 – FX09
– Jacques BAPST
8
SplitPane [2]
SplitPane [4]
§ La seule propriété du composant SplitPane est orientation qui
indique si les sous-panneaux sont placés horizontalement (orientation
par défaut) ou verticalement.
§ Exemple d'utilisation d'un SplitPane avec trois sous-panneaux.
§ On utilisera les constantes :
• Orientation.HORIZONTAL
• Orientation.VERTICAL
• getDividerPositions() : Retourne un tableau de double (double[])
root
sp1
sp2
sp3
lbl1
btn2
tfd3
=
=
=
=
=
=
=
new
new
new
new
new
new
new
SplitPane();
StackPane();
StackPane();
StackPane();
Label("A label");
Button("A button");
TextField("A textfield");
root.getItems().addAll(sp1, sp2, sp3);
root.setDividerPositions(0.2, 0.6);
• setDividerPosition(int dividerIndex, double position)
• setDividerPositions(double... positions)
– Jacques BAPST
SplitPane
StackPane
StackPane
StackPane
Label
Button
TextField
sp1.getChildren().add(lbl1);
sp2.getChildren().add(btn2);
sp3.getChildren().add(tfd3);
§ Les méthodes suivantes permettent de consulter ou définir la
position des barres de séparation (dividers) :
IHM-1 – FX09
private
private
private
private
private
private
private
primaryStage.setScene(new Scene(root, 250, 80));
9
IHM-1 – FX09
– Jacques BAPST
SplitPane [3]
TabPane [1]
§ Lorsque le composant SplitPane est redimensionné, la taille de
tous les sous-panneaux est adaptée proportionnellement (c'est le
comportement par défaut).
§ Le composant TabPane est un panneau avec des onglets (tabs)
associés à des sous-panneaux contenant chacun un composant.
§ Si l'on souhaite qu'un ou plusieurs sous-panneaux gardent leur taille
actuelle lors du redimensionnement du SplitPane on peut utiliser
la méthode statique :
• setResizableWithParent(Node node, Boolean resizable)
§ Une valeur false n'empêchera pas l'utilisateur de modifier la
position des barres de séparation. L'impact est uniquement sur le
redimensionnement du SplitPane lui-même (provoqué par
exemple par un redimensionnement de la fenêtre principale).
11
§ Seul un des onglets est visible à un moment donné, mais l'utilisateur
peut basculer entre ces différents sous-panneaux en cliquant sur
l'onglet associé.
• Les onglets peuvent être positionnés sur un des quatre bords du
composant (ils sont placés en haut par défaut).
• Si la taille du composant ne permet pas d'afficher tous les onglets, un
bouton (MenuButton) est automatiquement ajouté pour permettre à
l'utilisateur d'accéder aux onglets masqués.
§ La classe Tab représente un onglet et associe un composant (Node)
avec un texte (et éventuellement un graphique) qui identifie l'onglet.
§ Par défaut, les onglets peuvent être supprimés par l'utilisateur en
cliquant sur une croix affichée à côté de l'onglet actuellement
sélectionné (à la manière des onglets des navigateurs web).
IHM-1 – FX09
– Jacques BAPST
10
IHM-1 – FX09
– Jacques BAPST
12
TabPane [2]
TabPane [4]
§ Dans un TabPane on ne peut ajouter que des objets de type Tab (on
ne peut pas ajouter directement des conteneurs ou composants).
§ Quelques propriétés de la classe TabPane :
§ Il faut donc :
• Créer des objets de type Tab et y insérer des composants
• Créer un objet de type TabPane et y ajouter les onglets (tabs) créés
tabClosingPolicy
§ En code Java :
Tab tab1
Tab tab2
rotateGraphic
= new Tab("Texte Tab 1");
= new Tab("Texte Tab 2");
tabMinWidth
. . .
tab1.setContent(node1);
tab2.setContent(node2);
tabMinHeight
// Ajout du contenu à l'onglet 1
// Ajout du contenu à l'onglet 2
tabMaxWidth
tabMaxHeight
. . .
TabPane
side
tPane = new TabPane();
selectionModel
tPane.getTabs().addAll(tab1, tab2, …);
IHM-1 – FX09
– Jacques BAPST
13
Détermine de quel côté sont affichés les onglets ( Side).
Définit les règles globales de fermeture (suppression) des
onglets par l'utilisateur (en cliquant sur le symbole x)
(TabPane.TabClosingPolicy).
Valeurs possibles : ALL_TABS, SELECTED_TAB, UNAVAILABLE
Booléen qui indique si les icônes (graphic) associés aux
onglets doivent tourner pour suivre l'orientation des onglets
(en fonction de la position des onglets).
Largeur minimale des onglets (en-tête) (Double).
Hauteur minimale des onglets (en-tête) (Double).
Largeur maximale des onglets (en-tête) (Double).
Hauteur maximale des onglets (en-tête) (Double).
Modèle de sélection des onglets (sélection simple)
(SingleSelectionModel<Tab>).
IHM-1 – FX09
– Jacques BAPST
15
TabPane [3]
TabPane [5]
§ Quelques propriétés de la classe Tab :
§ Exemple d'une application comportant trois TabPane créés par trois
méthodes distinctes, de manière à décomposer le code.
text
graphic
closable
id
content
selected
disable
toolTip
contextMenu
IHM-1 – FX09
Texte affiché dans l'onglet (String).
Composant additionnel (généralement une icône ou un
graphique) associé à l'onglet (Node).
Booléen qui indique si l'onglet peut être fermé (supprimé)
par l'utilisateur.
Identificateur de l'onglet (indépendant du texte affiché)
(String).
Le composant associé à cet onglet (contenu affiché dans le
panneau) (Node).
Booléen qui indique si l'onglet est sélectionné.
Inactivation de l'onglet (Boolean).
Bulle d'aide associée à l'onglet (Tooltip).
Menu contextuel associé à l'onglet (ContextMenu).
– Jacques BAPST
14
• Les onglets peuvent être supprimés par l'utilisateur en cliquant sur la
croix (x) placée à côté du texte (comportement par défaut).
• Un des onglets (About) possède un graphique (rectangle rouge).
private TabPane root = new TabPane();
private Tab
private Tab
private Tab
tab1
tab2
tab3
= new Tab("Varia");
= new Tab("Login");
= new Tab("About");
tab1.setContent(createTab1());
tab2.setContent(createTab2());
tab3.setContent(createTab3());
tab3.setGraphic(new Rectangle(20, 10, Color.RED));
root.setRotateGraphic(true);
root.getTabs().addAll(tab1, tab2, tab3);
IHM-1 – FX09
– Jacques BAPST
16
TabPane [6]
TabPane [8]
§ Méthode de création du composant associé au premier onglet.
§ Méthode de création du composant associé au troisième onglet.
private Pane createTab1()
VBox
pane;
Button
btnA
=
Label
lblB
=
ComboBox<String> cbbC =
private Pane createTab3() {
BorderPane pane = new BorderPane();
{
new Button("Alpha");
new Label("Bravo");
new ComboBox<>();
Label lblTitle = new Label("About TabPane");
lblTitle.setFont(Font.font("SansSerif", FontWeight.BOLD, 18));
lblTitle.setTextFill(Color.DARKBLUE);
BorderPane.setAlignment(lblTitle, Pos.CENTER);
pane.setTop(lblTitle);
pane = new VBox(10);
pane.setPadding(new Insets(10));
pane.setStyle("-fx-background-color: #DDFFEE");
pane.setAlignment(Pos.CENTER);
TextArea
txaText = new TextArea("A very useful app\n"
+ "from Sella Kellos");
txaText.setPrefColumnCount(10);
txaText.setPrefRowCount(3);
BorderPane.setMargin(txaText, new Insets(10));
pane.setCenter(txaText);
cbbC.getItems().addAll("Charlie", "Delta");
cbbC.getSelectionModel().select(0);
pane.getChildren().addAll(btnA, lblB, cbbC);
return pane;
}
return pane;
}
IHM-1 – FX09
– Jacques BAPST
17
IHM-1 – FX09
– Jacques BAPST
TabPane [7]
Pagination [1]
§ Méthode de création du composant associé au deuxième onglet.
§ Le composant Pagination permet de présenter des informations
ou des éléments d'interaction sous forme de 'pages'. Une liste de
boutons (ou de puces) permettent à l'utilisateur d'accéder aux
pages individuelles (de les afficher).
private Pane createTab2() {
GridPane
pane
= new
Label
lblUser = new
Label
lblPass = new
TextField
tfdUser = new
PasswordField tfdPass = new
Button
btnLogin = new
GridPane();
Label("Username:");
Label("Password:");
TextField();
PasswordField();
Button("Login");
19
§ Les pages sont constituées de conteneurs ou
de composants quelconques (Nodes).
§ La classe dispose des constructeurs suivants :
pane.setHgap(6);
pane.setVgap(12);
pane.setPadding(new Insets(15));
pane.setStyle("-fx-background-color: #FCF7CC");
pane.add(lblUser, 0, 0);
pane.add(tfdUser, 1, 0);
pane.add(lblPass, 0, 1);
pane.add(tfdPass, 1, 1);
pane.add(btnLogin, 0, 2, 2, 1);
GridPane.setHalignment(btnLogin, HPos.CENTER);
GridPane.setMargin(btnLogin, new Insets(10, 0, 0, 0));
return pane;
• Pagination();
• Pagination(int pageCount);
• Pagination(int pageCount, int pageIndex);
§ Les boutons peuvent être remplacés par
des puces (bullets) en adaptant le style de
l'affichage du composant Pagination :
• getStyleClass().add(Pagination.STYLE_CLASS_BULLET);
}
IHM-1 – FX09
– Jacques BAPST
18
IHM-1 – FX09
– Jacques BAPST
20
Pagination [2]
Pagination [4]
§ Le nombre maximal d'indicateurs de pages (boutons ou puces), fixé
par défaut à 10, peut être adapté en modifiant la valeur de la
propriété maxPageIndicatorCount :
§ Méthode de création des pages (liste verticale de boutons).
private Node createPage(int pageIndex, int nbPerPage) {
VBox pane
= new VBox(10);
Label lblTitle = new Label("Inbox Messages ("+(pageIndex+1)+")");
• root.setMaxPageIndicatorCount(5);
pane.setPadding(new Insets(20));
pane.setAlignment(Pos.CENTER);
§ Pour créer les pages, il faut définir la propriété pageFactory qui
est de type Callback<Integer, Node> (interface fonctionnelle)
possédant une unique méthode chargée de créer la page dont
l'indice est passé en paramètre :
lblTitle.setFont(Font.font("System", FontWeight.BOLD, 18));
lblTitle.setTextFill(Color.TOMATO);
pane.getChildren().add(lblTitle);
int offset = pageIndex * nbPerPage;
• call(Integer pageIndex) : Doit retourner un objet de type Node
for (int i=offset; i<(offset+nbPerPage); i++) {
if (i>=sList.length) return new Label("Page not found !");
Button btn = new Button("Message from " + sList[i]);
btn.setMinWidth(150);
pane.getChildren().addAll(btn);
}
§ L'objet Callback peut être créé à l'aide d'une expression lambda
(voir l'exemple qui suit), d'une classe anonyme ou en instanciant une
classe 'ordinaire'.
return pane;
}
IHM-1 – FX09
– Jacques BAPST
21
IHM-1 – FX09
– Jacques BAPST
Pagination [3]
TitledPane [1]
§ Exemple d'une application affichant différentes pages à l'aide du
composant Pagination.
§ Le composant TitledPane est un panneau avec un titre.
§ Le panneau peut être ouvert ou fermé en cliquant sur le titre qui agit
comme un bouton à deux états.
• Les pages sont créées par la méthode createPage()
§ Le panneau peut contenir n'importe quel objet
de type Node (généralement un conteneur qui
regroupe des composants).
private String[] sList = {"Alpha", "Bravo", "Charlie", "Delta",
"Echo", "Foxtrot","Golf",
"Hotel",
"India", "Juliet", "Kilo",
"Lima"
};
private Pagination root = new Pagination(5, 0);
§ La propriété collapsible détermine si le
panneau peut être fermé (comportement par
défaut). Sinon, il reste toujours ouvert et le
composant est un panneau avec un titre.
primaryStage.setTitle("Pagination Test");
root.setPageFactory(index -> {
return createPage(index, 4);
});
§ La propriété expanded indique si le panneau
est ouvert ou fermé.
primaryStage.setScene(new Scene(root));
primaryStage.show();
IHM-1 – FX09
– Jacques BAPST
23
22
IHM-1 – FX09
– Jacques BAPST
24
TitledPane [2]
ToolBar [1]
§ Le titre du composant TitledPane est une sous-classe de Labeled
et peut donc contenir un texte et/ou un graphique :
§ Le composant ToolBar permet de rassembler (horizontalement ou
verticalement) des éléments d'interaction : c'est une barre d'outils.
• setText(String title)
• setGraphic(Node graphic)
§ Le contenu du composant (panneau) est géré par la propriété
content. Le panneau est généralement représenté par un conteneur
(layout-pane) qui rassemble et dispose les composants :
• setContent(Node root)
§ Le composant TitledPane constitue l'élément de base du
composant Accordion (voir pages suivantes).
§ Dans une barre d'outils on y trouve généralement des boutons,
des boutons à deux états, des séparateurs mais on peut y placer
n'importe quel composant.
§ Une des caractéristiques du composant ToolBar est que si tous les
composants qu'il contient ne peuvent pas être affichés, un boutonmenu apparaît automatiquement pour permettre à l'utilisateur
d'accéder aux outils non visibles.
§ La propriété orientation détermine si la barre d'outils est disposée
horizontalement ou verticalement.
§ Les éléments de la barre d'outils peuvent être insérés soit dans le
constructeur new ToolBar(node1, node2, …) soit avec les méthodes
getItems().add(node) ou getItems().addAll(node1, node2, …).
IHM-1 – FX09
– Jacques BAPST
25
IHM-1 – FX09
– Jacques BAPST
27
Accordion
ToolBar [2]
§ Le composant Accordion rassemble un groupe de TitledPane dont
un seul peut être ouvert à la fois.
§ Le composant Separator peut être inséré entre les éléments de la
barre d'outils. Son orientation sera automatiquement adaptée en
fonction de l'orientation de la barre d'outils.
§ La liste des composants TitledPane est
retournée par la méthode getPanes().
§ Pour créer un composant Accordion, il faut
• Créer les composants TitledPane
(voir pages précédentes)
• Les assembler dans le composant Accordion
en les ajoutant à la liste de manière individuelle
ou collective :
ð
ð
getPanes().add(tPane)
getPanes().addAll(tPane1, tPane2, …)
Separator
§ La propriété expandedPane représente le panneau
ouvert (type TitledPane).
IHM-1 – FX09
– Jacques BAPST
26
IHM-1 – FX09
– Jacques BAPST
28
Modal / Non-modal
§ Une boîte de dialogue peut être
Cours IHM-1
JavaFX
• Modale
: L'utilisateur ne peut pas interagir avec la fenêtre dont la
boîte de dialogue dépend avant de l'avoir fermée.
• Non-modale : L'utilisateur peut interagir avec la boîte de dialogue mais
(modeless)
aussi avec la fenêtre dont la boîte de dialogue dépend
(en laissant la boîte de dialogue ouverte).
(modal)
10 - Boîtes de dialogue
§ On utilisera une boîte de dialogue modale par ex. pour confirmer
ou annuler une action critique (suppression de données par exemple).
(simples et spécialisées)
• La fenêtre principale est 'bloquée' tant que l'utilisateur n'a pas décidé.
• La boîte de dialogue se ferme automatiquement dès la décision prise.
§ Une boîte de dialogue non-modale sera utilisée par exemple pour
présenter à l'utilisateur une palette d'outils qu'il pourra sélectionner
et appliquer successivement sur la fenêtre principale.
Jacques BAPST
[email protected]
• La boîte de dialogue reste ouverte tant que l'utilisateur ne la ferme pas
explicitement.
IHM-1 – FX10
– Jacques BAPST
3
Définition [1]
§ Les boîtes de dialogue sont des éléments d'une interface graphique
qui se présentent généralement sous la forme d'une fenêtre affichée
par une application (ou éventuellement par le système d'exploitation)
dans le but :
• d'informer l'utilisateur (texte, mise en garde, graphique, etc.)
Boîtes de dialogue simples
• d'obtenir une information de l'utilisateur (mot-de-passe, choix, etc.)
• ou une combinaison des deux
ð
Par exemple l'informer d'un événement et lui demander de faire un choix
§ Une boîte de dialogue dépend d'une autre fenêtre (év. d'une autre
boîte de dialogue), c'est ce qui distingue ce composant d'une fenêtre
indépendante (la fenêtre principale par exemple).
IHM-1 – FX10
– Jacques BAPST
2
IHM-1 – FX10
– Jacques BAPST
4
Types de boîtes de dialogue
Boîte de dialogue - Alert [1]
§ Dans une application, on peut trouver
§ Boîte de dialogue Alert de type "Information" avec en-tête (header).
• des boîtes de dialogue standard avec une mise en page prédéfinie et qui
sont utilisées
Pour informer l'utilisateur par un simple texte
ð Pour demander quittance à l'utilisateur
ð Pour demander à l'utilisateur de faire un choix entre plusieurs options
ð Pour saisir une information simple (ligne de texte)
Alert dialog = new Alert(AlertType.INFORMATION);
dialog.setTitle("An information dialog-box");
dialog.setHeaderText("An information dialog with header");
dialog.setContentText("Le message d'information\n" +
"(une deuxième ligne)");
dialog.showAndWait();
ð
• des boîtes de dialogue spécifiques avec
ð
ð
Une disposition des éléments propre à l'application
De nombreux composants pour représenter l'information
§ La plupart des kits de développement offrent des solutions clés en
main pour les boîtes de dialogue simples (standard).
§ Les boîtes de dialogue spécifiques doivent par contre être conçues et
réalisées avec les mêmes efforts que pour tout autre type d'interface
(choix des conteneurs et composants, contraintes de disposition, etc.).
IHM-1 – FX10
– Jacques BAPST
5
IHM-1 – FX10
– Jacques BAPST
Boîtes de dialogue JavaFX
Boîte de dialogue - Alert [2]
§ Les boîtes de dialogue standard n'ont été introduites que très
tardivement dans JavaFX (version 8u40, en 2015).
§ Boîte de dialogue Alert de type "Warning" sans en-tête (header).
Alert dialogW = new Alert(AlertType.WARNING);
§ Avant cette version, il était habituel d'utiliser les composants de type
Dialogs de la librairie externe ControlsFX (qui sont désormais signalés
comme étant deprecated).
§ Les pages suivantes décrivent sommairement, et sur la base
d'exemples concrets, la manière de coder les boîtes de dialogue en
se basant sur les classes et composants disponibles dans JavaFX.
IHM-1 – FX10
– Jacques BAPST
7
6
dialogW.setTitle("A warning dialog-box");
dialogW.setHeaderText(null); // No header
dialogW.setContentText("Caution : Low Level Battery !");
dialogW.showAndWait();
IHM-1 – FX10
– Jacques BAPST
8
Boîte de dialogue - Alert [3]
Boîte de dialogue - Alert [5]
§ Boîte de dialogue Alert de type "Error".
§ Boîte de dialogue Alert de type "Confirmation" avec options
personnalisées.
Alert dialogE = new Alert(AlertType.ERROR);
Alert dBox = new Alert(AlertType.CONFIRMATION);
dialogE.setTitle("An error dialog-box");
dialogE.setHeaderText("Not Printed");
dialogE.setContentText("Error : Incorrect Paper-Format !");
dialogE.showAndWait();
dBox.setTitle("A confirmation dialog-box with custom actions");
dBox.setHeaderText("Java-Pizza : The Very Best in Town !");
dBox.setContentText("Choose your pizza size :");
ButtonType
ButtonType
ButtonType
ButtonType
btnSmall
btnMedium
btnBig
btnCancel
=
=
=
=
new
new
new
new
ButtonType("Small");
ButtonType("Medium");
ButtonType("Big");
ButtonType("Cancel", ButtonData.CANCEL_CLOSE);
dBox.getButtonTypes().setAll(btnSmall, btnMedium, btnBig, btnCancel);
IHM-1 – FX10
– Jacques BAPST
9
Boîte de dialogue - Alert [4]
IHM-1 – FX10
– Jacques BAPST
11
Boîte de dialogue - Alert [6]
§ Boîte de dialogue Alert de type "Confirmation".
Optional<ButtonType> choice = dBox.showAndWait();
Alert dialogC = new Alert(AlertType.CONFIRMATION);
if (choice.get() == btnSmall) {
System.out.println("User chose Small");
}
else if (choice.get() == btnMedium) {
System.out.println("User chose Medium");
}
else if (choice.get() == btnBig) {
System.out.println("User chose Big");
}
else {
System.out.println("Cancel or Close");
}
dialogC.setTitle("A confirmation dialog-box");
dialogC.setHeaderText(null);
dialogC.setContentText("Full backup will start immediately");
Optional<ButtonType> answer = dialogC.showAndWait();
if (answer.get() == ButtonType.OK) {
System.out.println("User chose OK");
}
else {
System.out.println("User chose Cancel or closed the dialog-box");
}
IHM-1 – FX10
– Jacques BAPST
10
IHM-1 – FX10
– Jacques BAPST
12
Saisie de texte - TextInputDialog [1]
Sélection - ChoiceDialog [1]
§ Boîte de dialogue TextInputDialog pour saisir une ligne de texte.
§ Boîte de dialogue ChoiceDialog pour saisir un choix (dans une liste).
TextInputDialog inDialog = new TextInputDialog("Guest");
inDialog.setTitle("A Text-Input Dialog");
inDialog.setHeaderText("Account Login (or Guest Access)");
inDialog.setContentText("Username :");
String[]
Valeur par défaut
ChoiceDialog<String> cDial = new ChoiceDialog<>(choices[2], choices);
cDial.setTitle("A Choice Dialog");
cDial.setHeaderText("Select Execution Priority");
cDial.setContentText("Priority:");
Optional<String> textIn = inDialog.showAndWait();
//--- Get response value (traditional way)
if (textIn.isPresent()) {
System.out.println("Login name = " + textIn.get());
}
Valeur par défaut
Optional<String> selection = cDial.showAndWait();
selection.ifPresent(str -> System.out.println("Selection:" + str));
Expression lambda non
exécutée si l'utilisateur
presse Cancel ou ferme
la boîte de dialogue
Retourne false si l'utilisateur
presse Cancel ou ferme la
boîte de dialogue
IHM-1 – FX10
choices = {"Urgent", "High", "Standard", "Low"};
– Jacques BAPST
13
IHM-1 – FX10
– Jacques BAPST
Saisie de texte - TextInputDialog [2]
Boîte de dialogue personnalisée [1]
§ Le texte saisi par l'utilisateur peut aussi être récupéré en utilisant la
méthode ifPresent() qui prend en paramètre un objet de type
Consumer<T> que l'on peut instancier avec une expression lambda.
§ Il est possible de personnaliser des boîtes de dialogue (custom
dialog) en utilisant directement la classe Dialog qui est la classe
parente de Alert, TextInputDialog et ChoiceDialog.
L'interface fonctionnelle Consumer<T> possède la méthode abstraite void
accept(T t) qui effectue une opération avec la valeur t passée en paramètre
(pas de valeur de retour, opération par effet de bord).
Optional<String> textIn = inDialog.showAndWait();
//--- Get response value (with lambda expression)
textIn.ifPresent(txt -> System.out.println("Login name = " + txt));
15
§ En plus du texte principal défini par la propriété contentText (de
type String), il est possible, pour toutes les boîtes de dialogue, de
définir un contenu étendu (affiché par "Show Details") représenté par
la propriété expandableContent (de type Node) de l'objet interne
DialogPane.
Peut correspondre à
§ On peut définir ce contenu étendu ainsi :
tout un graphe de scène
Alert mBox = new Alert(AlertType.ERROR);
mBox.getDialogPane().setExpandableContent(node);
Expression lambda non
exécutée si l'utilisateur
presse Cancel ou ferme
la boîte de dialogue
IHM-1 – FX10
Expandable
Content
– Jacques BAPST
14
IHM-1 – FX10
– Jacques BAPST
16
Boîte de dialogue personnalisée [2]
§ Par défaut, toutes les boîtes de dialogues standards (qui héritent de
Dialog) sont modales.
§ Pour créer une boîte de dialogue non-modale, on peut invoquer la
méthode initModality() :
Boîtes de dialogue spécialisées
//--- To create a modeless dialog box
Alert mBox = new Alert(AlertType.ERROR);
mBox.initModality(Modality.NONE);
§ L'icône par défaut (placée dans la barre de titre de la fenêtre popup)
peut être remplacée par une icône personnalisée :
//--- To add a custom title-bar icon
Stage dStage = (Stage)(cDial.getDialogPane().getScene().getWindow());
dStage.getIcons().add(new Image("/resources/warn_1.png"));
IHM-1 – FX10
17
– Jacques BAPST
IHM-1 – FX10
– Jacques BAPST
19
Boîte de dialogue personnalisée [3]
FileChooser [1]
§ Pour changer l'icône principale qui est placée à gauche du contenu
de la boîte de dialogue ou dans son en-tête (header), on peut utiliser
la méthode setGraphic().
§ L'utilitaire FileChooser permet d'ouvrir une boîte de dialogue
permettant à l'utilisateur de naviguer dans l'arborescence des
fichiers de la machine cible et de sélectionner un ou plusieurs
fichiers (pour sélectionner un répertoire, il faut utiliser DirectoryChooser).
//--- Add a custom content icon
cDial.setGraphic(new ImageView("/resources/warn_1.png"));
§ Il s'agit d'une boîte de dialogue spécifique au système d'exploitation
de la machine cible. Le look & feel est indépendant de JavaFX (il
correspond en principe au look & feel natif).
§ Par défaut, cette icône est déterminée par le type de boîte de
dialogue ou par le type d'alerte
(AlertType : ERROR, WARNING, …).
§ Ce composant peut être utilisé pour :
• Naviguer et sélectionner un seul fichier ð showOpenDialog()
• Naviguer et sélectionner plusieurs fichiers ð showOpenMultipleDialog()
• Naviguer et sauvegarder un fichier
ð showSaveDialog()
Icône
§ L'invocation de FileChooser est généralement liée à l'action d'un
bouton, d'une option de menu ou autre action de l'utilisateur.
Icône
IHM-1 – FX10
– Jacques BAPST
18
IHM-1 – FX10
– Jacques BAPST
20
FileChooser [2]
FileChooser [4]
§ Exemple d'utilisation de FileChooser avec :
§ Affichage (sous Windows) :
• Définition d'un titre pour la fenêtre popup
• Définition d'un répertoire initial (point de départ de la navigation)
basé sur des propriétés du système
• Définition de filtres basés sur les extensions des fichiers
FileChooser fileChooser = new FileChooser();
fileChooser.setTitle("FileChooser Example");
File homeDir = new File(System.getProperty("user.home"));
fileChooser.setInitialDirectory(homeDir);
fileChooser.getExtensionFilters().addAll(
new ExtensionFilter("Text Files" , "*.txt"),
new ExtensionFilter("Image Files", "*.png", "*.jpg", "*.gif"),
new ExtensionFilter("Audio Files", "*.wav", "*.mp3", "*.aac"),
new ExtensionFilter("All Files" , "*.*"));
IHM-1 – FX10
– Jacques BAPST
21
IHM-1 – FX10
23
– Jacques BAPST
FileChooser [3]
FileChooser [5]
§ La fenêtre popup s'ouvre lors de l'invocation de la méthode show…()
qui retourne le fichier sélectionné ou une référence null si
l'utilisateur a pressé sur Cancel ou a fermé la fenêtre.
§ Pour permettre une sélection multiple, il faut utiliser la méthode
showOpenMultipleDialog() qui retourne une liste de fichiers ou
une référence null si l'utilisateur a pressé sur Cancel ou a fermé la
fenêtre.
File selectedFile = fileChooser.showOpenDialog(primaryStage);
List<File> selectedFiles = fileChooser.showOpenMultipleDialog(stage);
if (selectedFile != null) {
try {
//--- Open the file with associated application
Desktop.getDesktop().open(selectedFile);
}
catch (Exception e) {
System.err.println("ERROR: Unable to open the file");
}
}
if (selectedFiles != null) {
for (File f : selectedFiles) {
System.out.println(f);
}
Codage classique
Avec expression lambda
selectedFiles.forEach(f -> System.out.println(f));
selectedFiles.forEach(System.out::println);
}
Tente d'ouvrir le fichier
avec l'application associée
IHM-1 – FX10
– Jacques BAPST
Avec référence de méthode
22
IHM-1 – FX10
– Jacques BAPST
24
DirectoryChooser [1]
DirectoryChooser [3]
§ L'utilitaire DirectoryChooser permet d'ouvrir une boîte de dialogue
permettant à l'utilisateur de naviguer dans l'arborescence des
fichiers de la machine cible et de sélectionner un répertoire.
§ Affichage (sous Windows) :
§ Il s'agit d'une boîte de dialogue spécifique au système d'exploitation
de la machine cible. Le look & feel est indépendant de JavaFX (il
correspond au look & feel natif).
§ Ce composant ne comporte qu'une seule méthode de sélection
showDialog(), qui retourne un objet de type File représentant un
répertoire.
IHM-1 – FX10
– Jacques BAPST
25
IHM-1 – FX10
– Jacques BAPST
DirectoryChooser [2]
DatePicker [1]
§ Exemple :
§ La classe DatePicker représente un composant qui permet à
l'utilisateur de sélectionner une date dans un calendrier qui est
affiché en fenêtre popup.
DirectoryChooser dirChooser = new DirectoryChooser();
dirChooser.setTitle("DirectoryChooser Example");
dirChooser.setInitialDirectory(new File("D://Temp"));
27
§ Le composant représente une forme de ComboBox éditable (il a
comme classe parente ComboBoxBase) et la date sélectionnée par
l'utilisateur peut être consultée avec la méthode getValue() qui
retourne un objet de type LocalDate.
File selectedFile = dirChooser.showDialog(primaryStage);
if (selectedFile != null) {
try {
Desktop.getDesktop().open(selectedFile);
}
catch (Exception e) {
e.printStackTrace();
}
}
Tente d'ouvrir le gestionnaire de
fichiers de l'OS (file manager)
IHM-1 – FX10
– Jacques BAPST
26
IHM-1 – FX10
– Jacques BAPST
28
DatePicker [2]
ColorPicker [2]
§ Exemple d'utilisation du composant :
. . .
DatePicker dPicker = new DatePicker();
Ajout du composant
dans le graphe de scène
root.getChildren().add(dPicker);
dPicker.setOnAction(e -> {
LocalDate date = dPicker.getValue();
System.out.println("Selected date: " + date);
. . .
// 1..31
int day
= date.getDayOfMonth();
int month = date.getMonthValue();
// 1..12
int year = date.getYear();
. . .
});
. . .
IHM-1 – FX10
– Jacques BAPST
29
IHM-1 – FX10
ColorPicker [1]
ColorPicker [3]
§ La classe ColorPicker représente un composant qui permet à
l'utilisateur de sélectionner une couleur dans une palette ou selon
différents espaces de couleur (HSB, RBG, Web, …).
§ Exemple d'utilisation du composant :
§ Le composant représente une forme de ComboBox éditable (il a
comme classe parente ComboBoxBase) et la couleur sélectionnée par
l'utilisateur peut être consultée avec la méthode getValue().
. . .
ColorPicker cPicker = new ColorPicker(Color.BLUE);
Ajout du composant
dans le graphe de scène
root.getChildren().add(cPicker);
§ L'utilisateur peut définir et enregistrer des couleurs personnalisées
(custom colors) qui peuvent être récupérées avec la méthode
getCustomColors() qui retourne une liste de couleurs.
31
– Jacques BAPST
cPicker.setOnAction(e -> {
Color c = cPicker.getValue();
. . .
//--- Display hexadecimal values (#rrggbbaa)
System.out.println("Selected color: " + c);
// Display decimal values RGB (0.0...1.0)
System.out.println("Selected color = " + c.getRed()
+ ", "
+ c.getGreen() + ", "
+ c.getBlue()
);
. . .
});
§ Une couleur par défaut peut être passée en paramètre au
constructeur de la classe (par défaut Color.WHITE).
. . .
IHM-1 – FX10
– Jacques BAPST
30
IHM-1 – FX10
– Jacques BAPST
32
Feuille de style CSS [2]
§ Une définition de style est constituée :
Cours IHM-1
JavaFX
• D'un sélecteur (le nom du style)
• D'un ensemble de règles sous la forme de paires "propriété" / "valeur"
§ Le sélecteur est un identificateur qui servira à définir les éléments
sur lesquels s'appliqueront les règles.
11 - Feuilles de style
CSS
§ Les paires propriété / valeur sont constituées du nom de la propriété,
du séparateur ":" et de la valeur de cette propriété.
§ Les paires propriété / valeur sont séparées par des ";"
§ L'ensemble des paires propriété / valeur (les règles) est entouré par
des accolades "{ }" et ce bloc suit le sélecteur.
§ Exemple :
Jacques BAPST
[email protected]
.button {
-fx-background-color: blue;
Sélecteur
-fx-text-fill: yellow;
}
Propriété
IHM-1 – FX11
Valeur
– Jacques BAPST
3
Feuille de style CSS [1]
Feuille de style CSS [3]
§ Une feuille de style en cascade (CSS - Cascading Style Sheet) est un
élément permettant de décrire l'aspect visuel des composants d'une
interface.
§ Une feuille de style peut enregistrer plusieurs définitions de style.
Chacune est identifiée par son sélecteur (différents types : class, id, …).
§ A l'origine les CSS ont été élaborées (par le W3C) pour les pages web,
afin de pouvoir décrire la présentation des éléments HTML.
§ Exemple d'une feuille de style :
§ Un des buts de son utilisation est de séparer le contenu (HTML) de la
présentation (CSS) des pages web.
.label {
-fx-font-size: 11pt;
-fx-font-family: "Segoe UI Semibold";
-fx-text-fill: white;
-fx-opacity: 0.6;
}
§ JavaFX a repris le concept et des feuilles de style CSS peuvent être
utilisées pour définir l'aspect (le look, le style) des éléments UI d'une
application, qu'elle soit développée en mode procédural (API) ou
déclaratif (FXML).
.label-bright {
-fx-font-size: 11pt;
-fx-font-family: "Segoe UI Semibold";
-fx-text-fill: white;
-fx-opacity: 1;
}
§ Les CSS sont des éléments textuels, généralement enregistrés dans
des fichiers, qui définissent des règles de style. Le codage de ces
règles doit naturellement respecter une certaine syntaxe.
#welcome-text {
-fx-font-size: 32px;
-fx-font-family: "Arial Black";
-fx-fill: #818181;
}
IHM-1 – FX11
– Jacques BAPST
2
IHM-1 – FX11
– Jacques BAPST
4
Sélecteur [1]
Sélecteur de classe [1]
§ Il existe différentes formes de sélecteurs :
§ La plupart des classes de composants UI (qui héritent de Node)
possèdent, par défaut, un nom de classe de style enregistré dans la
liste retournée par la méthode getStyleClass().
• Sélecteur de classe
(class selector)
• Sélecteurs d'Id
(id selector)
• Sélecteurs d'éléments (element selector)
• Les conteneurs ne possèdent pas, par défaut, de noms de classes de
style mais on peut sans autre en ajouter explicitement dans la liste.
Pas utilisé dans JavaFX
§ Les sélecteurs de classe sont précédés par "."
• Exemple :
§ Le nom de classe de style correspond au nom de la classe du
composant (Node), en minuscules. On ajoute un tiret séparateur "-"
si le nom du composant est formé de plusieurs noms (nom-composé).
.button { . . . }
§ Les sélecteurs d'Id sont précédés par "#"
• Exemple :
§ Dans le CSS, le nom du sélecteur de classe correspondra au nom de
la classe de style, préfixé avec un point ".".
#saveButton { . . . }
§ Il est possible de grouper les sélecteurs pour appliquer les mêmes
règles à plusieurs sélecteurs. La virgule "," est utilisée comme
séparateur.
§ Par exemple :
.button, .label {
-fx-text-fill: blue;
}
IHM-1 – FX11
– Jacques BAPST
5
IHM-1 – FX11
Classe du composant
Sélecteur de classe
Button
TextField
Hyperlink
CheckBox
.button
.text-field
.hyperlink
.check-box
7
– Jacques BAPST
Sélecteur [2]
Sélecteur de classe [2]
§ Le but des CSS est de définir le style de présentation de l'interface et
plus précisément celui de chacun des nœuds du graphe de scène
(composant UI, conteneurs, etc.).
§ Le système assigne automatiquement au nœud qui est à la racine
du graphe de scène le nom de la classe de style "root".
§ Le rôle des sélecteurs est de déterminer à quels nœuds du graphe
de scène s'appliquent les règles de style qu'ils représentent.
§ Chaque objet Node possède différentes variables ou propriétés qui
permettent de lui associer un style.
§ Le JavaFX CSS Reference Guide est disponible à l'adresse :
• styleClass : Liste de noms de classes de styles (au sens CSS) utilisés
pour appliquer à ce nœud un style basé sur un sélecteur
de classe (ObservableList<String>)
• id
• style
IHM-1 – FX11
: Identificateur unique du nœud qui permet de lui
associer un style avec un sélecteur d'Id (String)
docs.oracle.com/javase/8/javafx/api/javafx/scene/doc-files/cssref.htm
Attention : Dans ce contexte, le mot "classe" est utilisé
dans deux sens différents, ce qui peut induire
une certaine confusion.
: Règles de style que l'on peut appliquer directement
au nœud considéré (inline, sans sélecteur) (String)
– Jacques BAPST
§ Si l'on veut s'assurer du nom de la classe de style d'un composant
JavaFX ou si l'on souhaite connaître d'autres détails associés à la
définition des styles CSS, il faut consulter la documentation officielle
d'Oracle.
6
IHM-1 – FX11
– Jacques BAPST
A insérer dans
vos bookmarks !
8
Sélecteur d'Id [1]
Autres sélecteurs [1]
§ Chaque objet Node possède une propriété baptisée Id dont le but
est de donner une identification unique à chacun des nœuds d'un
graphe de scène.
§ L'astérisque ("*") représente le sélecteur universel qui s'applique à
tous les nœuds. C'est le sélecteur le moins spécifique.
• Attention, c'est au programmeur de garantir l'unicité des Id d'un graphe
de scène. Le système n'effectue aucune vérification et aucune erreur ne
sera générée en cas de doublon. Cependant, on n'obtiendra peut-être
pas les effets souhaités dans ce cas !
§ L'Id est un identificateur enregistré sous la forme d'une chaîne de
caractères (String).
-fx-text-fill: darkgreen;
}
• Sélecteur de descendant (descendant selector) :
ð
Button btnOk = new Button("OK");
ð
le sélecteur d'Id du composant sera "#AcceptButton".
– Jacques BAPST
.vbox .button { … }
Les règles s'appliquent aux boutons qui se trouvent dans un conteneur de
type VBox (pas nécessairement comme enfant direct)
• Sélecteur d'enfant (child selector) :
btnOk.setId("AcceptButton");
IHM-1 – FX11
* {
§ Des règles peuvent être définies en prenant en compte la hiérarchie
du graphe de scène :
Espace
§ Le nom du sélecteur d'Id correspond au nom de l'Id préfixé avec le
caractère dièse "#".
§ Par exemple, avec le code :
• Par exemple, si l'on veut définir les textes de tous les nœuds en vert
foncé :
9
.vbox > .button { … }
Les règles s'appliquent aux boutons qui sont directement placés dans un
conteneur de type VBox (comme enfant direct)
IHM-1 – FX11
– Jacques BAPST
11
Sélecteur d'Id [2]
Autres sélecteurs [2]
§ L'Id d'un nœud peut être exploité de différentes manières.
§ Il est également possible de définir des sélecteurs qui dépendent de
l'état du composant (nœud) auquel ils s'appliquent.
§ Dans le code, on peut, par exemple, rechercher un composant à
partir de son sélecteur d'Id.
§ On parle dans ce cas de state-based selector ou de pseudo-class
selector.
Node n = scene.lookup("#AcceptButton");
§ L'indicateur d'état est un mot-clé qui se place à droite du sélecteur
avec ":" comme séparateur.
§ Dans un fichier CSS, on peut associer des règles de style à un
sélecteur d'Id.
§ Exemple :
§ Par exemple pour changer la couleur du texte des boutons lorsque le
curseur de la souris est placé au-dessus du composant :
#AcceptButton {
-fx-text-fill: red;
}
.button:hover {
-fx-text-fill: red;
}
IHM-1 – FX11
– Jacques BAPST
10
IHM-1 – FX11
– Jacques BAPST
12
Pseudo-class
Ajout des feuilles de styles [2]
§ Pour connaître la liste de tous les états (pseudo-class) traités par
JavaFX, il faut consulter la documentation (JavaFX CSS Reference
Guide).
§ Le paramètre passé à la méthode add() correspond à l'URL qui
référence la feuille de style (le fichier xxxx.css).
§ On peut indiquer un chemin relatif ou absolu.
§ Quelques états traités
• Le chemin relatif est basé sur la racine du classpath de l'application
État (pseudo-class)
S'applique à
hover
Node
Lorsque la souris survole le composant
focused
Node
Lorsque le composant possède le focus
pressed
Node
Lorsque le bouton de la souris est pressé sur l'élément
disabled
Node
Lorsque le composant est inactivé
show-mnemonic
Node
Lorsque le mnémonique doit être affiché (souligné)
filled
Cell
Lorsque la cellule est remplie
selected
Cell
CheckBox
Lorsque le composant est sélectionné
readonly
TextInput
Control
Lorsque le composant n'est pas éditable
. . .
. . .
IHM-1 – FX11
Description
ð
• Le chemin absolu est de la forme [scheme:][//authority][path]
ð
"http://www.orion.org/app/dynamiccss/chrome.css"
§ Une autre manière d'obtenir l'URL d'une ressource et de passer par
le class-loader en invoquant la méthode getResource() de la
classe.
• Un slash initial indique un chemin absolu (depuis racine de l'application)
• Sinon le système ajoute le nom du package avant la chaîne de caractères
final URL sURL = getClass().getResource("/css/mystyle.css");
scene.getStylesheets().add(sURL.toExternalForm());
...
– Jacques BAPST
Dans la chaîne de caractère, le '/' initial est ignoré
13
IHM-1 – FX11
– Jacques BAPST
Ajout des feuilles de styles [1]
Feuilles de styles par défaut
§ Il est possible d'ajouter plusieurs feuilles de style à une application
JavaFX.
§ Il est naturellement possible de créer une application JavaFX sans
définir explicitement de feuilles de styles.
§ Des feuilles de style peuvent être ajoutées
§ Le runtime JavaFX utilise, par défaut, une feuille de style globale
appelée Modena.css (représentée par la constante STYLESHEET_MODENA
de la classe Application).
• A une scène (objet Scene)
ð
Les styles s'appliquent à la scène et à tous ses descendants
• Remarque : JavaFX 2.0 utilisait Caspian.css (constante STYLESHEET_CASPIAN)
• Aux nœuds de type Parent (conteneurs, composants, …)
ð
15
Les styles s'appliquent au nœud courant et à ses descendants
§ Pour appliquer ou changer la feuille de style globale utilisée par
défaut (appelée aussi user-agent style sheet), on peut invoquer la
méthode statique (de la classe Application) :
§ Tous les objets de type Scene et Parent maintiennent une liste
observable (ObservableList<String>) des feuilles de style qui y
sont associées.
setUserAgentStylesheet(String url)
• La méthode getStylesheets() retourne cette liste
§ Exemple :
§ Pour ajouter une feuille de style, on invoque la méthode add() de
la liste :
Application.setUserAgentStylesheet(STYLESHEET_CASPIAN);
scene.getStylesheets().add("/resources/greatstyle.css");
IHM-1 – FX11
– Jacques BAPST
14
IHM-1 – FX11
– Jacques BAPST
16
Propriété style (inline style)
Priorités [2]
§ Tous les nœuds (Node) possèdent la propriété style qui permet
d'appliquer des règles de style appelées "inline" qui s'appliqueront
spécifiquement à ce nœud (sans impact sur d'autres).
§ Les propriétés d'un nœuds peuvent être définies de plusieurs
manières (dans le code Java, dans des feuilles de style CSS ou en
appliquant des règles de style inline).
§ Les règles de styles inline sont données directement dans le code et
ne sont pas enregistrées dans une feuille de style externe.
§ La plateforme JavaFX (runtime) applique les règles de priorités
suivantes pour déterminer la valeur finale d'une propriété :
1.
2.
3.
4.
5.
§ La définition d'un style inline ne comporte pas de sélecteur mais
comprend uniquement l'ensemble des règles :
propriétés : valeur ; propriété : valeur ; …
§ Exemple :
Règles de style inline
[la plus haute priorité]
Feuilles de style du nœud Parent
Feuilles de style de la Scene
Valeurs définies dans le code (API)
Feuilles de style User-Agent
[la plus basse priorité]
Button btnOk = new Button("OK");
btnOk.setStyle("-fx-font-weight: bold; -fx-background-color: green;");
§ Les styles inline sont prioritaires sur les autres (précisions plus loin).
IHM-1 – FX11
– Jacques BAPST
17
§ Par défaut, la feuille de style User-Agent est automatiquement
appliquée, mais certaines règles peuvent être ensuite modifiées
ou complétées par des règles avec un plus haut niveau de priorité.
IHM-1 – FX11
19
– Jacques BAPST
Priorités [1]
Propriétés : Valeurs [1]
§ Si plusieurs sélecteurs de style s'appliquent au même nœud d'un
graphe de scène, des règles assez complexes déterminent la priorité
(specificity) de l'application des styles.
§ Les règles de style enregistrées dans des feuilles de style CSS ou
injectées directement (inline) sont de la forme :
Propriété : Valeur
§ Pour plus de détail, il faut consulter le document :
§ Dans les CSS JavaFX, les noms de propriétés commencent par "-fx-".
www.w3.org/TR/CSS21/cascade.html#specificity
§ Parmi ces règles, un des points à considérer est que, si un sélecteur
de classe et un sélecteur d'Id sont utilisés pour un même nœud,
c'est le sélecteur d'Id qui a la plus haute priorité.
§ Le nombre de nœuds du graphe de scène affectés par une définition
de style dépend du nombre de nœuds qui correspondent (match) au
sélecteur considéré.
IHM-1 – FX11
– Jacques BAPST
18
§ Ce préfixe est suivi par un nom de propriété CSS standard ou par le
nom de la propriété de l'API JavaFX transformé en minuscules et
avec un tiret ("-") pour séparer les mots si la propriété est un mot
composé (mixed-case).
§ Exemples :
IHM-1 – FX11
CSS/API Property
JavaFX CSS Property
font
padding
-fx-font
-fx-padding
font-size
textAlignment
-fx-font-size
-fx-text-alignment
– Jacques BAPST
Std CSS Property
20
Propriétés : Valeurs [2]
Héritage de style [2]
§ Les valeurs associées aux propriétés peuvent être de différents types
§ Exemple :
• boolean, string, number, size, percentage, duration, angle, point,
color-stop, URI, effect, font, paint, inherit, …
/*--- Parent Node (Container VBox) ---*/
#CmdPanel {
-fx-cursor: hand;
Hérité par défaut
-fx-border-color: blue;
-fx-border-width: 5px;
}
§ Les propriétés qui s'appliquent à un composant particulier sont
déterminées notamment par :
• Les propriétés définies dans le composant lui-même
• Les propriétés héritées des classes parentes
/*--- Child Node (Save Button) ---*/
#SaveButton {
-fx-border-color: red;
-fx-border-width: inherit;
}
§ Un certain nombre de propriétés CSS standard peuvent s'ajouter.
• Les CSS JavaFX sont essentiellement basées sur la spécification du W3C
CSS v2.1 avec quelques ajouts de la version CSS 3 ainsi que quelques
extensions qui sont spécifiques à JavaFX.
§ Pour connaître l'ensemble des propriétés d'un composant ainsi que
les valeurs qu'il est possible de leur associer, il faut consulter la
documentation (JavaFX CSS Reference Guide).
IHM-1 – FX11
Que pensez-vous
du résultat final ?
21
– Jacques BAPST
Hérité du
conteneur parent
IHM-1 – FX11
– Jacques BAPST
23
Compilation des feuilles de style
§ Les styles (valeurs des propriétés) d'un composant peuvent être
héritées d'un composant parent (ancêtre) selon la hiérarchie du
graphe de scène (à ne pas confondre avec la hiérarchie des classes).
§ Si les feuilles de styles deviennent volumineuses, il est possible, pour
des raisons de performance et pour alléger le déploiement, de
compiler les fichiers CSS qui se transformeront alors en fichiers BSS
(Binary Style Sheet).
> > >
Héritage de style [1]
• Par exemple, un composant UI peut hériter d'un conteneur parent
-fx-cursor,
-fx-font-family,
-fx-font-style
-fx-text-alignment,
-fx-font-size,
Compléme nt
§ Par défaut, il n'y a que quelques propriétés CSS qui sont héritées
automatiquement :
-fx-font,
-fx-font-weight,
IHM-1 – FX11
– Jacques BAPST
< < <
§ Pour les autres propriétés, il est possible de "forcer" l'héritage en
donnant la valeur "inherit" à la propriété dont on veut qu'elle
hérite du nœud parent dans le graphe de scène.
22
§ Pour compiler les feuilles de style, on peut utiliser l'utilitaire (en ligne
de commande) javapackager qui fait partie de la plateforme Java.
§ Par exemple, pour compiler le fichier dark.css en dark.bss :
javapackager -createbss -srcfiles dark.css -outdir .
§ Après compilation, il n'est pas nécessaire de modifier le code source.
• Dans le code, on continuera à charger le fichier "dark.css" mais le
système chargera effectivement le fichier "dark.bss".
IHM-1 – FX11
– Jacques BAPST
24
Règles de base du threading [1]
§ Comme la majorité des librairies d'interfaces graphiques, JavaFX
utilise un seul thread, nommé JavaFX Application Thread, pour gérer
les composants et les événements de l'interface (GUI).
Cours IHM-1
JavaFX
§ Les classes représentant les nœuds du graphe de scène ne sont pas
thread-safe.
12 - Gestion de la concurrence
Longs traitements
• Avantage
: Plus rapide, pas de synchronisation (verrou) nécessaire
• Inconvénient : Ne doivent être accédées/manipulées que depuis un
seul thread pour garantir l'intégrité de leurs données
(état, propriétés)
§ Règle :
Tous les composants qui font partie d'une scène active (live scene)
ne doivent être accédés que depuis le JavaFX Application Thread.
Jacques BAPST
• Une live scene est une scène associée à une fenêtre (Stage).
• Dans la plupart des cas, une exception est générée si cette règle est
violée (IllegalStateException).
[email protected]
IHM-1 – FX12
– Jacques BAPST
3
Règles de base du threading [2]
§ Dans une application JavaFX, il y a différents threads à considérer.
• Le thread principal (appelé main) qui exécute les instructions de la
méthode main() au lancement de l'application.
• Le JavaFX Application Thread qui exécute notamment :
Le code des méthodes start() et stop() (classe Application)
Le code des gestionnaires d'événements (contrôleurs)
ð Le code chargé du rendu graphique des éléments de l'interface
ð
JavaFX Threading
ð
• Le thread JavaFX-Launcher qui exécute notamment :
ð
Le code de la méthode init() (classe Application)
• Les Worker threads (ou Background threads) qui sont des threads
optionnels, créés explicitement dans l'application, et dans lesquels sont
exécutées les tâches qui prennent du temps, qui sont en attente d'un
signal/résultat ou qui s'exécutent à intervalle régulier.
• D'autres threads, qui sont liés à certains composants de l'architecture
JavaFX, et dont on a généralement pas à s'occuper explicitement.
ð
IHM-1 – FX12
– Jacques BAPST
2
§IHM-1 –
QuantumRenderer-n par exemple
FX12
– Jacques BAPST
4
Règles de base du threading [3]
Comment régler le problème [1]
§ Le JavaFX Application Thread se charge notamment de deux tâches
importantes :
§ La plupart des applications avec interfaces graphiques comportent
des tâches dont les temps d'exécution dépassent (potentiellement)
les temps qui font qu'une application reste réactive :
• Le rendu graphique des composants (graphical rendering)
• La gestion des événements (event handling)
•
•
•
•
•
§ Cela implique, comme corollaire, les règles suivantes :
Le code qui gère les événements (event handler) doit s'exécuter
rapidement, sinon l'interface graphique risque d'être figée et ne
plus répondre aux actions de l'utilisateur.
§ Dans certains cas, l'utilisateur ne peut effectivement rien faire
d'autre avec l'interface durant l'exécution d'une tâche longue.
Toutes les tâches qui prennent du temps doivent être exécutées
en dehors du JavaFX Application Thread.
§
§
Quand on parle d'exécution rapide des contrôleurs (gestionnaires d'événements ),
le terme "rapide" est toujours à mettre en relation avec les temps de réaction et
de perception de l'humain.
On est dans l'ordre de grandeur : t < 0.1 s … 0.3 s (selon contexte)
IHM-1 – FX12
5
– Jacques BAPST
Règles de base du threading [4]
Key Press
Critical thread
Mouse Release
Touch Move
Time-consuming code
will freeze the GUI !
Touch Release
Event Handler
AT
– Jacques BAPST
IHM-1 – FX12
– Jacques BAPST
7
§ Pour résoudre ce dilemme, la class Platform dispose d'une
méthode statique runLater(Runnable runnable) qui permet de
soumettre l'exécution du code passé en paramètre, dans le JavaFX
Application Thread.
§ L'objet Runnable (transmis en paramètre) pourra donc légitimement
contenir des instructions qui manipulent des éléments de l'interface
graphique.
GUI
Rendering
§ L'objet Runnable s'exécutera de manière asynchrone par rapport au
thread utilisateur qui invoque runLater() (main thread par exemple).
JavaFX Application Thread
IHM-1 – FX12
Modification du curseur (spinning wheel), barre de progression, …
§ Cependant, il est fréquent que ces tâches doivent effectuer
des mises à jour dans l'interface, … ce qui n'est autorisé
que dans le JavaFX Application Thread !
Mouse Move
Touch Press
ð
§ Pour résoudre le problème, il faut que les tâches longues s'exécutent
dans des threads séparés (et non dans le JavaFX Application Thread).
Mouse Press
Operating System
• Les mécanismes proposés doivent malgré tout être mis en œuvre car
sinon, certaines opérations seront bloquées (redimensionnement de la
fenêtre, gestion des occlusions, mise à jour des composants, tooltips, etc.)
• Un indicateur (feedback) doit signaler à l'utilisateur qu'il doit attendre.
Comment régler le problème [2]
Event Queue
...
Lecture et chargement de fichiers volumineux
Communication avec un serveur distant (web service, etc.)
Recherche d'informations dans une base de données, dans le cloud, …
Calcul ou algorithme complexe
...
6
IHM-1 – FX12
– Jacques BAPST
8
Comment régler le problème [3]
§ Le code sera donc inséré dans le JavaFX Application Thread et
exécuté de manière asynchrone (dans un avenir plus ou moins proche).
§ La méthode runLater() retourne immédiatement et n'attend pas
que l'objet runnable ait été exécuté.
JavaFX Worker Framework
§ Illustration du séquencement des opérations :
JavaFX
Application Thread
1
2
3
Platform.runLater(
this::redCode();
);
Instruction en cours
User's Thread
IHM-1 – FX12
4
Worker activity
– Jacques BAPST
9
IHM-1 – FX12
– Jacques BAPST
Comment régler le problème [4]
Worker framework [1]
§ Il est parfois utile de pouvoir tester si le code courant s'exécute dans
le JavaFX Application Thread ou non, on peut pour cela faire appel à
la méthode statique isFxApplicationThread() qui se trouve dans
la classe Platform.
§ Pour simplifier la gestion des applications qui comportent des
activités dont la durée est indéterminée et qui doivent cependant
pouvoir manipuler des éléments de l'interface utilisateur, JavaFX
offre différents composants (concurrent framework API) dans le
package javafx.concurrent.
§ Cela permet d'écrire du code qui pourra être exécuté sans problème
depuis différents threads (méthodes utilitaires par exemple).
§ Exemple :
. . .
if (Platform.isFxApplicationThread()) {
displayStatus(message, level);
}
else {
Platform.runLater(() -> displayStatus(message, level));
}
. . .
11
§ A la base de ce framework, il y a l'interface Worker<V> qui
représente un objet qui effectue une tâche dont l'activité est
déléguée à un ou plusieurs background threads.
§ Trois classes abstraites implémentent cette interface :
• Task<V>
: Tâche destinée à n'être utilisée qu'une seule
fois par instance
• Service<V>
: Service permettant l'exécution et la
ré-exécution d'une tâche
• ScheduledService<V> : Service permettant l'exécution périodique
d'une tâche
IHM-1 – FX12
– Jacques BAPST
10
IHM-1 – FX12
– Jacques BAPST
12
Worker Framework [2]
Worker - Propriétés [1]
§ Relations entre les éléments du framework :
§ Les objets de type Worker possèdent différentes propriétés qui
peuvent être mises à jour durant le déroulement de la tâche de fond
et qui peuvent être observées par des event handlers.
§ Ces gestionnaires d'événements peuvent, eux, mettre à jour
l'interface (car ils s'exécutent dans le JavaFX Application Thread).
§ Ce mécanisme constitue la base de la communication entre les
tâches de fond et les activités de mises à jour de l'interface (GUI).
§ Principales propriétés des worker (elles sont toutes read-only) :
• title
: Titre de la tâche (String)
§ Worker.State est un type énuméré qui répertorie chacun des états
• message
: Message indiquant l'activité courante de la tâche
(String)
§ WorkerStateEvent représente un événement qui se produit
• value
: Résultat final de la tâche ou, éventuellement,
valeur intermédiaire (<V>)
dans lesquels peuvent se trouver les objets worker.
lorsqu'un worker change d'état.
IHM-1 – FX12
– Jacques BAPST
13
IHM-1 – FX12
– Jacques BAPST
Worker Framework [3]
Worker - Propriétés [2]
§ Durant son cycle de vie, un objet worker passe par différents états.
§ Suite…
• La liste des états valides est représentée par les constantes du type
énuméré Worker.State.
§ Les transitions possibles :
IHM-1 – FX12
• workDone
: Quantité de travail déjà réalisé par la tâche (Double)
[-1 si la valeur est non définie]
Après la création
du worker
– Jacques BAPST
15
• totalWork
: Quantité totale de travail à réaliser par la tâche (Double)
[-1 si la valeur est non définie]
14
• progress
: Progression du travail de la tâche [0 … 1] (Double).
Correspond au rapport workDone / totalWork
[-1 si la valeur est non définie]
• running
: Vrai si le worker se trouve dans l'état SCHEDULED ou
RUNNING, faux sinon (Boolean).
• state
: État dans lequel se trouve le worker (Worker.State)
• exception
: Exception éventuellement générée et non catchée par
la tâche de fond.
Valeur non-nulle seulement si l'état est FAIL (Throwable).
IHM-1 – FX12
– Jacques BAPST
16
Task [1]
Task [3]
§ La classe abstraite Task<V> (qui implémente l'interface Worker) définit
dans la méthode call() les instructions qui seront exécutées en
tâche de fond (dans un thread séparé).
§ Les propriétés de la tâche que les méthodes updateppp() mettent à
jour sont éventuellement agrégées (coalesced) avant d'être injectées
dans le JavaFX Application Thread.
§ Le paramètre générique V représente le type du résultat de la tâche.
§ Concrètement, cela a comme conséquence que :
• La valeur de la propriété n'est pas nécessairement mise à jour
immédiatement. Un observateur pourra être informé avec du retard.
• Chacun des appels ne conduira pas nécessairement à la mise à jour de
la propriété. Certaines valeurs pourront être "perdues".
§ Si la tâche ne produit aucun résultat on utilisera le type Void.
§ Une instance de Task ne peut être exécutée qu'une seule fois (elle
n'est pas réutilisable).
§ Exemple d'une tâche qui produit un résultat de type Long :
§ Si des mises à jours interviennent avec un intervalle très court
(modifications en rafale), seule la dernière valeur conduira à une
modification de la propriété.
public class NextPrimeTask extends Task<Long> {
@Override
protected Long call() throws Exception {
// Implement task code here
}
. . .
§ Ce mécanisme a été mis en place pour éviter que le JavaFX
Application Thread soit submergé par une avalanche de notifications
et de traitements consécutifs à des modifications très rapides des
propriétés des workers.
}
IHM-1 – FX12
– Jacques BAPST
17
IHM-1 – FX12
– Jacques BAPST
Task [2]
Task [4]
§ Les propriétés de la tâche (i.e. celles de Worker) peuvent être mises à
jour à l'aide des méthodes spéciales suivantes :
§ La classe Task dispose d'un certain nombre de propriétés qui
permettent de réagir aux changements d'état du worker.
•
•
•
•
•
updateTitle(String title)
updateMessage(String message)
updateProgress(double workDone, double totalWork)
updateProgress(long
workDone, long
totalWork)
updateValue(V value)
•
•
•
•
•
onScheduled
onRunning
onSucceeded
onCancelled
onFailed
19
Il n'y a pas de propriété
onReady car un objet Task ne
peut pas revenir à cet état.
§ Ces méthodes ont la particularité d'effectuer la mise à jour de ces
propriétés dans le JavaFX Application Thread.
§ On peut ainsi enregistrer des gestionnaires d'événements qui seront
appelés lors de la transition entre les états du worker.
§ Ainsi toutes ces propriétés peuvent être observées (lues) dans un
contexte apte à mettre à jour de manière sûre l'interface.
§ Exemple :
. . .
myTask.setOnRunning(event -> {
lblStatus.setText("< Running... >");
lblStatus.setTextFill(Color.GREEN);
});
. . .
• C'est un des buts principaux de ces propriétés que de transmettre
des informations de la tâche de fond vers l'interface graphique.
IHM-1 – FX12
– Jacques BAPST
18
IHM-1 – FX12
– Jacques BAPST
20
Task [5]
Service [2]
§ Comme la classe Task implémente les interfaces Runnable et
FutureTask, on peut exécuter la tâche en créant un objet Thread
ou en passant par un ExecutorService.
§ Pour créer un service, on sous-classe généralement la classe
abstraite Service<V> et on implémente la méthode createTask().
• Il est naturellement aussi possible de créer la sous-classe sous forme
de classe locale anonyme.
§ Exemple 1 :
§ Création d'un service qui fournit un résultat de type Long :
. . .
//--- Launch the task by creating a background thread
Thread workerThread = new Thread(aTask);
workerThread.setDaemon(true);
workerThread.start();
. . .
public class NextPrimeService extends Service<Long> {
. . .
@Override
protected Task<Long> createTask() {
// Create and return a task
Task<Long> task = . . .
. . .
return task;
}
§ Exemple 2 :
. . .
//--- Use an Executor-Service to launch the task
ExecutorService executor = Executors.newSingleThreadExecutor();
executor.submit(aTask);
. . .
Non-daemon thread !
Execution could continue
after application is closed.
IHM-1 – FX12
– Jacques BAPST
. . .
}
21
IHM-1 – FX12
– Jacques BAPST
23
Service [1]
Service [3]
§ La classe abstraite Service<V> implémente aussi l'interface Worker
et permet d'encapsuler une tâche (Task) en la rendant facilement
réutilisable (restart).
§ La classe service dispose des propriétés de Worker (title, message,
value, progress, state, …) mais pas des méthodes de mise à jour
(updateppp()) qui sont réservées à l'objet Task.
§ Une fois démarré, la majorité des méthodes de la classe Service
sont destinées à être gérées depuis la partie UI de l'application et
doivent être exécutées dans le JavaFX Application Thread.
§ Dans le service, ces propriétés sont liées (bound) aux propriétés
correspondantes de la tâche associée. Les changements sont ainsi
propagés et peuvent être observés au niveau du service.
§ Une instance de la classe Service crée et gère un objet Task qui
effectuera le travail en tâche de fond.
§ La propriété executor peut être utilisée pour définir l'objet de type
Executor qui sera utilisé pour exécuter le service (et donc la tâche).
§ La tâche sera créée dans l'unique méthode abstraite de la classe :
§ Si la propriété executor n'est pas définie, un daemon thread sera
créé pour exécuter le service.
Task<V> createTask()
§ La méthode createTask() sera invoquée lorsque le service sera
démarré ou redémarré à l'aide des méthodes start() / restart()
qui se chargeront également de démarrer la tâche dans un thread
séparé (background thread).
IHM-1 – FX12
– Jacques BAPST
22
§ Le démarrage du service (avec création et exécution de la tâche) est
lancé avec la méthode start() (exécutable depuis n'importe quel thread).
• Le service doit se trouver dans l'état READY lors de l'invocation de la
méthode start().
IHM-1 – FX12
– Jacques BAPST
24
Service [4]
Interruption Task / Service [2]
§ La méthode restart() du service permet d'interrompre une tâche
en cours, de réinitialiser le service et de le relancer. Cette méthode
doit être invoquée depuis le JavaFX Application Thread.
§ Remarque : Dans la classe Task, on trouve une deuxième méthode
cancel() qui permet de passer en paramètre un
booléen qui indique si le flag interrupted du Thread
doit être activé ou non.
• Son appel correspond à l'invocation en séquence des trois méthodes
suivantes :
cancel()
ð reset()
ð
ð
start()
boolean cancel(boolean mayInterruptIfRunning)
Fonctionnement expliqué plus loin
Remet les propriétés à l'état initial (notamment state=READY)
(ne peut pas être invoquée si state=SCHEDULED ou RUNNING)
Doit être appelée depuis le JavaFX Application Thread
Peut être exécutée depuis n'importe quel thread
§ Dans la classe service, on trouve également les propriétés qui
permettent de réagir aux changements d'état du worker (onRunning,
onSuccedded, onFailed, …). En plus des propriétés disponibles dans la
classe Task, le service ajoute la propriété onReady (car, contrairement
à la tâche, le service peut se retrouver plusieurs fois dans cet état).
IHM-1 – FX12
– Jacques BAPST
25
§ Dans la classe Task, il est possible de redéfinir la méthode
cancelled() qui sera automatiquement invoquée lorsque la tâche
passe dans l'état CANCELLED.
• Par défaut (dans la classe parente) cette méthode ne fait rien
• La méthode est exécutée dans le JavaFX Application Thread et ses
instructions peuvent donc agir sur l'interface graphique
§ Des méthodes similaires existent également pour la transition de la
tâche vers les autres états :
• scheduled() / running() / succeeded() / failed()
IHM-1 – FX12
– Jacques BAPST
27
Interruption Task / Service [1]
Interruption Task / Service [3]
§ Si l'on souhaite interrompre prématurément une tâche exécutée
dans un background thread il faut invoquer la méthode cancel()
qui existe dans la classe Task et dans la classe Service.
§ Exemple de détection de l'interruption dans la méthode call() de
la classe Task :
@Override
protected Long call() throws Exception {
. . .
for (int i=0; i<loopMax; i++) {
// Main loop
//--- Check for cancellation
if (isCancelled()) return null;
§ Lors de l'invocation de la méthode cancel()
• L'état de l'objet Task (propriété state) est mis à CANCELLED
• Et le flag interrupted du thread est mis à l'état true.
§ L'interruption d'une tâche est basée sur la coopération et
c'est à celui qui code la tâche de faire en sorte que les états
d'interruption soient testés périodiquement.
. . .
try {
Thread.sleep(20); // Can throw InterruptedException
}
//--- Check for thread interruption
catch (InterruptedException interrupted) {
if (isCancelled()) return null;
}
§ Ces états peuvent être testés
• Avec la méthode isCancelled() de Task
• Avec la méthode isInterrupted() de Thread ou si l'exception
InterruptedException est levée lors de certains appels bloquants
(Thread.sleep(t) ou obj.wait() sont des exemples classiques)
IHM-1 – FX12
– Jacques BAPST
}
. . .
}
26
IHM-1 – FX12
– Jacques BAPST
28
ScheduledService [1]
ScheduledService [3]
§ La classe abstraite ScheduledService<V> est une sous-classe de
Service. Elle permet de créer un service qui se ré-exécute à
intervalle périodique (la tâche redémarre automatiquement).
§ Parmi les propriétés spécifiques (qui s'ajoutent à celles de Service), on
peut mentionner :
§ Ce type de service est utile dans les applications qui nécessitent la
consultation (polling) et la mise à jour périodique (cyclique) de
certaines informations, par exemple :
• Informations météo, trafic routier
• Cours de la bourse, résultats sportifs
• État des serveurs, des spoolers d'impression, notifications, etc.
• delay
: Délai initial entre le lancement du service (start) et le
début de l'exécution de la tâche (Duration).
Durant cette période le service reste dans l'état SCHEDULED.
• period
: Temps minimal entre le démarrage de la dernière
exécution et le démarrage de la suivante (Duration)
• lastValue : Valeur retournée par la dernière exécution réussie de la
tâche (<V>).
La propriété value est réinitialisée à null à chaque
lancement de la tâche et n'est donc généralement pas
d'un grand intérêt dans ce contexte.
§ Comme pour la classe Service la tâche sera créée dans la méthode
abstraite createTask().
§ Le processus de création est identique à celui de Service. On crée
une sous-classe qui hérite de ScheduledService et qui implémente
la méthode createTask().
IHM-1 – FX12
– Jacques BAPST
29
• restartOnFailure
: Indique si la tâche doit redémarrer en cas d'échec
(Boolean).
Par défaut, la tâche ne redémarre pas en cas d'échec.
IHM-1 – FX12
– Jacques BAPST
ScheduledService [2]
ScheduledService [4]
§ Le service est démarré à l'aide de la méthode start() et il sera
automatiquement redémarré à la fin de l'exécution de la tâche en
fonction de certains paramètres qui sont définis par des propriétés
spécifiques (décrites plus loin).
§ Suite…
§ Les méthodes cancel(), reset() et restart() fonctionnent
essentiellement de la même manière que celles de la classe
Service.
31
• maximumFailureCount
: Indique le nombre de fois que le service peut échouer
avant de passer dans l'état FAILED (Integer).
Si l'on redémarre le service (restart), ce compteur est
remis à zéro.
• currentFailureCount
: Indique le nombre de fois que le service a échoué depuis
son lancement (start ou restart) (Integer).
§ Après la fin d'une tâche réussie (SUCCEEDED), le service passera
successivement (et automatiquement) par les états READY, SCHEDULED,
et RUNNING.
• backOffStrategy
: Permet de spécifier le temps d'attente à ajouter à period
à chaque exécution qui se termine en échec.
(Callback<ScheduledService<?>,Duration>).
Trois stratégies sont prédéfinies :
§ Selon la configuration, il peut faire de même après la fin d'une tâche
échouée (FAILED).
EXPONENTIAL_BACKOFF_STRATEGY
LINEAR_BACKOFF_STRATEGY
LOGARITHMIC_BACKOFF_STRATEGY
IHM-1 – FX12
– Jacques BAPST
30
IHM-1 – FX12
– Jacques BAPST
32
ScheduledService [5]
§ Suite…
• cumulativePeriod
: Détermine le temps actuel entre deux exécutions après un
échec en tenant compte de la backoff strategy (Duration>).
Cette valeur est réinitialisée à la valeur de period après
chaque tâche réussie.
• maximumCumulativePeriod
: Définit un temps maximal pour le temps entre deux
exécutions en échec (Duration>).
Permet de limiter l'effet de la backoff strategy en
définissant un temps d'attente qui ne sera jamais dépassé,
même en cas de nombreux échecs successifs.
IHM-1 – FX12
– Jacques BAPST
33
ScheduledService [6]
§ Les valeurs des propriétés delay et period peuvent être modifiées
durant l'exécution de la tâche.
§ Les nouvelles valeurs seront prises en considération au prochain
lancement du service (delay) respectivement au prochain cycle de
la tâche (period).
§ Par défaut, les valeurs des propriétés delay et period sont à zéro.
• Selon l'activité de la tâche (calcul pur par exemple), s'il y a redémarrage
sans attente (period=0), la tâche risque de monopoliser le CPU et ainsi
de figer l'interface utilisateur (ce que l'on cherche à éviter !).
§ On ne peut naturellement pas compter sur ce mécanisme (valeur de
la propriété period) pour définir un temps de cycle très précis.
• Généralement, plus la valeur de period est petite et plus la précision
relative du temps de cycle diminue.
IHM-1 – FX12
– Jacques BAPST
34

Documents pareils