I. Développement d'un client pas si lourd avec JavaFX - partie 1▲
I-A. Introduction▲
Il y a quelques années, on appelait les interfaces graphiques pour desktop des clients lourds, en opposition aux clients légers qui sont dans les navigateurs. Depuis, les clients légers ont pour beaucoup pris énormément de poids et peuvent venir concurrencer ces vieux clients lourds en termes de consommation de ressources.
Voici quelques remarques que l'on peut rencontrer autour de la machine à café sur les clients lourds Java :
- ils ne sont pas beaux : c'est vrai que des fois, les développeurs n'ont pas beaucoup investi sur le UI, mais quand on voit un grand nombre de sites, on se rend compte que c'est également le cas pour le web. De plus, avec JavaFX, vous pouvez styler facilement en css vos applis, ce qui peut donner des interfaces sympas. Regardez les démos à base de JFoenix par exemple.
- ils doivent passer par des processus d'installation et de mises à jour. Un des avantages du web et de sa démocratisation est le déploiement sans intervention sur le poste utilisateur. Les applications basées sur Electron bénéficient également de cet apport, mais il existe aussi des systèmes de bootstrap pour application Java ou autres langages.
- ils sont lents et consomment énormément de ressources : ce genre de remarque peu objective se retrouve dans n'importe quel langage et très souvent, cela dépend de son usage et non pas d'un défaut intrinsèque.
- enfin, pour clôturer cette liste sur une note positive, le principal avantage d'un client Java est de bénéficier de l'écosystème très vaste du langage.
I-B. Résumé▲
Le code de l'application se trouve dans notre repository.
Dans cette première partie, nous allons voir :
- l’initialisation du code : le main ;
- les ressources (images, css et i18n) ;
- les logs ;
- une interface graphique ou GUI : Graphical User Interface, d'application simple.
I-C. Environnement de développement et ses dépendances.▲
Il faut un environnement de développement Java avec un IDE (IntelliJ dans mon cas), git et maven. Le code présenté a été développé avec le JDK 8.211.
Note : après Java 8, JavaFX a été sorti du JDK et fait partie d'un projet séparé :OpenJFX.
Nous utiliserons en plus les librairies/outils suivants :
- scenebuilder : pour dessiner les écrans de l'application. Je ne suis pas un grand fan de ces outils et je préfère éditer le code manuellement, mais il permet de prototyper rapidement votre GUI ;
- scenicview : permet la visualisation de l'arbre des composants graphiques pendant le lancement de votre programme. Cet outil est très utile pour le débogage des composants graphiques et du css. C'est votre « chrome dev tool » pour JavaFX ;
- fxlauncher : un bootstrap d'application qui permet de mettre à jour automatiquement les clients au démarrage. Il est relativement simple d'utilisation et fait le job mais il ralentit le démarrage ;
- mvvmfx : un framework pour JavaFX. Il se rapproche du pattern MVVM de C#. Je le trouve simple et efficace avec ce qu'il faut pour nos besoins actuels, à savoir la séparation de la partie graphique dans les fichiers xml, de la vue et du contrôleur (appelé ViewModel). Il apporte une injection de dépendances, un système de notification et d'événements… Il existe plein d'autres initiatives de frameworks.
II. Développement d'un outil de suivi de portefeuille financier.▲
II-A. Les besoins▲
Pour partir sur un exemple concret, nous allons développer une application de gestion de portefeuille (très simplifiée). Voici les scénarios que nous allons mettre en œuvre :
- je veux pouvoir définir par quelques caractéristiques les titres financiers ;
- je veux pouvoir acheter ou vendre ces titres à un prix et pour une quantité fixée ;
- je veux pouvoir calculer la plus-value ou la moins-value réalisée sur chacun des achats/ventes.
II-B. Une première fenêtre▲
Dans votre IDE, créez un nouveau projet Maven vide.
Nous modifions le pom.xml pour ajouter la dépendance avec le framework mvvmfx.
Vous obtenez le pom.xml suivant :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
<properties>
<project.build.sourceEncoding>
UTF-8</project.build.sourceEncoding>
<maven.compiler.source>
1.8</maven.compiler.source>
<maven.compiler.target>
1.8</maven.compiler.target>
<mvvmfx.version>
1.7.0</mvvmfx.version>
<guice.version>
4.1.0</guice.version>
</properties>
<!-- use resource files (fxml, css) from the java directory -->
<build>
<resources>
<resource>
<directory>
src/main/java</directory>
</resource>
<resource>
<directory>
src/main/resources</directory>
</resource>
</resources>
</build>
<dependencies>
<!-- MVVM framework with CDI implementation -->
<dependency>
<groupId>
de.saxsys</groupId>
<artifactId>
mvvmfx</artifactId>
<version>
${mvvmfx.version}</version>
</dependency>
<dependency>
<groupId>
de.saxsys</groupId>
<artifactId>
mvvmfx-guice</artifactId>
<version>
${mvvmfx.version}</version>
</dependency>
</dependencies>
Ajoutez ensuite une nouvelle classe qui sera le point d'entrée de l'application.
Une application standard JavaFX hérite de javafx.application.Application. Mais comme nous utilisons MVVMFX, vous devez la faire hériter de MvvmfxGuiceApplication. Pour cet exemple, nous utilisons Guice en tant qu'implémentation d'injection des dépendances, mais, comme précisé dans la documentation, ce n'est pas une obligation.
Vous devez implémenter 3 méthodes qui sont assez parlantes : init, start et stop.
Nous complétons la méthode start pour afficher une fenêtre et obtenir un aperçu de notre dur labeur.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
@Override
public
void
startMvvmfx
(
Stage stage) throws
Exception {
stage.setTitle
(
"Portefeuille très simplifié"
);
// Chargement de la vue principale
final
ViewTuple<
MainView, MainViewModel>
main =
FluentViewLoader.fxmlView
(
MainView.class
).load
(
);
Scene rootScene =
new
Scene
(
main.getView
(
));
stage.setScene
(
rootScene);
stage.show
(
);
}
et n'oubliez pas d'ajouter dans la méthode void main launch(args); qui lancera JavaFX.
Puis, il faut créer les classes correspondantes à notre première fenêtre « main » que nous plaçons dans un folder « gui ».
Du fait de l'utilisation de MVVMFX, les composants graphiques sont constitués de 3 fichiers :
- un fichier xml qui décrit l'interface graphique ;
- une classe de vue, aussi appelée « code behind », qui traite les événements graphiques ;
- une classe vue/model, aussi appelée contrôleur, qui sert de lien entre les services métiers et le composant.
Vous pouvez ajouter des snippets dans IntelliJ ou Eclipse pour aller plus vite comme indiqué dans la documentation.
Nous ajoutons une classe MainViewModel.java, vide pour l’instant puisque nous n'avons pas de code métier :
2.
3.
4.
import
de.saxsys.mvvmfx.ViewModel;
public
class
MainViewModel implements
ViewModel {
}
et une classe MainView.java, vide également, avec le lien vers le contrôleur (ViewModel) :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
import
de.saxsys.mvvmfx.FxmlView;
import
de.saxsys.mvvmfx.InjectViewModel;
public
class
MainView implements
FxmlView<
MainViewModel>
{
@InjectViewModel
private
MainViewModel viewModel;
public
void
initialize
(
) {
}
}
et enfin le fichier MainView.fxml avec une interface graphique très évoluée :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.AnchorPane?>
<AnchorPane
prefHeight
=
"400.0"
prefWidth
=
"600.0"
xmlns
=
"http://javafx.com/javafx/8.0.121"
xmlns
:
fx
=
"http://javafx.com/fxml/1"
fx
:
controller
=
"MainView"
>
<children>
<Label
text
=
"Application de gestion de portefeuille"
/>
</children>
</AnchorPane>
Voilà, vous pouvez déjà lancer l'application pour voir le magnifique écran avec votre label !
III. Personnalisation de l'interface▲
Nous allons maintenant ajouter quelques éléments de structure à notre application.
III-A. Ajout des logs▲
Ajoutez les dépendances à slf4j dans le fichier pom.xml :
2.
3.
4.
5.
6.
<!-- SLF4J -->
<dependency>
<groupId>
ch.qos.logback</groupId>
<artifactId>
logback-classic</artifactId>
<version>
1.2.3</version>
</dependency>
et dans la classe App.java, ajoutez private static final Logger LOG = LoggerFactory.getLogger(App.class); que vous pouvez utiliser dans la méthode start avec LOG.debug("L'application démarre!");.
Vous pouvez configurer le logger en ajoutant un fichier logback.xml dans le répertoire « resources » :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appender
name
=
"STDOUT"
class
=
"ch.qos.logback.core.ConsoleAppender"
>
<encoder>
<pattern>
%date [%thread] %-5level %logger - %msg%n</pattern>
</encoder>
</appender>
<logger
name
=
"com.dgc.finance"
level
=
"TRACE"
/>
<root
level
=
"debug"
>
<appender-ref
ref
=
"STDOUT"
/>
</root>
</configuration>
Note : vous pouvez aussi utiliser lombok en vous aidant de cet excellent tuto disponible sur développez.com.
III-B. Ajout de ressources▲
Évitez d'hardcoder les chaînes de caractères comme nous venons de le faire pour le titre et passez le tout dans des fichiers de ressources. Cela vous sera d'autant plus utile si vous souhaitez ajouter du multilingue. Dans votre IDE, vous pouvez ajouter des nouvelles ressources appelées « default ». Ce sont un ou plusieurs fichiers sous format clé=valeur.
Pour le titre, nous ajoutons l'entrée suivante dans le fichier de ressources : window.title=Portefeuille simplifi\u00E9.
Nous ajoutons au passage une icône d'application et un lien vers un fichier CSS.
Pour les composants JavaFX, le CSS aura des balises spécifiques. Vous pouvez retrouver leur définition dans le guide de référence.
La méthode start devient :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
@Override
public
void
startMvvmfx
(
Stage stage) throws
Exception {
LOG.debug
(
"L'application démarre!"
);
// Ajout du paquet de resources par defaut pour l'i18n
ResourceBundle resourceBundle =
ResourceBundle.getBundle
(
"default"
);
MvvmFX.setGlobalResourceBundle
(
resourceBundle);
stage.setTitle
(
resourceBundle.getString
(
"window.title"
));
// Ajout d'une icone d'application
stage.getIcons
(
).add
(
new
Image
(
getClass
(
).getResourceAsStream
(
"/appIcon.png"
)));
// Chargement de la vue principale
final
ViewTuple<
MainView, MainViewModel>
main =
FluentViewLoader.fxmlView
(
MainView.class
).load
(
);
Scene rootScene =
new
Scene
(
main.getView
(
));
// Ajout d'une feuille de style
rootScene.getStylesheets
(
).add
(
"/main.css"
);
stage.setScene
(
rootScene);
stage.show
(
);
}
et nous ajoutons une feuille de style dans les ressources avec le fichier main.css :
2.
3.
4.
5.
6.
7.
/* Main css file */
.label
{
-fx-font-family:
"Courier New"
;
-fx-font-size:
12
px;
-fx-text-fill:
crimson;
-fx-background-color:
aquamarine;
}
et pour utiliser les ressources texte dans le fichier fxml, utilisez « % » devant la clef : <Label text="%text.intro" />.
Voici l'arborescence des fichiers obtenue :
III-C. Un GUI d'application simple et classique▲
Pour dessiner le GUI, nous allons personnaliser le MainView.fxml à l'aide de scene-builder. Celui-ci peut être lancé dans l'IDE ou bien séparément. Nous remplaçons la balise AnchorPane par un BorderPane qui découpe de façon classique l'interface main et nous utilisons les instructions <fx:include ...> pour le composant menu et sous-menu.
Le MainView.fxml devient :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
<BorderPane
prefHeight
=
"600.0"
prefWidth
=
"800.0"
xmlns
=
"http://javafx.com/javafx/8.0.121"
xmlns
:
fx
=
"http://javafx.com/fxml/1"
fx
:
controller
=
"gui.main.MainView"
>
<!-- Barre de menu -->
<top>
<
fx
:
include
source
=
"../menu/MenuView.fxml"
/>
</top>
<!-- Sous-menu de navigation -->
<left>
<
fx
:
include
source
=
"../navigation/NavigationView.fxml"
/>
</left>
<!-- Contenu principale -->
<center>
<TabPane
prefHeight
=
"200.0"
prefWidth
=
"200.0"
tabClosingPolicy
=
"UNAVAILABLE"
BorderPane.
alignment
=
"CENTER"
>
<tabs>
<Tab
text
=
"Untitled Tab 1"
>
<content>
<AnchorPane
minHeight
=
"0.0"
minWidth
=
"0.0"
prefHeight
=
"180.0"
prefWidth
=
"200.0"
>
<children>
</children>
</AnchorPane>
</content>
</Tab>
<Tab
text
=
"Untitled Tab 2"
>
<content>
<AnchorPane
minHeight
=
"0.0"
minWidth
=
"0.0"
prefHeight
=
"180.0"
prefWidth
=
"200.0"
>
<children>
</children>
</AnchorPane>
</content>
</Tab>
</tabs>
</TabPane>
</center>
<!-- Barre de notification -->
<bottom>
<HBox
id
=
"HBox"
alignment
=
"CENTER_LEFT"
spacing
=
"5.0"
BorderPane.
alignment
=
"CENTER"
>
<children>
<Label
maxHeight
=
"1.7976931348623157E308"
maxWidth
=
"-1.0"
text
=
"This is the left side."
HBox.
hgrow
=
"ALWAYS"
>
<font>
<Font
size
=
"11.0"
fx
:
id
=
"x3"
/>
</font>
<textFill>
<Color
blue
=
"0.625"
green
=
"0.625"
red
=
"0.625"
fx
:
id
=
"x4"
/>
</textFill>
</Label>
<Pane
prefHeight
=
"-1.0"
prefWidth
=
"-1.0"
HBox.
hgrow
=
"ALWAYS"
/>
<Label
font
=
"$x3"
maxWidth
=
"-1.0"
text
=
"This is the right side."
textFill
=
"$x4"
HBox.
hgrow
=
"NEVER"
/>
</children>
<padding>
<Insets
bottom
=
"3.0"
left
=
"3.0"
right
=
"3.0"
top
=
"3.0"
/>
</padding>
</HBox>
</bottom>
</BorderPane>
Créez les 3 fichiers (Vue, VueModèle et xml) pour le menu et le sous-menu. Le fichier MenuView.fxml contient la description du menu et de ses entrées. Il faut aussi ajouter, dans les fichiers ressources, les textes correspondant aux clefs (menu.file, menu.help…).
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
<AnchorPane
maxHeight
=
"-Infinity"
maxWidth
=
"+Infinity"
minHeight
=
"-Infinity"
minWidth
=
"-Infinity"
xmlns
=
"http://javafx.com/javafx"
xmlns
:
fx
=
"http://javafx.com/fxml"
fx
:
controller
=
"gui.menu.MenuView"
>
<children>
<MenuBar AnchorPane.
bottomAnchor
=
"0.0"
AnchorPane.
leftAnchor
=
"0.0"
AnchorPane.
rightAnchor
=
"0.0"
AnchorPane.
topAnchor
=
"0.0"
>
<menus>
<Menu
mnemonicParsing
=
"false"
text
=
"%menu.file"
>
<items>
<MenuItem
mnemonicParsing
=
"false"
text
=
"%menu.quit"
onAction
=
"#quit"
/>
</items>
</Menu>
<Menu
mnemonicParsing
=
"false"
text
=
"%menu.help"
>
<items>
<MenuItem
mnemonicParsing
=
"false"
onAction
=
"#about"
text
=
"%menu.about"
/>
</items>
</Menu>
</menus>
</MenuBar>
</children>
</AnchorPane>
et le « code-behind » MenuView.java récupère le stage principal pour pouvoir le fermer par l'action définie dans le fichier xml avec l'attribut onAction et un # suivi du nom de la méthode.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
public
class
MenuView implements
FxmlView<
MenuViewModel>
{
@InjectViewModel
private
MenuViewModel viewModel;
@Inject
Stage primaryStage;
public
void
initialize
(
) {
}
public
void
quit
(
ActionEvent actionEvent) {
primaryStage.close
(
);
}
public
void
about
(
ActionEvent actionEvent) {
}
}
Créez également les 3 fichiers (classe ViewModel, classe View et fichier FXML) pour la barre de navigation à gauche. Laissez ces fichiers avec le contenu par défaut pour l'instant sauf l'instruction prefWidth="150.0" pour ne pas avoir un menu à gauche trop large.
III-D. Un GUI propre et moderne (Material UI)▲
Notre interface graphique est simple et efficace, mais son apparence ne fait pas trop moderne. Nous allons la changer pour appliquer les principes de material design définis par les équipes de Google.
Pour cela, nous allons utiliser la librairie Java JFoeniX à inclure dans le pom.xml :
2.
3.
4.
5.
<dependency>
<groupId>
com.jfoenix</groupId>
<artifactId>
jfoenix</artifactId>
<version>
8.0.4</version>
</dependency>
III-E. Fenêtre principale▲
Nous ajoutons les CSS et polices de caractères propres au material design dans un sous-répertoire css :
Au début du fichier material-ui.css, nous ajoutons le chargement des fonts avec :
2.
3.
4.
5.
@import
url(
"jfoenix-fonts.css"
)
;
.root
{
-fx-font-family:
"Roboto"
;
}
et nous modifions le fichier App.java pour utiliser le JFXDecorator et charger les feuilles de style :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
// Chargement de la vue principale
final
ViewTuple<
MainView, MainViewModel>
main =
FluentViewLoader.fxmlView
(
MainView.class
).load
(
);
JFXDecorator jfxDecorator =
new
JFXDecorator
(
stage, main.getView
(
));
// pour ne pas cacher la barre de tache windows quand maximisé
jfxDecorator.setCustomMaximize
(
true
);
// pour ajouter une icône à gauche du titre. Le SVG est défini en css par l'attribut --fx-shape.
jfxDecorator.setGraphic
(
new
SVGGlyph
(
));
Scene rootScene =
new
Scene
(
jfxDecorator);
// Ajout d'une feuille de style
rootScene.getStylesheets
(
).add
(
"/css/material-ui.css"
);
rootScene.getStylesheets
(
).add
(
"/css/main.css"
);
Et nous personnalisons un peu les couleurs en ajoutant le style dans material-ui :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
.jfx-decorator
{
/*-fx-decorator-color: derive(#58a5f0, -40%);*/
-fx-decorator-color:
#004c8c
;
}
.jfx-decorator
.jfx-decorator-buttons-container
{
-fx-background-color:
-fx-decorator-color;
}
.jfx-decorator
.resize-border
{
-fx-border-color:
-fx-decorator-color;
-fx-border-width:
0
4
4
4
;
}
.jfx-decorator
.jfx-decorator-title-container
.jfx-decorator-text
{
-fx-fill:
WHITE
;
-fx-font-family:
"Roboto Medium"
;
-fx-font-size:
16
;
}
.jfx-decorator
.jfx-decorator-title-container
.jfx-svg-glyph
{
/* SVG of the icon */
-fx-shape:
"M146.286 694.857q0 14.857-10.857 25.714t-25.714 10.857-25.714-10.857-10.857-25.714 10.857-25.714 25.714-10.857 25.714 10.857 10.857 25.714zM804.571 365.714q0 20-12.286 46.286t-30.571 26.857q8.571 9.714 14.286 27.143t5.714 31.714q0 39.429-30.286 68 10.286 18.286 10.286 39.429t-10 42-27.143 30q2.857 17.143 2.857 32 0 48.571-28 72t-77.714 23.429h-73.143q-74.857 0-195.429-41.714-2.857-1.143-16.571-6t-20.286-7.143-20-6.571-21.714-6.286-18.857-3.714-18-1.714h-18.286v-365.714h18.286q9.143 0 20.286-5.143t22.857-15.429 22-20.286 22.857-25.143 19.714-24.286 18-23.429 13.143-17.143q31.429-38.857 44-52 23.429-24.571 34-62.571t17.429-71.714 21.714-48.571q54.857 0 73.143 26.857t18.286 82.857q0 33.714-27.429 91.714t-27.429 91.143h201.143q28.571 0 50.857 22t22.286 51.143zM877.714 365.143q0-58.857-43.429-102.286t-102.857-43.429h-100.571q27.429-56.571 27.429-109.714 0-67.429-20-106.286-20-39.429-58.286-58t-86.286-18.571q-29.143 0-51.429 21.143-19.429 18.857-30.857 46.857t-14.571 51.714-10 48.286-17.714 36.571q-27.429 28.571-61.143 72.571-57.714 74.857-78.286 88.571h-156.571q-30.286 0-51.714 21.429t-21.429 51.714v365.714q0 30.286 21.429 51.714t51.714 21.429h164.571q12.571 0 78.857 22.857 73.143 25.143 127.429 37.714t114.286 12.571h64q80 0 129.429-45.143t48.857-123.429v-2.857q34.286-44 34.286-101.714 0-12.571-1.714-24.571 21.714-38.286 21.714-82.286 0-20.571-5.143-39.429 28-42.286 28-93.143z"
;
-jfx-size:
15
;
-fx-background-color:
WHITE
;
}
IV. Conclusion partie 1▲
À ce stade, vous devriez avoir une application qui ressemble à ceci :
IV-A. Remerciements▲
D'abord, j'adresse mes remerciements aux équipes derrière MVVMFX et les autres librairies utilisées et aussi à GluonHQ qui reprend le flambeau de JavaFX après son abandon par Oracle. Je n'oublie pas tous les contributeurs qui participent aux développements des outils et librairies.
Plus spécifiquement en ce qui concerne cet article, je tiens à remercier mon binôme de toujours Denis Garcia, l'équipe de Developpez.com et plus particulièrement Mickael Baron pour son aide et son accompagnement et escartefigue pour sa relecture avisée.