7.1Navigation entre écrans
7.1.1 – Changer de Scene
Dans une application JavaFX, la navigation entre différents écrans est gérée par le changement de Scene. Chaque écran de votre application est défini dans un fichier FXML créé avec Scene Builder, et le code Java se contente de charger ces fichiers et de changer la Scene du Stage.
🎯 Principe : FXML pour la vue, Java pour la logique
Rappelons l'approche recommandée en JavaFX :
- FXML + Scene Builder : Créez tous vos écrans visuellement dans Scene Builder
- CSS : Définissez tous les styles dans des fichiers CSS externes
- Java : Chargez les FXML et gérez la navigation entre les Scenes
📁 Structure simple d'un projet
Pour commencer simplement, organisez vos fichiers ainsi :
src/
└── application/
├── Main.java # Point d'entrée de l'application
├── AccueilController.java # Contrôleur de l'écran d'accueil
└── ProfilController.java # Contrôleur de l'écran de profil
resources/
├── accueil.fxml # Écran d'accueil (créé avec Scene Builder)
├── profil.fxml # Écran de profil (créé avec Scene Builder)
└── style.css # Fichier de style
🎨 Étape 1 : Créer les écrans dans Scene Builder
Commençons par créer deux écrans : un écran d'accueil et un écran de profil.
Écran d'accueil (accueil.fxml)
Créez un fichier accueil.fxml dans Scene Builder :
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.VBox?>
<BorderPane xmlns="http://javafx.com/javafx"
xmlns:fx="http://javafx.com/fxml"
fx:controller="application.AccueilController"
stylesheets="style="".css"
styleClass="ecran-accueil">
<center>
<VBox alignment="CENTER" spacing="20.0">
<children>
<Label text="Bienvenue !" />
<Button fx:id="btnProfil" onAction="#allerAuProfil" text="Aller au profil" />
</children>
</VBox>
</center>
</BorderPane>
•
fx:controller : Indique quel contrôleur Java gère cet écran•
stylesheets="style="".css" : Indique quel fichier CSS utiliser•
fx:id="btnProfil" : Donne un nom au bouton pour pouvoir l'utiliser dans le contrôleur•
onAction="#allerAuProfil" : Quand on clique, appelle la méthode allerAuProfil() du contrôleur
Écran de profil (profil.fxml)
Créez un fichier profil.fxml dans Scene Builder :
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.VBox?>
<BorderPane xmlns="http://javafx.com/javafx"
xmlns:fx="http://javafx.com/fxml"
fx:controller="application.ProfilController"
stylesheets="style="".css"
styleClass="ecran-profil">
<center>
<VBox alignment="CENTER" spacing="20.0">
<children>
<Label text="Profil utilisateur" />
<Label text="Nom : Jean Dupont" />
<Label text="Email : jean@exemple.com" />
<Button fx:id="btnRetour" onAction="#retourAccueil" text="Retour à l'accueil" />
</children>
</VBox>
</center>
</BorderPane>
🎨 Étape 2 : Créer le fichier CSS
Créez un fichier simple resources/style.css :
/* Style pour les boutons */
.button {
-fx-font-size: 16px;
-fx-padding: 10px 20px;
-fx-background-color: #3498db;
-fx-text-fill: white;
}
.button:hover {
-fx-background-color: #2980b9;
}
/* Style pour les labels */
.label {
-fx-font-size: 14px;
}
/* Couleur d'arrière-plan pour l'écran d'accueil */
.ecran-accueil {
-fx-background-color: #e8f5e9; /* Vert clair */
}
/* Couleur d'arrière-plan pour l'écran de profil */
.ecran-profil {
-fx-background-color: #e3f2fd; /* Bleu clair */
}
💻 Étape 3 : Créer les contrôleurs (très simple !)
Les contrôleurs sont des classes Java qui gèrent ce qui se passe quand on clique sur les boutons. Commençons par le plus simple possible.
Contrôleur de l'écran d'accueil
Créez un fichier AccueilController.java :
package application;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
public class AccueilController {
// Ce bouton correspond au bouton dans accueil.fxml avec fx:id="btnProfil"
@FXML
private Button btnProfil;
// Cette variable stockera la référence au Stage (la fenêtre)
private Main main;
// Cette méthode est appelée quand on clique sur le bouton "Aller au profil"
@FXML
private void allerAuProfil() {
// On demande à Main de changer d'écran
if (main != null) {
main.afficherProfil();
}
}
// Cette méthode permet à Main de se connecter à ce contrôleur
public void setMain(Main main) {
this.main = main;
}
}
•
@FXML private Button btnProfil; : JavaFX va automatiquement remplir cette variable avec le bouton du FXML qui a fx:id="btnProfil"•
@FXML private void allerAuProfil() : Cette méthode est appelée automatiquement quand on clique sur le bouton (car on a mis onAction="#allerAuProfil" dans le FXML)•
main.afficherProfil() : On demande à la classe Main de changer d'écran
Contrôleur de l'écran de profil
Créez un fichier ProfilController.java :
package application;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
public class ProfilController {
@FXML
private Button btnRetour;
private Main main;
// Cette méthode est appelée quand on clique sur "Retour à l'accueil"
@FXML
private void retourAccueil() {
if (main != null) {
main.afficherAccueil();
}
}
public void setMain(Main main) {
this.main = main;
}
}
🚀 Étape 4 : Classe principale (Main.java) - La plus simple possible
La classe Main charge les FXML et change d'écran. Voici une version très simple :
package application;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class Main extends Application {
// On garde les références aux Scenes et contrôleurs
private Stage stage;
private Scene sceneAccueil;
private Scene sceneProfil;
private AccueilController accueilController;
private ProfilController profilController;
@Override
public void start(Stage primaryStage) {
this.stage = primaryStage;
try {
// Charger l'écran d'accueil
chargerAccueil();
// Afficher l'écran d'accueil au démarrage
stage.setScene(sceneAccueil);
stage.setTitle("Mon Application");
stage.setWidth(600);
stage.setHeight(400);
stage.show();
} catch (Exception e) {
e.printStackTrace();
}
}
// Méthode pour charger l'écran d'accueil
private void chargerAccueil() throws Exception {
FXMLLoader loader = new FXMLLoader(getClass().getResource("/accueil.fxml"));
sceneAccueil = new Scene(loader.load());
// Récupérer le contrôleur
accueilController = loader.getController();
// Lui donner la référence à Main
accueilController.setMain(this);
}
// Méthode pour charger l'écran de profil
private void chargerProfil() throws Exception {
// Si on ne l'a pas encore chargé, on le charge
if (sceneProfil == null) {
FXMLLoader loader = new FXMLLoader(getClass().getResource("/profil.fxml"));
sceneProfil = new Scene(loader.load());
// Récupérer le contrôleur
profilController = loader.getController();
// Lui donner la référence à Main
profilController.setMain(this);
}
}
// Méthode appelée par AccueilController pour aller au profil
public void afficherProfil() {
try {
chargerProfil();
stage.setScene(sceneProfil);
} catch (Exception e) {
e.printStackTrace();
}
}
// Méthode appelée par ProfilController pour retourner à l'accueil
public void afficherAccueil() {
stage.setScene(sceneAccueil);
}
public static void main(String[] args) {
launch(args);
}
}
•
chargerAccueil() : Charge le fichier accueil.fxml et crée une Scene•
chargerProfil() : Charge le fichier profil.fxml et crée une Scene•
afficherProfil() : Change la Scene du Stage pour afficher l'écran de profil•
afficherAccueil() : Change la Scene du Stage pour afficher l'écran d'accueil• Les contrôleurs appellent ces méthodes quand on clique sur les boutons
✅ Résumé : Comment ça fonctionne ?
Voici le flux complet, étape par étape :
- Au démarrage :
Main.start()chargeaccueil.fxmlet l'affiche - L'utilisateur clique sur "Aller au profil" dans l'écran d'accueil
- JavaFX appelle automatiquement
AccueilController.allerAuProfil() - Le contrôleur appelle
main.afficherProfil() - Main charge
profil.fxml(si pas déjà chargé) et change la Scene - L'écran change : on voit maintenant l'écran de profil
- Pour revenir : même principe avec le bouton "Retour à l'accueil"
Félicitations ! Vous savez maintenant naviguer entre deux écrans. C'est la base !
Pour aller plus loin : Dans la section suivante (7.1.2), nous verrons comment modifier le contenu d'un écran sans changer de Scene complète.
7.1.2 – Modifier le root FXML
Parfois, vous voulez changer le contenu d'un écran sans créer une nouvelle Scene complète. Au lieu de changer de Scene, vous pouvez simplement remplacer le root (l'élément racine) de votre FXML. C'est plus rapide et plus simple pour des changements de contenu dans le même écran.
🎯 Quand utiliser cette méthode ?
Modifier le root FXML est utile quand :
- Vous voulez changer le contenu d'un écran sans changer de fenêtre
- Vous avez un menu latéral qui reste fixe et seul le contenu central change
- Vous voulez éviter de créer trop de Scenes différentes
- Le changement est simple (juste remplacer une partie de l'interface)
📝 Exemple simple : Changer le contenu central
Créons un exemple très simple : un écran avec un BorderPane qui a un menu à gauche et un contenu à droite. Quand on clique sur un bouton du menu, on change juste le contenu de droite.
Étape 1 : Créer l'écran principal dans Scene Builder
Créez un fichier ecranPrincipal.fxml dans Scene Builder :
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.VBox?>
<BorderPane xmlns="http://javafx.com/javafx"
xmlns:fx="http://javafx.com/fxml"
fx:controller="application.EcranPrincipalController"
stylesheets="style="".css">
<left>
<VBox spacing="10.0">
<children>
<Button fx:id="btnAccueil" onAction="#afficherAccueil" text="Accueil" />
<Button fx:id="btnProfil" onAction="#afficherProfil" text="Profil" />
<Button fx:id="btnParametres" onAction="#afficherParametres" text="Paramètres" />
</children>
</VBox>
</left>
<center>
<VBox fx:id="zoneContenu" />
</center>
</BorderPane>
Étape 2 : Créer les contenus à afficher dans Scene Builder
Créez maintenant trois petits FXML pour les différents contenus. Commençons par le contenu d'accueil :
Créez un fichier contenuAccueil.fxml dans Scene Builder :
<?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"
spacing="20.0">
<children>
<Label text="Bienvenue sur la page d'accueil !" />
<Label text="Ceci est le contenu principal" />
</children>
</VBox>
Créez de la même manière contenuProfil.fxml et contenuParametres.fxml avec des textes différents.
Étape 3 : Le contrôleur (très simple !)
Le contrôleur charge les différents contenus et les affiche dans la zone centrale :
package application;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.layout.VBox;
import java.io.IOException;
public class EcranPrincipalController {
// La zone où on va changer le contenu
@FXML
private VBox zoneContenu;
// Méthode appelée quand on clique sur "Accueil"
@FXML
private void afficherAccueil() {
chargerContenu("contenuAccueil.fxml");
}
// Méthode appelée quand on clique sur "Profil"
@FXML
private void afficherProfil() {
chargerContenu("contenuProfil.fxml");
}
// Méthode appelée quand on clique sur "Paramètres"
@FXML
private void afficherParametres() {
chargerContenu("contenuParametres.fxml");
}
// Méthode qui charge un FXML et le met dans zoneContenu
private void chargerContenu(String cheminFXML) {
try {
// Charger le FXML
FXMLLoader loader = new FXMLLoader(getClass().getResource(cheminFXML));
VBox nouveauContenu = loader.load();
// Vider la zone de contenu actuelle
zoneContenu.getChildren().clear();
// Ajouter le nouveau contenu
zoneContenu.getChildren().add(nouveauContenu);
} catch (IOException e) {
System.err.println("Erreur lors du chargement de " + cheminFXML);
e.printStackTrace();
}
}
}
•
zoneContenu.getChildren().clear() : Enlève tout ce qui est actuellement dans la zone•
zoneContenu.getChildren().add(nouveauContenu) : Ajoute le nouveau contenu chargé• C'est comme vider une boîte et y mettre autre chose !
Étape 4 : La classe Main (très simple aussi)
La classe Main charge juste l'écran principal :
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) {
try {
// Charger l'écran principal
FXMLLoader loader = new FXMLLoader(getClass().getResource("/ecranPrincipal.fxml"));
Scene scene = new Scene(loader.load());
// Afficher la fenêtre
primaryStage.setScene(scene);
primaryStage.setTitle("Mon Application");
primaryStage.setWidth(800);
primaryStage.setHeight(600);
primaryStage.show();
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
launch(args);
}
}
🔄 Comment ça fonctionne ?
Voici ce qui se passe étape par étape :
- Au démarrage : Main charge
ecranPrincipal.fxmlqui affiche le menu à gauche et une zone vide au centre - L'utilisateur clique sur "Accueil" dans le menu
- JavaFX appelle
afficherAccueil()dans le contrôleur - Le contrôleur charge
contenuAccueil.fxml - Le contrôleur vide la zone centrale avec
clear() - Le contrôleur ajoute le nouveau contenu dans la zone
- L'écran se met à jour : le menu reste à gauche, seul le contenu de droite change !
🎨 Amélioration : Afficher un contenu par défaut
Pour afficher un contenu dès le démarrage, ajoutez une méthode initialize() dans le contrôleur :
@FXML
private void initialize() {
// Afficher le contenu d'accueil au démarrage
afficherAccueil();
}
initialize() est appelée automatiquement par JavaFX après que tous les éléments FXML ont été chargés. C'est le bon moment pour faire des initialisations.
💡 Points clés à retenir
- Modifier le root : Changez le contenu d'une zone sans changer de Scene complète
- getChildren().clear() : Vide une zone de son contenu
- getChildren().add() : Ajoute un nouveau contenu dans une zone
- FXML séparés : Créez des FXML séparés pour chaque contenu dans Scene Builder
- FXMLLoader : Utilisez cette classe pour charger les FXML de contenu
- initialize() : Méthode appelée automatiquement pour initialiser l'écran
- Couleurs distinctes : Utilisez des couleurs différentes pour chaque contenu afin de voir clairement le changement
• Changer de Scene (7.1.1) : Quand vous voulez changer complètement d'écran
• Modifier le root (7.1.2) : Quand vous voulez garder une partie de l'écran (comme un menu) et changer juste le contenu
Pour débuter, utilisez la méthode qui vous semble la plus simple pour votre cas !
7.1.3 – Structure multi-écrans simple
Quand votre application a plusieurs écrans (3, 4, 5 ou plus), il devient important d'organiser votre code de manière claire. Cette section vous montre comment structurer un projet avec plusieurs écrans de façon simple et maintenable.
🎯 Pourquoi organiser son code ?
Quand vous avez seulement 2 écrans, mettre toute la logique dans Main.java fonctionne. Mais avec plus d'écrans, cela devient vite difficile à gérer :
- Le fichier
Main.javadevient très long - Il est difficile de trouver quelle méthode charge quel écran
- Ajouter un nouvel écran nécessite de modifier plusieurs endroits
- Le code devient difficile à maintenir
NavigationManager qui centralise toute la gestion des écrans. C'est simple à comprendre et facile à utiliser !
📁 Structure organisée d'un projet
Voici comment organiser un projet avec plusieurs écrans :
src/application/
├── Main.java # Point d'entrée (très simple)
├── NavigationManager.java # Gère tous les écrans
├── AccueilController.java # Contrôleur de l'écran d'accueil
├── ProfilController.java # Contrôleur de l'écran de profil
├── ParametresController.java # Contrôleur de l'écran de paramètres
└── AProposController.java # Contrôleur de l'écran "À propos"
resources/
├── accueil.fxml
├── profil.fxml
├── parametres.fxml
├── aPropos.fxml
└── style.css
NavigationManager s'occupe de tout charger et afficher. C'est beaucoup plus clair !
💻 Étape 1 : Créer NavigationManager (le cœur du système)
La classe NavigationManager est responsable de charger tous les écrans et de les afficher. C'est elle qui centralise toute la navigation :
package application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.stage.Stage;
import java.util.HashMap;
import java.util.Map;
public class NavigationManager {
// Le Stage (la fenêtre) de l'application
private Stage stage;
// On stocke toutes les Scenes dans une Map (comme un dictionnaire)
// Clé = nom de l'écran, Valeur = la Scene
private Map<String, Scene> scenes = new HashMap<>();
// Constructeur : on lui donne le Stage
public NavigationManager(Stage stage) {
this.stage = stage;
}
// Méthode pour charger un écran (si pas déjà chargé)
private Scene chargerScene(String nomFXML, String nomEcran) {
// Si on l'a déjà chargé, on le récupère
if (scenes.containsKey(nomEcran)) {
return scenes.get(nomEcran);
}
// Sinon, on le charge
try {
FXMLLoader loader = new FXMLLoader(getClass().getResource(nomFXML));
Scene scene = new Scene(loader.load());
// On le stocke pour ne pas le recharger
scenes.put(nomEcran, scene);
// On donne la référence au contrôleur (s'il en a besoin)
Object controller = loader.getController();
if (controller instanceof AccueilController) {
((AccueilController) controller).setNavigationManager(this);
} else if (controller instanceof ProfilController) {
((ProfilController) controller).setNavigationManager(this);
} else if (controller instanceof ParametresController) {
((ParametresController) controller).setNavigationManager(this);
} else if (controller instanceof AProposController) {
((AProposController) controller).setNavigationManager(this);
}
return scene;
} catch (Exception e) {
System.err.println("Erreur lors du chargement de " + nomFXML);
e.printStackTrace();
return null;
}
}
// Méthodes publiques pour afficher chaque écran
public void afficherAccueil() {
Scene scene = chargerScene("accueil.fxml", "accueil");
if (scene != null) {
stage.setScene(scene);
}
}
public void afficherProfil() {
Scene scene = chargerScene("profil.fxml", "profil");
if (scene != null) {
stage.setScene(scene);
}
}
public void afficherParametres() {
Scene scene = chargerScene("parametres.fxml", "parametres");
if (scene != null) {
stage.setScene(scene);
}
}
public void afficherAPropos() {
Scene scene = chargerScene("aPropos.fxml", "aPropos");
if (scene != null) {
stage.setScene(scene);
}
}
}
•
Map<String, Scene> : C'est comme un dictionnaire qui stocke les écrans déjà chargés•
chargerScene() : Charge un écran s'il n'est pas déjà chargé, sinon le récupère• Les méthodes
afficherXXX() : Chaque méthode affiche un écran spécifique•
setNavigationManager() : Donne la référence au contrôleur pour qu'il puisse naviguer
💻 Étape 2 : Modifier les contrôleurs (très simple)
Les contrôleurs deviennent encore plus simples. Ils appellent juste NavigationManager :
Exemple : AccueilController
package application;
import javafx.fxml.FXML;
public class AccueilController {
private NavigationManager navigationManager;
// Méthode appelée quand on clique sur "Voir le profil"
@FXML
private void allerAuProfil() {
if (navigationManager != null) {
navigationManager.afficherProfil();
}
}
// Méthode appelée quand on clique sur "Paramètres"
@FXML
private void allerAuxParametres() {
if (navigationManager != null) {
navigationManager.afficherParametres();
}
}
// Méthode appelée quand on clique sur "À propos"
@FXML
private void allerAAPropos() {
if (navigationManager != null) {
navigationManager.afficherAPropos();
}
}
// NavigationManager appelle cette méthode pour se connecter
public void setNavigationManager(NavigationManager navigationManager) {
this.navigationManager = navigationManager;
}
}
navigationManager.afficherXXX(). Pas besoin de connaître Main ou Stage !
💻 Étape 3 : La classe Main (ultra simple)
La classe Main devient très simple. Elle crée juste le NavigationManager et affiche l'écran d'accueil :
package application;
import javafx.application.Application;
import javafx.stage.Stage;
public class Main extends Application {
@Override
public void start(Stage primaryStage) {
// Créer le gestionnaire de navigation
NavigationManager navigationManager = new NavigationManager(primaryStage);
// Afficher l'écran d'accueil
navigationManager.afficherAccueil();
// Configurer la fenêtre
primaryStage.setTitle("Mon Application Multi-Écrans");
primaryStage.setWidth(800);
primaryStage.setHeight(600);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
🎨 Étape 4 : Créer les écrans dans Scene Builder
Créez vos écrans normalement dans Scene Builder. Par exemple, pour l'écran d'accueil :
- Créez
accueil.fxmldans Scene Builder - Ajoutez un BorderPane
- Ajoutez des boutons : "Voir le profil", "Paramètres", "À propos"
- Définissez le contrôleur :
application.AccueilController - Ajoutez le CSS :
style.css - Donnez des actions aux boutons :
#allerAuProfil,#allerAuxParametres,#allerAAPropos
Faites de même pour les autres écrans (profil.fxml, parametres.fxml, aPropos.fxml).
🔄 Comment ajouter un nouvel écran ?
Pour ajouter un nouvel écran (par exemple "Contact"), suivez ces étapes :
- Créer le FXML :
contact.fxmldans Scene Builder - Créer le contrôleur :
ContactController.java - Ajouter dans NavigationManager :
- Ajouter un
else ifdanschargerScene()pour donner la référence - Ajouter la méthode
afficherContact()
- Ajouter un
- C'est tout ! Vous pouvez maintenant naviguer vers cet écran depuis n'importe où
✅ Avantages de cette structure
- Code organisé : Chaque classe a une responsabilité claire
- Facile à étendre : Pour ajouter un nouvel écran, ajoutez juste une méthode dans NavigationManager
- Contrôleurs simples : Les contrôleurs sont très courts et faciles à comprendre
- Main simple : La classe Main reste très courte
- Performance : Les écrans sont chargés une seule fois et réutilisés
💡 Points clés à retenir
- NavigationManager : Centralise toute la gestion des écrans
- Map pour stocker les Scenes : Évite de recharger les écrans plusieurs fois
- Contrôleurs simples : Ils appellent juste
navigationManager.afficherXXX() - Main très simple : Crée juste le NavigationManager et affiche l'accueil
- Facile à étendre : Ajouter un nouvel écran nécessite juste quelques lignes
- Structure claire : Chaque classe a une responsabilité bien définie
• Changer de Scene (7.1.1) : Pour 2 écrans simples
• Modifier le root (7.1.2) : Pour un menu fixe avec contenu changeant
• Structure multi-écrans (7.1.3) : Pour 3 écrans ou plus, avec une navigation organisée
Choisissez la méthode qui correspond le mieux à votre projet !