CHAPITRE 2.1

Comprendre FXML

Découvrir FXML, le format XML pour créer des interfaces JavaFX de manière déclarative
Dans cette section, vous allez découvrir FXML, un format XML qui permet de décrire les interfaces JavaFX de manière déclarative. Vous apprendrez la structure d'un fichier FXML, ses avantages par rapport au code Java pur, et comment associer un contrôleur pour gérer la logique de votre application. FXML est l'un des outils les plus puissants de JavaFX pour séparer la présentation de la logique métier.

2.1Comprendre FXML

2.1.1 – Rôle et structure d'un fichier FXML

FXML (JavaFX Markup Language) est un format XML qui permet de décrire l'interface utilisateur d'une application JavaFX de manière déclarative. Au lieu d'écrire du code Java pour créer chaque composant, vous pouvez utiliser FXML pour définir la structure de votre interface de façon plus lisible et maintenable.

🎯 Qu'est-ce que FXML ?

FXML est un langage de balisage basé sur XML qui permet de :

  • Décrire la structure de l'interface : Définir quels composants sont présents et comment ils sont organisés
  • Séparer la vue de la logique : L'interface est dans le FXML, la logique est dans le contrôleur Java
  • Faciliter la maintenance : Modifier l'interface sans toucher au code Java
  • Permettre l'édition visuelle : Utiliser Scene Builder pour créer et modifier l'interface graphiquement
Analogie : FXML est à JavaFX ce que HTML est au web. HTML décrit la structure d'une page web, FXML décrit la structure d'une interface JavaFX.

📄 Structure de base d'un fichier FXML

Un fichier FXML minimal contient les éléments suivants :

1. En-tête XML

<?xml version="1.0" encoding="UTF-8"?>

Cette ligne indique que le fichier est un document XML en UTF-8.

2. Imports des classes JavaFX

<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.VBox?>

Les directives <?import ... ?> permettent d'importer les classes JavaFX que vous allez utiliser dans le fichier. C'est l'équivalent des import en Java.

Note : Vous devez importer chaque classe JavaFX que vous utilisez dans le FXML. Par exemple, si vous utilisez un Button, vous devez ajouter <?import javafx.scene.control.Button?>.

3. Élément racine

<VBox xmlns="http://javafx.com/javafx" xmlns:fx="http://javafx.com/fxml/1">
   <!-- Contenu ici -->
</VBox>

L'élément racine est le conteneur principal de votre interface. Il peut être un layout comme VBox, HBox, BorderPane, etc. Les attributs xmlns définissent les espaces de noms XML nécessaires.

📋 Exemple complet d'un fichier FXML minimal

Voici un exemple complet d'un fichier FXML simple qui affiche un Label :

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.VBox?>

<VBox xmlns="http://javafx.com/javafx" xmlns:fx="http://javafx.com/fxml/1">
   <children>
      <Label text="Hello JavaFX" />
   </children>
</VBox>
Explication :
<?xml ... ?> : Déclaration XML
<?import ... ?> : Import des classes JavaFX nécessaires
<VBox> : Conteneur racine qui organise les éléments verticalement
<children> : Liste des éléments enfants du VBox
<Label text="Hello JavaFX" /> : Un Label avec le texte "Hello JavaFX"

🔄 Comparaison : FXML vs Code Java

Pour mieux comprendre FXML, comparons la même interface créée de deux manières différentes :

Version Java (code impératif)

package application;

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class MonApplication extends Application {
    
    @Override
    public void start(Stage primaryStage) {
        // Créer le conteneur
        VBox root = new VBox();
        
        // Créer le Label
        Label label = new Label("Hello JavaFX");
        
        // Ajouter le Label au conteneur
        root.getChildren().add(label);
        
        // Créer la Scene
        Scene scene = new Scene(root, 400, 300);
        
        // Afficher
        primaryStage.setScene(scene);
        primaryStage.setTitle("Mon Application");
        primaryStage.show();
    }
    
    public static void main(String[] args) {
        launch(args);
    }
}

Version FXML (déclarative)

Fichier main.fxml :

<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.VBox?>

<VBox xmlns="http://javafx.com/javafx" xmlns:fx="http://javafx.com/fxml/1">
   <children>
      <Label text="Hello JavaFX" />
   </children>
</VBox>

Fichier Main.java (chargement du FXML) :

package application;

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.stage.Stage;

public class Main extends Application {
    
    @Override
    public void start(Stage primaryStage) throws Exception {
        // Charger le fichier FXML
        VBox root = FXMLLoader.load(getClass().getResource("main.fxml"));
        
        // Créer la Scene
        Scene scene = new Scene(root, 400, 300);
        
        // Afficher
        primaryStage.setScene(scene);
        primaryStage.setTitle("Mon Application");
        primaryStage.show();
    }
    
    public static void main(String[] args) {
        launch(args);
    }
}
Avantage du FXML : L'interface est séparée du code Java. Vous pouvez modifier le FXML sans recompiler le code Java, et utiliser Scene Builder pour créer l'interface visuellement.

📁 Où placer les fichiers FXML ?

Dans un projet Eclipse JavaFX créé avec e(fx)clipse, vous avez deux options pour placer vos fichiers FXML :

Option 1 : Dans le package application (structure par défaut)

Par défaut, e(fx)clipse place les fichiers directement dans le package application :

src/
└── application/
    ├── Main.java
    ├── main.fxml    # Fichier FXML ici
    └── style.css    # Fichier CSS ici

Pour charger le FXML depuis Java, utilisez :

FXMLLoader.load(getClass().getResource("main.fxml"));

Option 2 : Dans un dossier resources (organisation recommandée)

Pour une meilleure organisation, vous pouvez créer un dossier resources dans src et le configurer comme Source Folder :

src/
├── application/
│   └── Main.java
└── resources/          # Dossier à créer manuellement
    └── main.fxml       # Fichier FXML ici

Configuration importante : Pour que cette structure fonctionne, vous devez configurer le dossier resources comme Source Folder dans Eclipse :

  1. Créez le dossier resources dans src
  2. Dans Eclipse, faites un clic droit sur le dossier resources
  3. Sélectionnez Build Path → Use as Source Folder
  4. Le dossier apparaîtra alors comme un Source Folder dans votre projet

Pour charger le FXML depuis Java, utilisez :

FXMLLoader.load(getClass().getResource("/main.fxml"));
Note : Le / au début du chemin indique que le fichier est à la racine du classpath. Une fois resources configuré comme Source Folder, il devient partie du classpath, donc /main.fxml cherche dans resources/main.fxml. Si votre fichier FXML est dans un sous-dossier, par exemple src/resources/vues/accueil.fxml, utilisez getResource("/vues/accueil.fxml").

2.1.2 – Avantages du FXML vs code Java

Utiliser FXML pour créer vos interfaces JavaFX offre de nombreux avantages par rapport à la création purement en code Java. Cette section explore ces avantages en détail.

🎨 Séparation de la vue et de la logique

Le principal avantage de FXML est la séparation claire entre la présentation (vue) et la logique métier :

  • FXML : Contient uniquement la description de l'interface (quels composants, comment ils sont organisés)
  • Contrôleur Java : Contient la logique (que faire quand on clique sur un bouton, comment traiter les données)
Exemple concret :
Si vous voulez changer la couleur d'un bouton, vous modifiez le FXML ou le CSS, pas le code Java. Si vous voulez changer ce qui se passe quand on clique, vous modifiez le contrôleur Java, pas le FXML.

🔄 Réutilisabilité

Un fichier FXML peut être réutilisé dans différentes parties de votre application :

  • Composants réutilisables : Créez un composant personnalisé dans un FXML et réutilisez-le plusieurs fois
  • Modularité : Divisez votre interface en plusieurs fichiers FXML plus petits et gérables
  • Chargement dynamique : Chargez différents FXML selon le contexte de l'application

🛠️ Maintenance facilitée

FXML rend la maintenance de votre code plus simple :

  • Modifications visuelles sans recompilation : Modifiez le FXML et rechargez l'application sans recompiler le code Java
  • Code Java plus propre : Le code Java se concentre sur la logique, pas sur la création de composants
  • Lisibilité : La structure hiérarchique du FXML est plus facile à comprendre que du code Java imbriqué

👁️ Édition visuelle avec Scene Builder

FXML peut être édité visuellement avec Scene Builder :

  • Glisser-déposer : Ajoutez des composants en les glissant depuis la palette
  • Prévisualisation en temps réel : Voyez immédiatement le résultat de vos modifications
  • Configuration des propriétés : Modifiez les propriétés des composants via une interface graphique
  • Pas besoin de connaître le code : Les designers peuvent créer des interfaces sans écrire de code

📊 Comparaison détaillée

Voici un tableau comparatif entre FXML et code Java pur :

Critère Code Java FXML
Séparation vue/logique Mélangée dans le code Séparée (FXML + contrôleur)
Édition visuelle Non (code uniquement) Oui (Scene Builder)
Lisibilité Code Java imbriqué Structure XML hiérarchique
Maintenance Modifications dans le code Modifications dans FXML
Réutilisabilité Code à dupliquer Fichier FXML réutilisable
Collaboration Développeurs uniquement Développeurs + designers

💡 Quand utiliser FXML vs Code Java ?

Bien que FXML soit recommandé pour la plupart des cas, il y a des situations où le code Java peut être préférable :

✅ Utilisez FXML pour :

  • La structure principale de l'interface
  • Les écrans complets de l'application
  • Les composants réutilisables
  • Quand vous voulez utiliser Scene Builder

✅ Utilisez le code Java pour :

  • Les interfaces très simples (une fenêtre avec un Label)
  • Les composants générés dynamiquement (créés en boucle)
  • Les prototypes rapides
  • Quand la structure change beaucoup selon les données
Recommandation : Pour la plupart des applications, utilisez FXML pour la structure principale et le code Java pour les parties dynamiques. C'est le meilleur des deux mondes !

📝 Exemple comparatif complet

Voici le même formulaire de connexion créé de deux manières différentes :

Version Java (code impératif)

VBox root = new VBox(10);
root.setPadding(new Insets(20));
root.setAlignment(Pos.CENTER);

Label titre = new Label("Connexion");
titre.setFont(new Font("Arial", 24));
titre.setTextFill(Color.DARKBLUE);

TextField username = new TextField();
username.setPromptText("Nom d'utilisateur");
username.setPrefWidth(200);

PasswordField password = new PasswordField();
password.setPromptText("Mot de passe");
password.setPrefWidth(200);

Button btnConnexion = new Button("Se connecter");
btnConnexion.setPrefWidth(200);
btnConnexion.setOnAction(e -> {
    // Logique de connexion
});

root.getChildren().addAll(titre, username, password, btnConnexion);

Version FXML (déclarative)

<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.geometry.*?>

<VBox spacing="10" alignment="CENTER" xmlns="http://javafx.com/javafx" xmlns:fx="http://javafx.com/fxml/1">
   <padding>
      <Insets top="20" right="20" bottom="20" left="20"/>
   </padding>
   <children>
      <Label text="Connexion" style="-fx-font-size: 24px; -fx-text-fill: darkblue;"/>
      <TextField promptText="Nom d'utilisateur" prefWidth="200"/>
      <PasswordField promptText="Mot de passe" prefWidth="200"/>
      <Button text="Se connecter" prefWidth="200" onAction="#handleConnexion"/>
   </children>
</VBox>
Avantages de la version FXML :
• Plus lisible et structuré
• Peut être édité visuellement dans Scene Builder
• Séparation claire entre la vue et la logique (la méthode handleConnexion est dans le contrôleur)
• Plus facile à modifier sans toucher au code Java

2.1.3 – Déclaration du contrôleur (fx:controller)

Pour que votre fichier FXML puisse interagir avec le code Java, vous devez associer un contrôleur. Le contrôleur est une classe Java qui gère les événements et la logique de votre interface.

🎯 Qu'est-ce qu'un contrôleur ?

Un contrôleur est une classe Java qui :

  • Gère les événements : Répond aux clics de boutons, aux saisies dans les champs, etc.
  • Accède aux composants : Lit et modifie les valeurs des composants de l'interface
  • Contient la logique métier : Traite les données, effectue les calculs, appelle les services
Architecture MVC : Le contrôleur fait le lien entre la Vue (FXML) et le Modèle (données). C'est le "C" de MVC (Model-View-Controller).

📝 Déclarer un contrôleur dans FXML

Pour associer un contrôleur à votre fichier FXML, utilisez l'attribut fx:controller sur l'élément racine :

<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>

<VBox xmlns="http://javafx.com/javafx" xmlns:fx="http://javafx.com/fxml/1" 
      fx:controller="application.MonController">
   <children>
      <Label fx:id="monLabel" text="Hello" />
      <Button fx:id="monBouton" text="Cliquer" onAction="#handleClick" />
   </children>
</VBox>
Points importants :
fx:controller="application.MonController" : Spécifie le nom complet de la classe contrôleur
fx:id="monLabel" : Donne un identifiant au Label pour pouvoir y accéder dans le contrôleur
onAction="#handleClick" : Spécifie la méthode à appeler quand on clique sur le bouton

💻 Créer la classe contrôleur

La classe contrôleur doit respecter certaines conventions :

  • Package : Doit correspondre au package spécifié dans fx:controller
  • Annotations @FXML : Utilisez @FXML pour marquer les champs et méthodes liés au FXML
  • Méthode initialize() : Optionnelle, appelée automatiquement après le chargement du FXML

Exemple de contrôleur simple

package application;

import javafx.fxml.FXML;
import javafx.scene.control.Label;
import javafx.scene.control.Button;

public class MonController {
    
    // Référence au Label dans le FXML (fx:id="monLabel")
    @FXML
    private Label monLabel;
    
    // Référence au Button dans le FXML (fx:id="monBouton")
    @FXML
    private Button monBouton;
    
    // Méthode appelée automatiquement après le chargement du FXML
    @FXML
    private void initialize() {
        // Code d'initialisation ici
        monLabel.setText("Initialisé !");
    }
    
    // Méthode appelée quand on clique sur le bouton (onAction="#handleClick")
    @FXML
    private void handleClick() {
        monLabel.setText("Clic détecté !");
        System.out.println("Le bouton a été cliqué");
    }
}
Convention de nommage : Les méthodes liées aux événements commencent souvent par "handle" (ex: handleClick, handleSubmit). C'est une convention, pas une obligation.

🔗 Liaison FXML ↔ Contrôleur

La liaison entre le FXML et le contrôleur se fait de plusieurs manières :

1. Liaison par fx:id

Utilisez fx:id dans le FXML et un champ annoté @FXML dans le contrôleur :

Dans le FXML :

<Label fx:id="monLabel" text="Hello" />
<TextField fx:id="champNom" />
<Button fx:id="btnValider" text="Valider" />

Dans le contrôleur :

@FXML
private Label monLabel;

@FXML
private TextField champNom;

@FXML
private Button btnValider;
Important : Le nom du champ dans le contrôleur doit correspondre exactement au fx:id dans le FXML. JavaFX fait la correspondance automatiquement.

2. Liaison des événements

Utilisez onAction (ou autres attributs d'événement) dans le FXML et une méthode annotée @FXML dans le contrôleur :

Dans le FXML :

<Button text="Cliquer" onAction="#handleClick" />
<TextField onKeyPressed="#handleKeyPress" />

Dans le contrôleur :

@FXML
private void handleClick() {
    // Code exécuté quand on clique sur le bouton
}

@FXML
private void handleKeyPress(KeyEvent event) {
    // Code exécuté quand on appuie sur une touche
}
Note : Le nom de la méthode dans le contrôleur doit correspondre au nom dans onAction (sans le #). Par exemple, onAction="#handleClick" appelle la méthode handleClick().

📋 Exemple complet : Application avec contrôleur

Voici un exemple complet d'une application simple avec FXML et contrôleur :

1. Fichier main.fxml

<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>

<VBox spacing="20" alignment="CENTER" xmlns="http://javafx.com/javafx" xmlns:fx="http://javafx.com/fxml/1" 
      fx:controller="application.MonController">
   <padding>
      <Insets top="20" right="20" bottom="20" left="20"/>
   </padding>
   <children>
      <Label fx:id="labelMessage" text="Bienvenue !" />
      <TextField fx:id="champTexte" promptText="Entrez votre nom" />
      <Button fx:id="btnValider" text="Valider" onAction="#handleValider" />
   </children>
</VBox>

2. Fichier MonController.java

package application;

import javafx.fxml.FXML;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.control.Button;

public class MonController {
    
    @FXML
    private Label labelMessage;
    
    @FXML
    private TextField champTexte;
    
    @FXML
    private Button btnValider;
    
    @FXML
    private void initialize() {
        // Initialisation : désactiver le bouton au départ
        btnValider.setDisable(true);
        
        // Activer le bouton quand on tape dans le champ
        champTexte.textProperty().addListener((obs, oldVal, newVal) -> {
            btnValider.setDisable(newVal.trim().isEmpty());
        });
    }
    
    @FXML
    private void handleValider() {
        String nom = champTexte.getText();
        if (!nom.trim().isEmpty()) {
            labelMessage.setText("Bonjour, " + nom + " !");
            champTexte.clear();
        }
    }
}

3. Fichier Main.java (chargement du FXML)

package application;

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class Main extends Application {
    
    @Override
    public void start(Stage primaryStage) throws Exception {
        // Charger le fichier FXML
        FXMLLoader loader = new FXMLLoader(getClass().getResource("main.fxml"));
        VBox root = loader.load();
        
        // Créer la Scene
        Scene scene = new Scene(root, 400, 300);
        
        // Afficher
        primaryStage.setScene(scene);
        primaryStage.setTitle("Mon Application");
        primaryStage.show();
    }
    
    public static void main(String[] args) {
        launch(args);
    }
}
Fonctionnement :
1. L'application charge le fichier main.fxml
2. JavaFX crée automatiquement une instance de MonController
3. Les champs annotés @FXML sont remplis avec les références aux composants
4. La méthode initialize() est appelée automatiquement
5. Quand on clique sur "Valider", la méthode handleValider() est appelée

⚠️ Erreurs courantes

Voici les erreurs les plus courantes lors de l'utilisation de contrôleurs :

1. Nom de package incorrect

<!-- ❌ Erreur : package incorrect -->
<VBox fx:controller="monpackage.MonController">
<!-- ✅ Correct -->
<VBox fx:controller="application.MonController">

2. fx:id ne correspond pas au nom du champ

<!-- FXML -->
<Label fx:id="monLabel" />
// ❌ Erreur : nom différent
@FXML
private Label label;

// ✅ Correct
@FXML
private Label monLabel;

3. Méthode d'événement introuvable

<!-- FXML -->
<Button onAction="#handleClick" />
// ❌ Erreur : méthode non annotée @FXML ou nom incorrect
private void click() { }

// ✅ Correct
@FXML
private void handleClick() { }
Conseil : Si vous avez des erreurs, vérifiez que :
• Le package dans fx:controller correspond au package de votre classe
• Les fx:id correspondent exactement aux noms des champs
• Les méthodes d'événement sont annotées @FXML et ont le bon nom