Tutoriel pour le développement d'un client desktop en JavaFX - mise en place de l'environnement de travail (partie 1)

Dans ce tutoriel, nous allons développer un client « lourd » en JavaFX. À titre d’exemple, nous développerons un logiciel de gestion de portefeuille de titres financiers.

Vu le vaste sujet, nous découperons ce guide en plusieurs parties et aborderons ici la mise en place de l'environnement de développement, les librairies utilisées et nous verrons l'affichage de la fenêtre principale.

Bonne lecture !

6 commentaires Donner une note  l'article (5)

Article lu   fois.

L'auteur

Profil ProSite personnel

Liens sociaux

Viadeo Twitter Facebook Share on Google+   

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 (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. client01

Nous modifions le pom.xml pour ajouter la dépendance avec le framework mvvmfx.

Vous obtenez le pom.xml suivant :

Pom.xml
Sélectionnez
1.
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.

client02

Nous complétons la méthode start pour afficher une fenêtre et obtenir un aperçu de notre dur labeur.

 
Sélectionnez
1.
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 :

 
Sélectionnez
1.
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) :

 
Sélectionnez
1.
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 :

 
Sélectionnez
1.
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 :

 
Sélectionnez
1.
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 » :

 
Sélectionnez
1.
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 :

 
Sélectionnez
1.
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 :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
/* Main css file */
.label {
    -fx-font-family: "Courier New";
    -fx-font-size: 12px;
    -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 :

client03-1

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 :

 
Sélectionnez
1.
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…).

 
Sélectionnez
1.
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.

 
Sélectionnez
1.
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 :

 
Sélectionnez
1.
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 :

client04

Au début du fichier material-ui.css, nous ajoutons le chargement des fonts avec :

 
Sélectionnez
1.
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 :

 
Sélectionnez
1.
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 :

 
Sélectionnez
1.
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 :

client10

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.

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

  

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2019 Gérald Colin. Aucune reproduction, même partielle, ne peut être faite de ce site ni de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.