CHAPITRE 7.2

Navigation, Dialogues & Fenêtres

Maîtriser la navigation et la communication entre écrans dans JavaFX
Dans cette section, vous allez découvrir comment créer et gérer des fenêtres secondaires et des dialogues dans JavaFX. Vous apprendrez à utiliser les différents types de dialogues (Alert, TextInputDialog, ChoiceDialog), à créer des fenêtres secondaires, et à gérer la communication entre les fenêtres. Ces compétences vous permettront de créer des interfaces utilisateur plus interactives et professionnelles.

7.2Fenêtres et dialogues

7.2.1 – Alert, Confirm, TextInputDialog

Les dialogues (ou boîtes de dialogue) sont des fenêtres qui apparaissent au-dessus de votre application pour afficher des messages, demander des confirmations, ou récupérer des informations de l'utilisateur. JavaFX fournit plusieurs types de dialogues prêts à l'emploi.

🎯 Types de dialogues disponibles

JavaFX propose plusieurs types de dialogues :

  • Alert : Pour afficher des messages (information, avertissement, erreur, confirmation)
  • TextInputDialog : Pour demander à l'utilisateur de saisir du texte
  • ChoiceDialog : Pour faire choisir parmi plusieurs options
  • Dialog : Pour créer des dialogues personnalisés
Important : Les dialogues sont créés directement en Java (pas besoin de FXML). Ils sont simples à utiliser et s'affichent automatiquement au-dessus de votre application.

📝 Alert : Les boîtes de dialogue simples

La classe Alert permet d'afficher différents types de messages. Il existe plusieurs types prédéfinis :

  • INFORMATION : Pour afficher une information
  • WARNING : Pour avertir l'utilisateur
  • ERROR : Pour signaler une erreur
  • CONFIRMATION : Pour demander une confirmation (Oui/Non)
  • NONE : Pour créer un dialogue personnalisé

Exemple 1 : Alert générique (NONE)

Un Alert de type NONE vous permet de personnaliser complètement les boutons :

Exemple d'Alert NONE JavaFX
boîte de dialogue AlertType.NONE affichée
@FXML
private void showAlert() {
    Alert alert = new Alert(AlertType.NONE);
    alert.setTitle("Alert");
    alert.setHeaderText("Ceci est une boîte de dialogue Alert");
    alert.setContentText("Message d'alerte générique.");
    alert.getButtonTypes().setAll(ButtonType.OK);
    alert.showAndWait();
}
Explication :
AlertType.NONE : Type personnalisé
setTitle() : Le titre de la fenêtre
setHeaderText() : Le texte principal
setContentText() : Le texte de contenu
getButtonTypes().setAll(ButtonType.OK) : Définit les boutons (ici juste OK)
showAndWait() : Affiche le dialogue et attend que l'utilisateur clique

Exemple 2 : Alert d'information

Pour afficher une simple information :

Exemple Alert JavaFX type INFORMATION
boîte de dialogue AlertType.INFORMATION affichée
@FXML
private void showInformationAlert() {
    Alert alert = new Alert(AlertType.INFORMATION);
    alert.setTitle("Information");
    alert.setHeaderText("Information");
    alert.setContentText("Ceci est une boîte de dialogue d'information.");
    alert.showAndWait();
}
Note : Avec AlertType.INFORMATION, le bouton OK est ajouté automatiquement. Vous n'avez pas besoin de le définir manuellement.

Exemple 3 : Alert d'avertissement

Pour afficher une simple avertissement :

Exemple Alert JavaFX type WARNING
boîte de dialogue AlertType.WARNING affichée
@FXML
private void showWarningAlert() {
    Alert alert = new Alert(AlertType.WARNING);
    alert.setTitle("Avertissement");
    alert.setHeaderText("Attention !");
    alert.setContentText("Ceci est une boîte de dialogue d'avertissement.");
    alert.showAndWait();
}

Exemple 4 : Alert d'erreur

Pour afficher une simple erreur :

Exemple Alert JavaFX type ERROR
boîte de dialogue AlertType.ERROR affichée
@FXML
private void showErrorAlert() {
    Alert alert = new Alert(AlertType.ERROR);
    alert.setTitle("Erreur");
    alert.setHeaderText("Une erreur s'est produite");
    alert.setContentText("Ceci est une boîte de dialogue d'erreur.");
    alert.showAndWait();
}

Exemple 5 : Alert de confirmation

Pour demander une confirmation à l'utilisateur, utilisez CONFIRMATION et récupérez le résultat :

Exemple Alert JavaFX type CONFIRMATION
boîte de dialogue AlertType.CONFIRMATION affichée
@FXML
private void showConfirmationAlert() {
    Alert alert = new Alert(AlertType.CONFIRMATION);
    alert.setTitle("Confirmation");
    alert.setHeaderText("Confirmation requise");
    alert.setContentText("Voulez-vous continuer ?");
    
    Optional<ButtonType> result = alert.showAndWait();
    if (result.isPresent() && result.get() == ButtonType.OK) {
        System.out.println("Vous avez cliqué sur OK !");
    } else {
        System.out.println("Vous avez annulé l'opération.");
    }
}
Explication :
showAndWait() retourne un Optional<ButtonType>
result.isPresent() : Vérifie si l'utilisateur a cliqué sur un bouton (et n'a pas fermé la fenêtre)
result.get() == ButtonType.OK : Vérifie si c'est le bouton OK qui a été cliqué
• Les boutons par défaut sont OK et Annuler

Exemple 6 : Confirmation avec plusieurs boutons personnalisés

Vous pouvez créer une confirmation avec plusieurs boutons personnalisés :

Exemple Alert JavaFX type CONFIRMATION avec plusieurs boutons personnalisés
boîte de dialogue AlertType.CONFIRMATION avec plusieurs boutons personnalisés affichée
@FXML
private void showMultiButtonConfirmation() {
    Alert dialog = new Alert(Alert.AlertType.CONFIRMATION);
    dialog.setTitle("logout");
    dialog.setHeaderText(null);
    dialog.setContentText("Merci de confirmer votre choix");
    
    ButtonType yes = new ButtonType("OUI");
    ButtonType non = new ButtonType("NON");
    ButtonType later = new ButtonType("Plus tard");
    ButtonType cancel = new ButtonType("Annuler", ButtonBar.ButtonData.CANCEL_CLOSE);
    
    dialog.getButtonTypes().setAll(yes, non, later, cancel);
    
    Optional<ButtonType> answer = dialog.showAndWait();
    if (answer.isPresent()) {
        if (answer.get() == yes) {
            System.out.println("User chose OUI");
        } else if (answer.get() == non) {
            System.out.println("User chose Non");
        } else if (answer.get() == later) {
            System.out.println("User chose Plus tard");
        } else {
            System.out.println("Annuler ou fermer");
        }
    }
}
Note : Vous pouvez créer autant de boutons que vous voulez. Utilisez ButtonBar.ButtonData.CANCEL_CLOSE pour le bouton d'annulation qui fermera la fenêtre.

📝 TextInputDialog : Saisie de texte

TextInputDialog permet de demander à l'utilisateur de saisir du texte :

Exemple TextInputDialog JavaFX
boîte de dialogue TextInputDialog affichée
@FXML
private void showTextInputDialog() {
    TextInputDialog dialog = new TextInputDialog("Valeur par défaut");
    dialog.setTitle("Saisie de texte");
    dialog.setHeaderText("Entrez votre nom");
    dialog.setContentText("Nom :");
    
    Optional<String> result = dialog.showAndWait();
    result.ifPresent(name -> {
        System.out.println("Vous avez entré : " + name);
    });
}
Explication :
new TextInputDialog("Valeur par défaut") : Crée un dialogue avec une valeur par défaut
showAndWait() retourne un Optional<String>
result.ifPresent() : Exécute le code seulement si l'utilisateur a saisi quelque chose et cliqué sur OK
• Si l'utilisateur annule, result sera vide

📝 ChoiceDialog : Choix parmi plusieurs options

ChoiceDialog permet de faire choisir l'utilisateur parmi une liste d'options :

Exemple ChoiceDialog JavaFX
boîte de dialogue ChoiceDialog affichée
@FXML
private void showChoiceDialog() {
    List<String> choices = Arrays.asList("Option 1", "Option 2", "Option 3", "Option 4");
    ChoiceDialog<String> dialog = new ChoiceDialog<>("Option 1", choices);
    dialog.setTitle("Choix");
    dialog.setHeaderText("Sélectionnez une option");
    dialog.setContentText("Choisissez :");
    
    Optional<String> result = dialog.showAndWait();
    result.ifPresent(choice -> {
        System.out.println("Vous avez choisi : " + choice);
    });
}
Explication :
Arrays.asList() : Crée une liste d'options
new ChoiceDialog<>("Option 1", choices) : "Option 1" est la sélection par défaut
• L'utilisateur verra une liste déroulante avec les 4 options
• Le résultat est la chaîne choisie par l'utilisateur

📝 Dialog personnalisé : Créer votre propre dialogue

Pour créer un dialogue complètement personnalisé avec vos propres champs, utilisez la classe Dialog :

Exemple CustomDialog JavaFX
boîte de dialogue Dialog affichée
@FXML
private void showCustomDialog() {
    Dialog<String> dialog = new Dialog<>();
    dialog.setTitle("Boîte de dialogue personnalisée");
    dialog.setHeaderText("Exemple de boîte de dialogue personnalisée");
    dialog.setResizable(true);
    
    // Créer le contenu personnalisé
    Label label1 = new Label("Nom :");
    TextField textField1 = new TextField();
    Label label2 = new Label("Email :");
    TextField textField2 = new TextField();
    
    VBox vbox = new VBox(10);
    vbox.getChildren().addAll(label1, textField1, label2, textField2);
    vbox.getStyleClass().add("dialog-content");
    
    dialog.getDialogPane().setContent(vbox);
    
    // Ajouter les boutons
    ButtonType okButtonType = new ButtonType("OK", ButtonData.OK_DONE);
    ButtonType cancelButtonType = new ButtonType("Annuler", ButtonData.CANCEL_CLOSE);
    dialog.getDialogPane().getButtonTypes().addAll(okButtonType, cancelButtonType);
    
    // Désactiver le bouton OK si les champs sont vides
    Button okButton = (Button) dialog.getDialogPane().lookupButton(okButtonType);
    okButton.setDisable(true);
    
    textField1.textProperty().addListener((observable, oldValue, newValue) -> {
        okButton.setDisable(newValue.trim().isEmpty() || textField2.getText().trim().isEmpty());
    });
    
    textField2.textProperty().addListener((observable, oldValue, newValue) -> {
        okButton.setDisable(newValue.trim().isEmpty() || textField1.getText().trim().isEmpty());
    });
    
    // Convertir le résultat
    dialog.setResultConverter(dialogButton -> {
        if (dialogButton == okButtonType) {
            return "Nom: " + textField1.getText() + ", Email: " + textField2.getText();
        }
        return null;
    });
    
    Optional<String> result = dialog.showAndWait();
    result.ifPresent(data -> {
        System.out.println("Données saisies :\n" + data);
    });
}
Explication détaillée :
Dialog<String> : Le type entre <> est le type de résultat retourné
dialog.getDialogPane().setContent(vbox) : Définit le contenu personnalisé
vbox.getStyleClass().add("dialog-content") : Ajoute une classe CSS (définie dans votre fichier CSS externe)
lookupButton() : Récupère un bouton pour le modifier (désactiver, etc.)
textProperty().addListener() : Écoute les changements dans les champs de texte
setResultConverter() : Convertit le bouton cliqué en résultat (ici, une chaîne avec les données)
Important : Utilisez getStyleClass().add() pour ajouter des classes CSS au lieu de setStyle(). Définissez les styles dans votre fichier CSS externe.
Note : Vous pouvez aussi créer un contenu de Dialog personnalisé en important un fichier FXML !
Utilisez FXMLLoader pour charger le contenu (par exemple un formulaire complexe créé dans Scene Builder), puis faites dialog.getDialogPane().setContent(fxmlNode).
C'est idéal si votre dialogue nécessite une interface plus riche ou de la réutilisation de code graphique.
Important :
  • ✅ Les dialogues sont créés en Java (pas besoin de FXML)
  • ✅ Utilisez showAndWait() pour attendre la réponse de l'utilisateur
  • ✅ Le résultat est toujours un Optional (peut être vide si l'utilisateur annule)
  • ✅ Utilisez ifPresent() ou isPresent() pour vérifier si l'utilisateur a répondu

💡 Points clés à retenir

  • Alert : Pour afficher des messages simples (information, erreur, confirmation)
  • TextInputDialog : Pour demander une saisie de texte
  • ChoiceDialog : Pour faire choisir parmi plusieurs options
  • Dialog : Pour créer des dialogues complètement personnalisés
  • showAndWait() : Affiche le dialogue et attend la réponse
  • Optional : Le résultat est toujours un Optional (peut être vide)
  • ifPresent() : Méthode pratique pour traiter le résultat seulement s'il existe
Conseil : Testez chaque dialogue et regardez le code dans MainController.java pour comprendre comment ils fonctionnent. C'est le meilleur moyen d'apprendre !

7.2.2 – Stage secondaire (fenêtre enfant)

Un Stage secondaire (ou fenêtre enfant) est une fenêtre indépendante qui s'ouvre depuis votre fenêtre principale. Contrairement aux dialogues qui sont modaux (ils bloquent l'interaction avec le reste de l'application), les Stages secondaires permettent à l'utilisateur de continuer à utiliser la fenêtre principale.

🎯 Quand utiliser un Stage secondaire ?

Utilisez un Stage secondaire quand :

  • Vous voulez ouvrir une nouvelle fenêtre indépendante
  • L'utilisateur doit pouvoir utiliser les deux fenêtres en même temps
  • Vous voulez afficher un formulaire, une fenêtre de paramètres, ou une fenêtre d'aide
  • Vous avez besoin de plusieurs fenêtres ouvertes simultanément
Différence avec les dialogues : Les dialogues sont modaux (bloquent l'interaction), tandis que les Stages secondaires sont non-modaux (vous pouvez utiliser les deux fenêtres).

📝 Exemple simple : Créer une fenêtre secondaire

Créons un exemple simple : une fenêtre principale avec un bouton qui ouvre une fenêtre secondaire.

Exemple FenetreSecondaire JavaFX
fenêtre secondaire affichée

Étape 1 : Créer l'interface de la fenêtre secondaire dans Scene Builder

Créez un fichier fenetreSecondaire.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.FenetreSecondaireController"
            stylesheets="style="".css">
   <center>
      <VBox alignment="CENTER" spacing="20.0">
         <children>
            <Label text="Ceci est une fenêtre secondaire" />
            <Label text="Vous pouvez utiliser les deux fenêtres en même temps" />
            <Button fx:id="btnFermer" onAction="#fermer" text="Fermer" />
         </children>
      </VBox>
   </center>
</BorderPane>

Étape 2 : Créer le contrôleur de la fenêtre secondaire

Créez un fichier FenetreSecondaireController.java :

package application;

import javafx.fxml.FXML;
import javafx.stage.Stage;

public class FenetreSecondaireController {
    
    private Stage stage;
    
    // Cette méthode est appelée quand on clique sur "Fermer"
    @FXML
    private void fermer() {
        if (stage != null) {
            stage.close();
        }
    }
    
    // Cette méthode permet de donner la référence au Stage
    public void setStage(Stage stage) {
        this.stage = stage;
    }
}
Explication :
stage.close() : Ferme la fenêtre secondaire
setStage() : Permet à la classe qui crée la fenêtre de donner la référence au Stage

Étape 3 : Créer la fenêtre secondaire depuis le contrôleur principal

Dans votre contrôleur principal, créez une méthode pour ouvrir la fenêtre secondaire :

package application;

import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.stage.Stage;

public class MainController {
    
    private Stage stagePrincipal; // La fenêtre principale
    
    // Cette méthode est appelée quand on clique sur "Ouvrir fenêtre secondaire"
    @FXML
    private void ouvrirFenetreSecondaire() {
        try {
            // Charger le FXML de la fenêtre secondaire
            FXMLLoader loader = new FXMLLoader(getClass().getResource("fenetreSecondaire.fxml"));
            Scene scene = new Scene(loader.load());
            
            // Créer un nouveau Stage (fenêtre)
            Stage stageSecondaire = new Stage();
            stageSecondaire.setScene(scene);
            stageSecondaire.setTitle("Fenêtre secondaire");
            stageSecondaire.setWidth(400);
            stageSecondaire.setHeight(300);
            
            // Définir la fenêtre principale comme parent (optionnel mais recommandé)
            stageSecondaire.initOwner(stagePrincipal);
            
            // Donner la référence au contrôleur
            FenetreSecondaireController controller = loader.getController();
            controller.setStage(stageSecondaire);
            
            // Afficher la fenêtre
            stageSecondaire.show();
            
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    // Cette méthode permet de donner la référence au Stage principal
    public void setStagePrincipal(Stage stage) {
        this.stagePrincipal = stage;
    }
}
Explication détaillée :
new Stage() : Crée une nouvelle fenêtre
stageSecondaire.setScene(scene) : Définit le contenu de la fenêtre
initOwner(stagePrincipal) : Définit la fenêtre principale comme parent (la fenêtre secondaire se fermera si la principale se ferme)
stageSecondaire.show() : Affiche la fenêtre (non-modale par défaut)

🔧 Propriétés importantes d'un Stage secondaire

Définir le propriétaire (initOwner)

Il est recommandé de définir la fenêtre principale comme propriétaire de la fenêtre secondaire :

stageSecondaire.initOwner(stagePrincipal);

Avantages :

  • La fenêtre secondaire se ferme automatiquement si la principale se ferme
  • La fenêtre secondaire reste toujours au-dessus de la principale
  • Meilleure gestion de la hiérarchie des fenêtres

Rendre la fenêtre modale (optionnel)

Par défaut, une fenêtre secondaire est non-modale. Pour la rendre modale (comme un dialogue) :

stageSecondaire.initModality(Modality.APPLICATION_MODAL);
// ou
stageSecondaire.initModality(Modality.WINDOW_MODAL);
Types de modalité :
Modality.NONE : Non-modale (par défaut) - vous pouvez utiliser les deux fenêtres
Modality.WINDOW_MODAL : Modale par rapport à la fenêtre parente
Modality.APPLICATION_MODAL : Modale pour toute l'application

Positionner la fenêtre

Vous pouvez positionner la fenêtre secondaire par rapport à la fenêtre principale :

// Centrer la fenêtre secondaire par rapport à la principale
stageSecondaire.setX(stagePrincipal.getX() + (stagePrincipal.getWidth() - stageSecondaire.getWidth()) / 2);
stageSecondaire.setY(stagePrincipal.getY() + (stagePrincipal.getHeight() - stageSecondaire.getHeight()) / 2);

// Ou simplement utiliser centerOnScreen() pour centrer sur l'écran
stageSecondaire.centerOnScreen();

📝 Exemple complet : Fenêtre de paramètres

Créons un exemple plus complet : une fenêtre principale avec un bouton "Paramètres" qui ouvre une fenêtre secondaire de paramètres.

FenetreParametresController.java

package application;

import javafx.fxml.FXML;
import javafx.scene.control.TextField;
import javafx.stage.Stage;

public class FenetreParametresController {
    
    @FXML
    private TextField txtNom;
    
    @FXML
    private TextField txtEmail;
    
    private Stage stage;
    
    // Méthode appelée automatiquement après le chargement
    @FXML
    private void initialize() {
        // Charger les valeurs par défaut (exemple)
        txtNom.setText("Jean Dupont");
        txtEmail.setText("jean@exemple.com");
    }
    
    // Méthode appelée quand on clique sur "Enregistrer"
    @FXML
    private void enregistrer() {
        String nom = txtNom.getText();
        String email = txtEmail.getText();
        
        System.out.println("Nom : " + nom);
        System.out.println("Email : " + email);
        
        // Fermer la fenêtre après enregistrement
        if (stage != null) {
            stage.close();
        }
    }
    
    // Méthode appelée quand on clique sur "Annuler"
    @FXML
    private void annuler() {
        if (stage != null) {
            stage.close();
        }
    }
    
    public void setStage(Stage stage) {
        this.stage = stage;
    }
}

💡 Points clés à retenir

  • Stage secondaire : Une fenêtre indépendante créée avec new Stage()
  • Non-modale par défaut : L'utilisateur peut utiliser les deux fenêtres en même temps
  • initOwner() : Définit la fenêtre principale comme parent (recommandé)
  • initModality() : Pour rendre la fenêtre modale si nécessaire
  • show() : Affiche la fenêtre (non-bloquant)
  • close() : Ferme la fenêtre secondaire
  • FXML + Scene Builder : Créez l'interface de la fenêtre secondaire dans Scene Builder
Important :
  • ✅ Utilisez initOwner() pour définir la fenêtre parente
  • ✅ Donnez la référence au Stage au contrôleur pour qu'il puisse fermer la fenêtre
  • ✅ Les fenêtres secondaires sont non-modales par défaut (contrairement aux dialogues)
Conseil : Testez les fenêtres secondaires et comparez avec les dialogues de la section précédente. Vous verrez la différence : les dialogues bloquent l'interaction, les fenêtres secondaires ne le font pas (sauf si vous les rendez modales).

7.2.3 – Fenêtres modales

Une fenêtre modale est une fenêtre qui bloque l'interaction avec les autres fenêtres de l'application jusqu'à ce qu'elle soit fermée. C'est le comportement par défaut des dialogues (Alert, TextInputDialog, etc.), mais vous pouvez aussi rendre n'importe quelle fenêtre secondaire modale.

🎯 Comprendre la modalité

En JavaFX, il existe trois niveaux de modalité :

  • NONE : La fenêtre est non-modale. L'utilisateur peut interagir avec toutes les fenêtres.
  • WINDOW_MODAL : La fenêtre bloque uniquement l'interaction avec sa fenêtre parente.
  • APPLICATION_MODAL : La fenêtre bloque l'interaction avec toute l'application.
Schéma des modalités JavaFX
Les modalités JavaFX
Rappel : Par défaut, les Stages secondaires sont non-modaux (Modality.NONE). Les dialogues (Alert, etc.) sont automatiquement modaux (APPLICATION_MODAL).

📝 Exemple 1 : Fenêtre modale par rapport à la fenêtre parente (WINDOW_MODAL)

Utilisez WINDOW_MODAL quand vous voulez que la fenêtre bloque uniquement l'interaction avec sa fenêtre parente, mais permette l'utilisation d'autres fenêtres de l'application :

package application;

import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.stage.Modality;
import javafx.stage.Stage;

public class MainController {
    
    private Stage stagePrincipal;
    
    @FXML
    private void ouvrirFenetreModale() {
        try {
            // Charger le FXML de la fenêtre modale
            FXMLLoader loader = new FXMLLoader(getClass().getResource("fenetreModale.fxml"));
            Scene scene = new Scene(loader.load());
            
            // Créer un nouveau Stage
            Stage stageModale = new Stage();
            stageModale.setScene(scene);
            stageModale.setTitle("Fenêtre modale");
            stageModale.setWidth(400);
            stageModale.setHeight(300);
            
            // Définir la fenêtre principale comme parent
            stageModale.initOwner(stagePrincipal);
            
            // Rendre la fenêtre modale par rapport à la fenêtre parente
            stageModale.initModality(Modality.WINDOW_MODAL);
            
            // Donner la référence au contrôleur
            FenetreModaleController controller = loader.getController();
            controller.setStage(stageModale);
            
            // Afficher la fenêtre (bloquera l'interaction avec la fenêtre principale)
            stageModale.showAndWait();
            
            // Ce code s'exécutera seulement après la fermeture de la fenêtre modale
            System.out.println("La fenêtre modale a été fermée");
            
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    public void setStagePrincipal(Stage stage) {
        this.stagePrincipal = stage;
    }
}
Explication :
initModality(Modality.WINDOW_MODAL) : Rend la fenêtre modale par rapport à sa fenêtre parente
showAndWait() : Affiche la fenêtre et attend qu'elle soit fermée (bloque l'exécution)
• L'utilisateur ne peut pas interagir avec la fenêtre principale tant que la fenêtre modale est ouverte
• Mais il peut toujours utiliser d'autres fenêtres de l'application (si elles existent)

📝 Exemple 2 : Fenêtre modale pour toute l'application (APPLICATION_MODAL)

Utilisez APPLICATION_MODAL quand vous voulez que la fenêtre bloque l'interaction avec toute l'application :

@FXML
private void ouvrirFenetreApplicationModale() {
    try {
        FXMLLoader loader = new FXMLLoader(getClass().getResource("fenetreModale.fxml"));
        Scene scene = new Scene(loader.load());
        
        Stage stageModale = new Stage();
        stageModale.setScene(scene);
        stageModale.setTitle("Fenêtre modale (Application)");
        stageModale.setWidth(400);
        stageModale.setHeight(300);
        
        // Définir la fenêtre principale comme parent (optionnel mais recommandé)
        stageModale.initOwner(stagePrincipal);
        
        // Rendre la fenêtre modale pour toute l'application
        stageModale.initModality(Modality.APPLICATION_MODAL);
        
        FenetreModaleController controller = loader.getController();
        controller.setStage(stageModale);
        
        // Afficher et attendre
        stageModale.showAndWait();
        
        System.out.println("Fenêtre modale fermée");
        
    } catch (Exception e) {
        e.printStackTrace();
    }
}
Différence importante :
WINDOW_MODAL : Bloque uniquement la fenêtre parente
APPLICATION_MODAL : Bloque toute l'application (comme les dialogues)
APPLICATION_MODAL est le comportement par défaut des dialogues (Alert, etc.)

📝 Exemple 3 : Fenêtre non-modale (NONE)

Par défaut, les Stages secondaires sont non-modaux. Vous pouvez l'expliciter avec Modality.NONE :

@FXML
private void ouvrirFenetreNonModale() {
    try {
        FXMLLoader loader = new FXMLLoader(getClass().getResource("fenetreSecondaire.fxml"));
        Scene scene = new Scene(loader.load());
        
        Stage stageSecondaire = new Stage();
        stageSecondaire.setScene(scene);
        stageSecondaire.setTitle("Fenêtre non-modale");
        stageSecondaire.setWidth(400);
        stageSecondaire.setHeight(300);
        
        stageSecondaire.initOwner(stagePrincipal);
        
        // Expliciter que la fenêtre est non-modale (optionnel, c'est le défaut)
        stageSecondaire.initModality(Modality.NONE);
        
        FenetreSecondaireController controller = loader.getController();
        controller.setStage(stageSecondaire);
        
        // Utiliser show() au lieu de showAndWait() pour ne pas bloquer
        stageSecondaire.show();
        
        // Ce code s'exécutera immédiatement (sans attendre)
        System.out.println("Fenêtre non-modale ouverte");
        
    } catch (Exception e) {
        e.printStackTrace();
    }
}
Explication :
Modality.NONE : La fenêtre est non-modale (comportement par défaut)
show() : Affiche la fenêtre sans bloquer l'exécution
• L'utilisateur peut utiliser toutes les fenêtres en même temps
• Le code continue à s'exécuter immédiatement après show()

🔄 Comparaison : show() vs showAndWait()

La méthode que vous utilisez pour afficher la fenêtre est importante :

  • show() : Affiche la fenêtre et continue l'exécution immédiatement (non-bloquant)
  • showAndWait() : Affiche la fenêtre et attend qu'elle soit fermée (bloquant)

Exemple avec show()

@FXML
private void exempleShow() {
    Stage stage = new Stage();
    stage.setScene(new Scene(new Label("Fenêtre")));
    stage.show(); // N'attend pas
    
    // Ce code s'exécute immédiatement
    System.out.println("Fenêtre ouverte, code continue");
}

Exemple avec showAndWait()

@FXML
private void exempleShowAndWait() {
    Stage stage = new Stage();
    stage.setScene(new Scene(new Label("Fenêtre")));
    stage.showAndWait(); // Attend la fermeture
    
    // Ce code s'exécute seulement après la fermeture de la fenêtre
    System.out.println("Fenêtre fermée, code continue");
}
Conseil :
• Utilisez show() pour les fenêtres non-modales (l'utilisateur peut continuer à utiliser l'application)
• Utilisez showAndWait() pour les fenêtres modales (vous voulez attendre la réponse de l'utilisateur)
• Les dialogues utilisent automatiquement showAndWait()

📝 Exemple complet : Fenêtre modale de saisie d'informations

Créons un exemple pratique d'une fenêtre modale qui demande à l'utilisateur de saisir son nom, prénom et âge. Après avoir cliqué sur OK, les informations saisies doivent être affichées dans la fenêtre parente. Si l'utilisateur clique sur Annuler, la fenêtre modale doit simplement se fermer sans rien afficher.

Exemple complet JavaFX fenêtre modale
Exemple d'une fenêtre modale de saisie complète

FenetreSaisie.fxml

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

<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.VBox?>

<BorderPane xmlns="http://javafx.com/javafx"
            xmlns:fx="http://javafx.com/fxml"
            fx:controller="application.FenetreSaisieController"
            stylesheets="style="".css"
            prefWidth="400.0"
            prefHeight="250.0">
   <center>
      <VBox spacing="20.0" style="-fx-padding: 30px;">
         <children>
            <Label text="Saisissez vos informations" 
                   style="-fx-font-size: 18px; -fx-font-weight: bold;" />
            <GridPane hgap="10.0" vgap="15.0">
               <columnConstraints>
                  <ColumnConstraints minWidth="80.0" />
                  <ColumnConstraints minWidth="200.0" />
               </columnConstraints>
               <children>
                  <Label text="Prénom :" GridPane.columnIndex="0" GridPane.rowIndex="0" />
                  <TextField fx:id="txtPrenom" GridPane.columnIndex="1" GridPane.rowIndex="0" />
                  
                  <Label text="Nom :" GridPane.columnIndex="0" GridPane.rowIndex="1" />
                  <TextField fx:id="txtNom" GridPane.columnIndex="1" GridPane.rowIndex="1" />
                  
                  <Label text="Âge :" GridPane.columnIndex="0" GridPane.rowIndex="2" />
                  <TextField fx:id="txtAge" GridPane.columnIndex="1" GridPane.rowIndex="2" />
               </children>
            </GridPane>
            <HBox spacing="15.0" alignment="CENTER">
               <children>
                  <Button fx:id="btnOK" 
                          onAction="#valider" 
                          text="OK" 
                          style="-fx-min-width: 100px;" 
                          defaultButton="true" />
                  <Button fx:id="btnAnnuler" 
                          onAction="#annuler" 
                          text="Annuler" 
                          style="-fx-min-width: 100px;" 
                          cancelButton="true" />
               </children>
            </HBox>
         </children>
      </VBox>
   </center>
</BorderPane>

FenetreSaisieController.java

package application;

import javafx.fxml.FXML;
import javafx.scene.control.TextField;
import javafx.stage.Stage;

public class FenetreSaisieController {
    
    @FXML
    private TextField txtPrenom;
    
    @FXML
    private TextField txtNom;
    
    @FXML
    private TextField txtAge;
    
    private Stage stage;
    private boolean valide = false;
    private String prenom;
    private String nom;
    private String age;
    
    // Méthodes pour récupérer les données saisies
    public String getPrenom() {
        return prenom;
    }
    
    public String getNom() {
        return nom;
    }
    
    public String getAge() {
        return age;
    }
    
    // Méthode pour vérifier si l'utilisateur a validé
    public boolean estValide() {
        return valide;
    }
    
    @FXML
    private void valider() {
        // Récupérer les valeurs saisies
        prenom = txtPrenom.getText().trim();
        nom = txtNom.getText().trim();
        age = txtAge.getText().trim();
        
        // Vérifier que les champs ne sont pas vides
        if (prenom.isEmpty() || nom.isEmpty() || age.isEmpty()) {
            // Ne pas fermer la fenêtre, l'utilisateur doit remplir tous les champs
            return;
        }
        
        valide = true;
        if (stage != null) {
            stage.close();
        }
    }
    
    @FXML
    private void annuler() {
        valide = false;
        if (stage != null) {
            stage.close();
        }
    }
    
    public void setStage(Stage stage) {
        this.stage = stage;
    }
}

style.css

Créez un fichier style.css dans le même package que vos fichiers FXML pour définir les styles :

/* Styles pour les messages */
.message-success {
    -fx-text-fill: green;
}

.message-warning {
    -fx-text-fill: orange;
}

/* Style pour le contenu des dialogues */
.dialog-content {
    -fx-padding: 20px;
}

/* Styles généraux */
Label {
    -fx-font-size: 14px;
}

Button {
    -fx-font-size: 14px;
    -fx-padding: 8px 16px;
}

TextField {
    -fx-font-size: 14px;
    -fx-padding: 5px;
}
Note : Le fichier CSS est référencé dans les fichiers FXML avec stylesheets="style="".css". Assurez-vous que le fichier CSS se trouve dans le même répertoire que les fichiers FXML (dans le package application).

main.fxml (fenêtre principale)

Dans votre fenêtre principale, vous devez avoir des Labels pour afficher les informations et les messages :

<?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.MainController"
            stylesheets="style="".css"
            prefWidth="600.0"
            prefHeight="400.0">
   <center>
      <VBox spacing="20.0" alignment="CENTER" style="-fx-padding: 30px;">
         <children>
            <Label text="Exemple Fenêtre Modale" 
                   style="-fx-font-size: 20px; -fx-font-weight: bold;" />
            <Button fx:id="btnOuvrirSaisie" 
                    onAction="#ouvrirFenetreSaisie" 
                    text="Saisir mes informations" 
                    style="-fx-min-width: 200px; -fx-min-height: 40px;" />
            <Label fx:id="lblMessage" 
                   text="" 
                   style="-fx-font-size: 14px;" />
            <Label fx:id="lblAffichage" 
                   text="" 
                   style="-fx-font-size: 16px; -fx-font-weight: bold;" 
                   wrapText="true" />
         </children>
      </VBox>
   </center>
</BorderPane>

Utilisation dans le contrôleur principal

package application;

import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.stage.Modality;
import javafx.stage.Stage;

public class MainController {
    
    @FXML
    private Label lblAffichage; // Label dans la fenêtre principale pour afficher les infos
    
    @FXML
    private Label lblMessage; // Label pour afficher les messages (erreurs, annulation, etc.)
    
    private Stage stagePrincipal;
    
    @FXML
    private void ouvrirFenetreSaisie() {
        try {
            // Charger la fenêtre de saisie
            FXMLLoader loader = new FXMLLoader(getClass().getResource("FenetreSaisie.fxml"));
            Scene scene = new Scene(loader.load());
            
            // Créer le Stage modale
            Stage stageSaisie = new Stage();
            stageSaisie.setScene(scene);
            stageSaisie.setTitle("Saisie d'informations");
            stageSaisie.setResizable(false); // Taille définie dans FXML
            
            // Définir comme modale pour toute l'application
            stageSaisie.initOwner(stagePrincipal);
            stageSaisie.initModality(Modality.APPLICATION_MODAL);
            
            // Configurer le contrôleur
            FenetreSaisieController controller = loader.getController();
            controller.setStage(stageSaisie);
            
            // Afficher et attendre la réponse
            stageSaisie.showAndWait();
            
            // Vérifier le résultat
            if (controller.estValide()) {
                // L'utilisateur a cliqué sur OK et les champs sont remplis
                String prenom = controller.getPrenom();
                String nom = controller.getNom();
                String age = controller.getAge();
                
                // Afficher les informations dans la fenêtre parente
                String message = String.format("Prénom : %s\nNom : %s\nÂge : %s ans", 
                                                prenom, nom, age);
                lblAffichage.setText(message);
                
                // Afficher un message de confirmation
                if (lblMessage != null) {
                    lblMessage.setText("Informations saisies avec succès !");
                    lblMessage.getStyleClass().setAll("message-success");
                }
            } else {
                // L'utilisateur a cliqué sur Annuler ou fermé la fenêtre
                if (lblMessage != null) {
                    lblMessage.setText("Saisie annulée");
                    lblMessage.getStyleClass().setAll("message-warning");
                }
                // Effacer l'affichage précédent
                lblAffichage.setText("");
            }
            
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    public void setStagePrincipal(Stage stage) {
        this.stagePrincipal = stage;
    }
}
Explication :
• La fenêtre de saisie est modale (APPLICATION_MODAL)
• L'utilisateur doit remplir les champs et cliquer sur OK ou Annuler
• Le contrôleur stocke les données saisies dans des variables privées
• Après showAndWait(), on vérifie si l'utilisateur a validé
• Si OK : on récupère les données et on les affiche dans lblAffichage de la fenêtre parente
• Un message de confirmation s'affiche dans lblMessage avec la classe CSS message-success
• Si Annuler : un message d'annulation s'affiche dans lblMessage avec la classe CSS message-warning, et lblAffichage est effacé
• Les méthodes getPrenom(), getNom(), getAge() permettent de récupérer les données après la fermeture de la fenêtre
• Tous les messages sont affichés dans la fenêtre principale via des Labels, pas dans la console
Important : La taille de la fenêtre est définie dans le FXML avec prefWidth="400.0" et prefHeight="250.0" sur le BorderPane. Ne pas utiliser setWidth() et setHeight() dans le code Java.
Important : Utilisez getStyleClass().setAll() pour définir les classes CSS au lieu de setStyle(). Définissez les styles dans votre fichier CSS externe (ex: .message-success { -fx-text-fill: green; } et .message-warning { -fx-text-fill: orange; })

💡 Quand utiliser une fenêtre modale ?

Utilisez une fenêtre modale quand :

  • Vous avez besoin d'une confirmation avant une action importante
  • Vous voulez que l'utilisateur saisisse des informations avant de continuer
  • Vous voulez afficher un message critique qui nécessite l'attention de l'utilisateur
  • Vous avez besoin d'une interface personnalisée (plus complexe qu'un simple Alert)

N'utilisez pas une fenêtre modale pour :

  • Des fenêtres d'aide ou de documentation (l'utilisateur doit pouvoir consulter l'aide tout en utilisant l'application)
  • Des fenêtres de paramètres simples (sauf si vous voulez forcer la configuration avant de continuer)
  • Des fenêtres d'information non-critiques
Bonnes pratiques :
  • ✅ Utilisez APPLICATION_MODAL pour les confirmations critiques
  • ✅ Utilisez WINDOW_MODAL pour les fenêtres liées à une fenêtre spécifique
  • ✅ Utilisez NONE (ou show()) pour les fenêtres d'aide, de paramètres non-critiques
  • ✅ Utilisez showAndWait() avec les fenêtres modales pour récupérer le résultat
  • ✅ Donnez toujours un moyen à l'utilisateur de fermer la fenêtre (bouton Annuler, etc.)

💡 Points clés à retenir

  • Modality.NONE : Fenêtre non-modale (par défaut pour les Stages)
  • Modality.WINDOW_MODAL : Bloque uniquement la fenêtre parente
  • Modality.APPLICATION_MODAL : Bloque toute l'application (comme les dialogues)
  • show() : Affiche sans bloquer (pour les fenêtres non-modales)
  • showAndWait() : Affiche et attend (pour les fenêtres modales)
  • initModality() : Définit le niveau de modalité
  • initOwner() : Définit la fenêtre parente (nécessaire pour WINDOW_MODAL)
Conseil : Testez les différents niveaux de modalité pour comprendre la différence. Créez plusieurs fenêtres et essayez d'interagir avec elles selon leur niveau de modalité. C'est la meilleure façon de comprendre quand utiliser chaque type !

7.2.4 – FileChooser (ouvrir/enregistrer un fichier)

Le FileChooser est un dialogue système qui permet à l'utilisateur de sélectionner un fichier sur son ordinateur. Il est très utile pour ouvrir des fichiers existants ou enregistrer de nouveaux fichiers. JavaFX fournit la classe FileChooser qui affiche automatiquement le dialogue natif du système d'exploitation.

Illustration du FileChooser JavaFX
Exemple d'utilisation du FileChooser sous JavaFX

🎯 Quand utiliser FileChooser ?

Utilisez FileChooser quand :

  • Vous voulez permettre à l'utilisateur d'ouvrir un fichier (image, document, etc.)
  • Vous voulez permettre à l'utilisateur d'enregistrer un fichier
  • Vous avez besoin d'un dialogue natif du système (Windows, Mac, Linux)
  • Vous voulez filtrer les types de fichiers (par exemple, seulement les images)
Important : Le FileChooser utilise le dialogue natif du système d'exploitation. Sur Windows, vous verrez l'explorateur Windows, sur Mac le Finder, et sur Linux le gestionnaire de fichiers par défaut.

📝 Exemple 1 : Ouvrir un fichier

Pour permettre à l'utilisateur d'ouvrir un fichier, utilisez showOpenDialog() :

package application;

import javafx.fxml.FXML;
import javafx.scene.control.Label;
import javafx.stage.FileChooser;
import javafx.stage.Stage;

import java.io.File;

public class MainController {
    
    private Stage stagePrincipal;
    
    @FXML
    private Label lblFichier; // Label pour afficher le fichier sélectionné
    
    @FXML
    private void ouvrirFichier() {
        FileChooser fileChooser = new FileChooser();
        fileChooser.setTitle("Ouvrir un fichier");
        
        // Optionnel : définir le répertoire initial
        fileChooser.setInitialDirectory(new File(System.getProperty("user.home")));
        
        // Afficher le dialogue et récupérer le fichier sélectionné
        File fichierSelectionne = fileChooser.showOpenDialog(stagePrincipal);
        
        // Vérifier si l'utilisateur a sélectionné un fichier
        if (fichierSelectionne != null) {
            String chemin = fichierSelectionne.getAbsolutePath();
            lblFichier.setText("Fichier sélectionné : " + chemin);
            System.out.println("Fichier ouvert : " + chemin);
        } else {
            lblFichier.setText("Aucun fichier sélectionné");
            System.out.println("L'utilisateur a annulé");
        }
    }
    
    public void setStagePrincipal(Stage stage) {
        this.stagePrincipal = stage;
    }
}
Explication :
new FileChooser() : Crée un nouveau FileChooser
setTitle() : Définit le titre du dialogue
setInitialDirectory() : Définit le répertoire initial (optionnel)
showOpenDialog(stagePrincipal) : Affiche le dialogue d'ouverture et retourne le fichier sélectionné
• Le résultat est un File ou null si l'utilisateur a annulé
getAbsolutePath() : Récupère le chemin complet du fichier

📝 Exemple 2 : Enregistrer un fichier

Pour permettre à l'utilisateur d'enregistrer un fichier, utilisez showSaveDialog() :

@FXML
private void enregistrerFichier() {
    FileChooser fileChooser = new FileChooser();
    fileChooser.setTitle("Enregistrer un fichier");
    
    // Optionnel : définir le nom de fichier par défaut
    fileChooser.setInitialFileName("mon_fichier.txt");
    
    // Optionnel : définir le répertoire initial
    fileChooser.setInitialDirectory(new File(System.getProperty("user.home")));
    
    // Afficher le dialogue d'enregistrement
    File fichierSelectionne = fileChooser.showSaveDialog(stagePrincipal);
    
    if (fichierSelectionne != null) {
        String chemin = fichierSelectionne.getAbsolutePath();
        lblFichier.setText("Fichier à enregistrer : " + chemin);
        System.out.println("Fichier à enregistrer : " + chemin);
        
        // Ici, vous pouvez écrire dans le fichier
        // Par exemple : Files.write(fichierSelectionne.toPath(), contenu.getBytes());
    } else {
        lblFichier.setText("Enregistrement annulé");
        System.out.println("L'utilisateur a annulé l'enregistrement");
    }
}
Explication :
showSaveDialog(stagePrincipal) : Affiche le dialogue d'enregistrement
setInitialFileName() : Définit un nom de fichier par défaut (optionnel)
• Le dialogue permet à l'utilisateur de choisir l'emplacement et le nom du fichier
• Si l'utilisateur confirme, vous pouvez ensuite écrire dans le fichier

📝 Exemple 3 : Filtrer les types de fichiers

Vous pouvez limiter les types de fichiers que l'utilisateur peut sélectionner en ajoutant des filtres d'extension :

@FXML
private void ouvrirImage() {
    FileChooser fileChooser = new FileChooser();
    fileChooser.setTitle("Ouvrir une image");
    
    // Créer un filtre pour les images
    FileChooser.ExtensionFilter filtreImage = 
        new FileChooser.ExtensionFilter("Images", "*.png", "*.jpg", "*.jpeg", "*.gif");
    
    // Créer un filtre pour tous les fichiers
    FileChooser.ExtensionFilter filtreTous = 
        new FileChooser.ExtensionFilter("Tous les fichiers", "*.*");
    
    // Ajouter les filtres au FileChooser
    fileChooser.getExtensionFilters().addAll(filtreImage, filtreTous);
    
    // Définir le filtre par défaut (le premier sera sélectionné)
    fileChooser.setSelectedExtensionFilter(filtreImage);
    
    File fichierSelectionne = fileChooser.showOpenDialog(stagePrincipal);
    
    if (fichierSelectionne != null) {
        lblFichier.setText("Image sélectionnée : " + fichierSelectionne.getName());
        System.out.println("Image : " + fichierSelectionne.getAbsolutePath());
    }
}
Explication :
ExtensionFilter : Crée un filtre pour limiter les types de fichiers
• Le premier paramètre est la description ("Images")
• Les paramètres suivants sont les extensions acceptées ("*.png", "*.jpg", etc.)
getExtensionFilters().addAll() : Ajoute plusieurs filtres
setSelectedExtensionFilter() : Définit le filtre sélectionné par défaut
• L'utilisateur pourra choisir parmi les filtres dans le menu déroulant du dialogue

📝 Exemple 4 : Filtrer plusieurs types de fichiers

Vous pouvez créer plusieurs filtres pour différents types de fichiers :

@FXML
private void ouvrirDocument() {
    FileChooser fileChooser = new FileChooser();
    fileChooser.setTitle("Ouvrir un document");
    
    // Filtre pour les fichiers texte
    FileChooser.ExtensionFilter filtreTexte = 
        new FileChooser.ExtensionFilter("Fichiers texte", "*.txt", "*.md");
    
    // Filtre pour les fichiers PDF
    FileChooser.ExtensionFilter filtrePDF = 
        new FileChooser.ExtensionFilter("Fichiers PDF", "*.pdf");
    
    // Filtre pour les fichiers Word
    FileChooser.ExtensionFilter filtreWord = 
        new FileChooser.ExtensionFilter("Fichiers Word", "*.doc", "*.docx");
    
    // Filtre pour tous les fichiers
    FileChooser.ExtensionFilter filtreTous = 
        new FileChooser.ExtensionFilter("Tous les fichiers", "*.*");
    
    // Ajouter tous les filtres
    fileChooser.getExtensionFilters().addAll(
        filtreTexte, 
        filtrePDF, 
        filtreWord, 
        filtreTous
    );
    
    // Sélectionner le filtre texte par défaut
    fileChooser.setSelectedExtensionFilter(filtreTexte);
    
    File fichierSelectionne = fileChooser.showOpenDialog(stagePrincipal);
    
    if (fichierSelectionne != null) {
        String nomFichier = fichierSelectionne.getName();
        String extension = nomFichier.substring(nomFichier.lastIndexOf(".") + 1);
        lblFichier.setText("Document sélectionné : " + nomFichier + " (." + extension + ")");
    }
}
Note : L'utilisateur verra un menu déroulant dans le dialogue pour choisir parmi les différents filtres. Le filtre sélectionné détermine quels fichiers sont affichés dans le dialogue.

📝 Exemple complet : Ouvrir et afficher une image

Créons un exemple complet : un bouton qui ouvre une image et l'affiche dans l'interface :

main.fxml

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

<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.image.ImageView?>
<?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.MainController"
            prefWidth="600.0"
            prefHeight="500.0">
   <center>
      <VBox spacing="20.0" alignment="CENTER" style="-fx-padding: 30px;">
         <children>
            <Label text="Exemple FileChooser" 
                   style="-fx-font-size: 20px; -fx-font-weight: bold;" />
            <Button fx:id="btnOuvrirImage" 
                    onAction="#ouvrirEtAfficherImage" 
                    text="Ouvrir une image" 
                    style="-fx-min-width: 200px; -fx-min-height: 40px;" />
            <Label fx:id="lblChemin" 
                   text="" 
                   style="-fx-font-size: 12px; -fx-text-fill: gray;" 
                   wrapText="true" />
            <ImageView fx:id="imageView" 
                      fitWidth="400.0" 
                      fitHeight="300.0" 
                      preserveRatio="true" 
                      style="-fx-border-color: #ccc; -fx-border-width: 1px;" />
         </children>
      </VBox>
   </center>
</BorderPane>

MainController.java

package application;

import javafx.fxml.FXML;
import javafx.scene.control.Label;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.stage.FileChooser;
import javafx.stage.Stage;

import java.io.File;

public class MainController {
    
    @FXML
    private ImageView imageView; // ImageView pour afficher l'image
    
    @FXML
    private Label lblChemin; // Label pour afficher le chemin du fichier
    
    private Stage stagePrincipal;
    
    @FXML
    private void ouvrirEtAfficherImage() {
        FileChooser fileChooser = new FileChooser();
        fileChooser.setTitle("Sélectionner une image");
        
        // Filtrer pour n'afficher que les images
        FileChooser.ExtensionFilter filtreImage = 
            new FileChooser.ExtensionFilter("Images", "*.png", "*.jpg", "*.jpeg", "*.gif", "*.bmp");
        FileChooser.ExtensionFilter filtreTous = 
            new FileChooser.ExtensionFilter("Tous les fichiers", "*.*");
        
        fileChooser.getExtensionFilters().addAll(filtreImage, filtreTous);
        fileChooser.setSelectedExtensionFilter(filtreImage);
        
        // Ouvrir le dialogue
        File fichierSelectionne = fileChooser.showOpenDialog(stagePrincipal);
        
        if (fichierSelectionne != null) {
            try {
                // Afficher le chemin du fichier
                String chemin = fichierSelectionne.getAbsolutePath();
                lblChemin.setText("Fichier : " + chemin);
                
                // Charger et afficher l'image
                Image image = new Image("file:" + chemin);
                imageView.setImage(image);
                
                System.out.println("Image chargée : " + chemin);
                
            } catch (Exception e) {
                lblChemin.setText("Erreur lors du chargement de l'image : " + e.getMessage());
                System.err.println("Erreur : " + e.getMessage());
                e.printStackTrace();
            }
        } else {
            lblChemin.setText("Aucun fichier sélectionné");
        }
    }
    
    public void setStagePrincipal(Stage stage) {
        this.stagePrincipal = stage;
    }
}
Explication :
• Le FileChooser filtre pour n'afficher que les images
• Quand l'utilisateur sélectionne un fichier, on récupère son chemin
new Image("file:" + chemin) : Charge l'image depuis le fichier
imageView.setImage(image) : Affiche l'image dans l'ImageView
• Le préfixe "file:" est nécessaire pour charger un fichier local
preserveRatio="true" dans le FXML maintient les proportions de l'image

📝 Exemple 5 : Enregistrer du texte dans un fichier

Créons un exemple pour enregistrer du texte saisi par l'utilisateur dans un fichier :

package application;

import javafx.fxml.FXML;
import javafx.scene.control.Label;
import javafx.scene.control.TextArea;
import javafx.stage.FileChooser;
import javafx.stage.Stage;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;

public class MainController {
    
    @FXML
    private TextArea textArea; // TextArea pour saisir du texte
    
    @FXML
    private Label lblMessage; // Label pour afficher les messages
    
    private Stage stagePrincipal;
    
    @FXML
    private void enregistrerTexte() {
        // Récupérer le texte saisi
        String texte = textArea.getText();
        
        if (texte.trim().isEmpty()) {
            lblMessage.setText("Veuillez saisir du texte avant d'enregistrer");
            return;
        }
        
        FileChooser fileChooser = new FileChooser();
        fileChooser.setTitle("Enregistrer le texte");
        fileChooser.setInitialFileName("mon_texte.txt");
        
        // Filtrer pour les fichiers texte
        FileChooser.ExtensionFilter filtreTexte = 
            new FileChooser.ExtensionFilter("Fichiers texte", "*.txt");
        FileChooser.ExtensionFilter filtreTous = 
            new FileChooser.ExtensionFilter("Tous les fichiers", "*.*");
        
        fileChooser.getExtensionFilters().addAll(filtreTexte, filtreTous);
        fileChooser.setSelectedExtensionFilter(filtreTexte);
        
        // Ouvrir le dialogue d'enregistrement
        File fichierSelectionne = fileChooser.showSaveDialog(stagePrincipal);
        
        if (fichierSelectionne != null) {
            try {
                // Écrire le texte dans le fichier
                FileWriter writer = new FileWriter(fichierSelectionne);
                writer.write(texte);
                writer.close();
                
                lblMessage.setText("Fichier enregistré : " + fichierSelectionne.getAbsolutePath());
                System.out.println("Fichier enregistré : " + fichierSelectionne.getAbsolutePath());
                
            } catch (IOException e) {
                lblMessage.setText("Erreur lors de l'enregistrement : " + e.getMessage());
                System.err.println("Erreur : " + e.getMessage());
                e.printStackTrace();
            }
        } else {
            lblMessage.setText("Enregistrement annulé");
        }
    }
    
    public void setStagePrincipal(Stage stage) {
        this.stagePrincipal = stage;
    }
}
Explication :
• On récupère le texte depuis le TextArea
• On vérifie que le texte n'est pas vide
showSaveDialog() : Ouvre le dialogue d'enregistrement
FileWriter : Écrit le texte dans le fichier sélectionné
• On gère les exceptions avec un try-catch
• Un message de confirmation ou d'erreur est affiché dans le Label
Important :
  • ✅ Toujours vérifier si le fichier sélectionné n'est pas null (l'utilisateur peut annuler)
  • ✅ Utiliser try-catch pour gérer les erreurs lors de la lecture/écriture de fichiers
  • ✅ Le préfixe "file:" est nécessaire pour charger des fichiers locaux avec Image
  • ✅ Utilisez FileWriter ou Files.write() pour écrire dans un fichier
  • ✅ Les filtres d'extension améliorent l'expérience utilisateur

💡 Points clés à retenir

  • FileChooser : Dialogue natif pour sélectionner des fichiers
  • showOpenDialog() : Pour ouvrir un fichier existant
  • showSaveDialog() : Pour enregistrer un nouveau fichier
  • ExtensionFilter : Pour filtrer les types de fichiers
  • setInitialDirectory() : Pour définir le répertoire initial
  • setInitialFileName() : Pour définir un nom de fichier par défaut (enregistrement)
  • Résultat null : Si l'utilisateur annule, le résultat est null
  • File.getAbsolutePath() : Pour obtenir le chemin complet du fichier
Conseil : Testez le FileChooser avec différents types de fichiers (images, texte, etc.). Essayez d'ouvrir et d'enregistrer des fichiers pour bien comprendre le fonctionnement. C'est un outil très utile pour créer des applications qui manipulent des fichiers !

7.2.5 – DirectoryChooser (sélection de dossier)

Le DirectoryChooser est un dialogue système qui permet à l'utilisateur de sélectionner un dossier (répertoire) sur son ordinateur. Contrairement au FileChooser qui sélectionne des fichiers, le DirectoryChooser permet de choisir un dossier entier. C'est très utile pour des opérations comme sauvegarder plusieurs fichiers dans un dossier, parcourir le contenu d'un dossier, ou configurer un répertoire de travail.

🎯 Quand utiliser DirectoryChooser ?

Utilisez DirectoryChooser quand :

  • Vous voulez permettre à l'utilisateur de choisir un dossier pour y enregistrer des fichiers
  • Vous avez besoin de parcourir le contenu d'un dossier
  • Vous voulez configurer un répertoire de travail ou de sauvegarde
  • Vous devez sélectionner un emplacement pour des exports ou des sauvegardes multiples
Différence avec FileChooser : Le FileChooser sélectionne un fichier spécifique, tandis que le DirectoryChooser sélectionne un dossier entier. Utilisez DirectoryChooser quand vous travaillez avec des dossiers plutôt qu'avec des fichiers individuels.

📝 Exemple 1 : Sélectionner un dossier simple

Pour permettre à l'utilisateur de sélectionner un dossier, utilisez showDialog() :

package application;

import javafx.fxml.FXML;
import javafx.scene.control.Label;
import javafx.stage.DirectoryChooser;
import javafx.stage.Stage;

import java.io.File;

public class MainController {
    
    private Stage stagePrincipal;
    
    @FXML
    private Label lblDossier; // Label pour afficher le dossier sélectionné
    
    @FXML
    private void selectionnerDossier() {
        DirectoryChooser directoryChooser = new DirectoryChooser();
        directoryChooser.setTitle("Sélectionner un dossier");
        
        // Optionnel : définir le répertoire initial
        directoryChooser.setInitialDirectory(new File(System.getProperty("user.home")));
        
        // Afficher le dialogue et récupérer le dossier sélectionné
        File dossierSelectionne = directoryChooser.showDialog(stagePrincipal);
        
        // Vérifier si l'utilisateur a sélectionné un dossier
        if (dossierSelectionne != null) {
            String chemin = dossierSelectionne.getAbsolutePath();
            lblDossier.setText("Dossier sélectionné : " + chemin);
            System.out.println("Dossier sélectionné : " + chemin);
        } else {
            lblDossier.setText("Aucun dossier sélectionné");
            System.out.println("L'utilisateur a annulé");
        }
    }
    
    public void setStagePrincipal(Stage stage) {
        this.stagePrincipal = stage;
    }
}
Explication :
new DirectoryChooser() : Crée un nouveau DirectoryChooser
setTitle() : Définit le titre du dialogue
setInitialDirectory() : Définit le répertoire initial (optionnel)
showDialog(stagePrincipal) : Affiche le dialogue et retourne le dossier sélectionné
• Le résultat est un File représentant le dossier ou null si l'utilisateur a annulé
getAbsolutePath() : Récupère le chemin complet du dossier

📝 Exemple 2 : Lister les fichiers d'un dossier

Une fois qu'un dossier est sélectionné, vous pouvez lister son contenu :

@FXML
private Label lblContenu; // Label pour afficher le contenu du dossier

@FXML
private void listerContenuDossier() {
    DirectoryChooser directoryChooser = new DirectoryChooser();
    directoryChooser.setTitle("Sélectionner un dossier à lister");
    directoryChooser.setInitialDirectory(new File(System.getProperty("user.home")));
    
    File dossierSelectionne = directoryChooser.showDialog(stagePrincipal);
    
    if (dossierSelectionne != null) {
        try {
            StringBuilder contenu = new StringBuilder();
            contenu.append("Contenu du dossier : ").append(dossierSelectionne.getName()).append("\n\n");
            
            // Lister les fichiers et dossiers
            File[] fichiers = dossierSelectionne.listFiles();
            if (fichiers != null) {
                for (File fichier : fichiers) {
                    if (fichier.isDirectory()) {
                        contenu.append("[DOSSIER] ").append(fichier.getName()).append("\n");
                    } else {
                        contenu.append("[FICHIER] ").append(fichier.getName())
                               .append(" (").append(fichier.length()).append(" octets)\n");
                    }
                }
            }
            
            lblContenu.setText(contenu.toString());
            System.out.println("Contenu listé pour : " + dossierSelectionne.getAbsolutePath());
            
        } catch (Exception e) {
            lblContenu.setText("Erreur lors de la lecture du dossier : " + e.getMessage());
            System.err.println("Erreur : " + e.getMessage());
            e.printStackTrace();
        }
    }
}
Explication :
listFiles() : Retourne un tableau de tous les fichiers et dossiers dans le répertoire
isDirectory() : Vérifie si c'est un dossier
fichier.length() : Retourne la taille du fichier en octets
fichier.getName() : Retourne le nom du fichier ou dossier

📝 Exemple 3 : Enregistrer plusieurs fichiers dans un dossier

Vous pouvez utiliser DirectoryChooser pour sélectionner un dossier où enregistrer plusieurs fichiers :

@FXML
private void enregistrerFichiersDansDossier() {
    DirectoryChooser directoryChooser = new DirectoryChooser();
    directoryChooser.setTitle("Sélectionner un dossier pour enregistrer les fichiers");
    directoryChooser.setInitialDirectory(new File(System.getProperty("user.home")));
    
    File dossierSelectionne = directoryChooser.showDialog(stagePrincipal);
    
    if (dossierSelectionne != null) {
        try {
            // Créer plusieurs fichiers dans le dossier sélectionné
            File fichier1 = new File(dossierSelectionne, "fichier1.txt");
            File fichier2 = new File(dossierSelectionne, "fichier2.txt");
            File fichier3 = new File(dossierSelectionne, "fichier3.txt");
            
            // Écrire dans les fichiers
            java.nio.file.Files.write(fichier1.toPath(), "Contenu du fichier 1".getBytes());
            java.nio.file.Files.write(fichier2.toPath(), "Contenu du fichier 2".getBytes());
            java.nio.file.Files.write(fichier3.toPath(), "Contenu du fichier 3".getBytes());
            
            lblMessage.setText("3 fichiers créés dans : " + dossierSelectionne.getAbsolutePath());
            lblMessage.getStyleClass().setAll("message-success");
            
            System.out.println("Fichiers créés dans : " + dossierSelectionne.getAbsolutePath());
            
        } catch (Exception e) {
            lblMessage.setText("Erreur lors de l'enregistrement : " + e.getMessage());
            lblMessage.getStyleClass().setAll("message-error");
            System.err.println("Erreur : " + e.getMessage());
            e.printStackTrace();
        }
    }
}
Explication :
new File(dossier, "nomFichier") : Crée un fichier dans le dossier spécifié
Files.write() : Écrit le contenu dans le fichier
• Vous pouvez créer autant de fichiers que nécessaire dans le dossier sélectionné

📝 Exemple 4 : Filtrer les fichiers d'un dossier

Vous pouvez filtrer les fichiers d'un dossier selon leur extension :

@FXML
private void listerImagesDansDossier() {
    DirectoryChooser directoryChooser = new DirectoryChooser();
    directoryChooser.setTitle("Sélectionner un dossier contenant des images");
    directoryChooser.setInitialDirectory(new File(System.getProperty("user.home")));
    
    File dossierSelectionne = directoryChooser.showDialog(stagePrincipal);
    
    if (dossierSelectionne != null) {
        try {
            StringBuilder images = new StringBuilder();
            images.append("Images trouvées dans : ").append(dossierSelectionne.getName()).append("\n\n");
            
            // Extensions d'images acceptées
            String[] extensions = {".png", ".jpg", ".jpeg", ".gif", ".bmp"};
            
            File[] fichiers = dossierSelectionne.listFiles();
            if (fichiers != null) {
                int compteur = 0;
                for (File fichier : fichiers) {
                    if (fichier.isFile()) {
                        String nom = fichier.getName().toLowerCase();
                        for (String ext : extensions) {
                            if (nom.endsWith(ext)) {
                                images.append("• ").append(fichier.getName())
                                      .append(" (").append(fichier.length()).append(" octets)\n");
                                compteur++;
                                break;
                            }
                        }
                    }
                }
                images.append("\nTotal : ").append(compteur).append(" image(s)");
            }
            
            lblContenu.setText(images.toString());
            System.out.println("Images listées dans : " + dossierSelectionne.getAbsolutePath());
            
        } catch (Exception e) {
            lblContenu.setText("Erreur : " + e.getMessage());
            System.err.println("Erreur : " + e.getMessage());
            e.printStackTrace();
        }
    }
}
Explication :
• On parcourt tous les fichiers du dossier
• On vérifie si le nom du fichier se termine par une extension d'image
• On affiche uniquement les fichiers qui correspondent aux critères
• On compte le nombre total d'images trouvées

📝 Exemple complet : Gestionnaire de dossiers

Créons un exemple complet qui permet de sélectionner un dossier, lister son contenu, filtrer les fichiers par type, et créer de nouveaux fichiers dans le dossier :

Illustration du DirectoryChooser JavaFX
Exemple d'utilisation du DirectoryChooser sous JavaFX

main.fxml

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

<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TextArea?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.VBox?>

<BorderPane xmlns="http://javafx.com/javafx"
            xmlns:fx="http://javafx.com/fxml"
            fx:controller="application.MainController"
            stylesheets="style="".css"
            prefWidth="700.0"
            prefHeight="600.0">
   <top>
      <VBox spacing="10.0" style="-fx-padding: 20px;">
         <children>
            <Label text="Gestionnaire de dossiers - DirectoryChooser" 
                   style="-fx-font-size: 20px; -fx-font-weight: bold;" />
            <Label fx:id="lblDossier" 
                   text="Aucun dossier sélectionné" 
                   style="-fx-font-size: 12px; -fx-text-fill: gray;" 
                   wrapText="true" />
         </children>
      </VBox>
   </top>
   <center>
      <VBox spacing="15.0" style="-fx-padding: 20px;">
         <children>
            <HBox spacing="10.0">
               <children>
                  <Button fx:id="btnSelectionnerDossier" 
                          onAction="#selectionnerDossier" 
                          text="Sélectionner un dossier" 
                          style="-fx-min-width: 180px;" />
                  <Button fx:id="btnListerContenu" 
                          onAction="#listerContenu" 
                          text="Lister le contenu" 
                          style="-fx-min-width: 150px;" />
                  <Button fx:id="btnListerImages" 
                          onAction="#listerImages" 
                          text="Lister les images" 
                          style="-fx-min-width: 150px;" />
                  <Button fx:id="btnCreerFichiers" 
                          onAction="#creerFichiers" 
                          text="Créer des fichiers" 
                          style="-fx-min-width: 150px;" />
               </children>
            </HBox>
            <TextArea fx:id="textArea" 
                     promptText="Le contenu du dossier s'affichera ici..." 
                     wrapText="true" 
                     VBox.vgrow="ALWAYS" />
            <Label fx:id="lblMessage" 
                   text="" 
                   style="-fx-font-size: 12px;" 
                   wrapText="true" />
         </children>
      </VBox>
   </center>
</BorderPane>

MainController.java

package application;

import javafx.fxml.FXML;
import javafx.scene.control.Label;
import javafx.scene.control.TextArea;
import javafx.stage.DirectoryChooser;
import javafx.stage.Stage;

import java.io.File;

public class MainController {
    
    @FXML
    private Label lblDossier; // Label pour afficher le dossier sélectionné
    
    @FXML
    private TextArea textArea; // TextArea pour afficher le contenu
    
    @FXML
    private Label lblMessage; // Label pour afficher les messages
    
    private Stage stagePrincipal;
    private File dossierCourant; // Dossier actuellement sélectionné
    
    /**
     * Sélectionne un dossier avec DirectoryChooser
     */
    @FXML
    private void selectionnerDossier() {
        DirectoryChooser directoryChooser = new DirectoryChooser();
        directoryChooser.setTitle("Sélectionner un dossier");
        directoryChooser.setInitialDirectory(new File(System.getProperty("user.home")));
        
        File dossierSelectionne = directoryChooser.showDialog(stagePrincipal);
        
        if (dossierSelectionne != null) {
            dossierCourant = dossierSelectionne;
            String chemin = dossierCourant.getAbsolutePath();
            lblDossier.setText("Dossier sélectionné : " + chemin);
            lblMessage.setText("Dossier sélectionné avec succès !");
            lblMessage.getStyleClass().setAll("message-success");
            textArea.clear();
            System.out.println("Dossier sélectionné : " + chemin);
        } else {
            lblMessage.setText("Aucun dossier sélectionné");
            lblMessage.getStyleClass().setAll("message-warning");
        }
    }
    
    /**
     * Liste tout le contenu du dossier sélectionné
     */
    @FXML
    private void listerContenu() {
        if (dossierCourant == null) {
            lblMessage.setText("Veuillez d'abord sélectionner un dossier");
            lblMessage.getStyleClass().setAll("message-warning");
            return;
        }
        
        try {
            StringBuilder contenu = new StringBuilder();
            contenu.append("=== CONTENU DU DOSSIER ===\n");
            contenu.append("Chemin : ").append(dossierCourant.getAbsolutePath()).append("\n\n");
            
            File[] fichiers = dossierCourant.listFiles();
            if (fichiers != null && fichiers.length > 0) {
                contenu.append("DOSSIERS :\n");
                for (File fichier : fichiers) {
                    if (fichier.isDirectory()) {
                        contenu.append("  📁 ").append(fichier.getName()).append("\n");
                    }
                }
                
                contenu.append("\nFICHIERS :\n");
                for (File fichier : fichiers) {
                    if (fichier.isFile()) {
                        long taille = fichier.length();
                        String tailleStr = taille < 1024 ? taille + " o" : 
                                          taille < 1048576 ? (taille / 1024) + " Ko" : 
                                          (taille / 1048576) + " Mo";
                        contenu.append("  📄 ").append(fichier.getName())
                               .append(" (").append(tailleStr).append(")\n");
                    }
                }
                contenu.append("\nTotal : ").append(fichiers.length).append(" élément(s)");
            } else {
                contenu.append("Le dossier est vide");
            }
            
            textArea.setText(contenu.toString());
            lblMessage.setText("Contenu listé avec succès");
            lblMessage.getStyleClass().setAll("message-success");
            
        } catch (Exception e) {
            lblMessage.setText("Erreur lors de la lecture : " + e.getMessage());
            lblMessage.getStyleClass().setAll("message-error");
            System.err.println("Erreur : " + e.getMessage());
            e.printStackTrace();
        }
    }
    
    /**
     * Liste uniquement les images du dossier sélectionné
     */
    @FXML
    private void listerImages() {
        if (dossierCourant == null) {
            lblMessage.setText("Veuillez d'abord sélectionner un dossier");
            lblMessage.getStyleClass().setAll("message-warning");
            return;
        }
        
        try {
            StringBuilder images = new StringBuilder();
            images.append("=== IMAGES TROUVÉES ===\n");
            images.append("Dossier : ").append(dossierCourant.getName()).append("\n\n");
            
            String[] extensions = {".png", ".jpg", ".jpeg", ".gif", ".bmp", ".webp"};
            File[] fichiers = dossierCourant.listFiles();
            
            if (fichiers != null) {
                int compteur = 0;
                for (File fichier : fichiers) {
                    if (fichier.isFile()) {
                        String nom = fichier.getName().toLowerCase();
                        for (String ext : extensions) {
                            if (nom.endsWith(ext)) {
                                long taille = fichier.length();
                                String tailleStr = taille < 1024 ? taille + " o" : 
                                                  taille < 1048576 ? (taille / 1024) + " Ko" : 
                                                  (taille / 1048576) + " Mo";
                                images.append("🖼️  ").append(fichier.getName())
                                      .append(" (").append(tailleStr).append(")\n");
                                compteur++;
                                break;
                            }
                        }
                    }
                }
                images.append("\nTotal : ").append(compteur).append(" image(s)");
            }
            
            textArea.setText(images.toString());
            lblMessage.setText("Images listées avec succès");
            lblMessage.getStyleClass().setAll("message-success");
            
        } catch (Exception e) {
            lblMessage.setText("Erreur : " + e.getMessage());
            lblMessage.getStyleClass().setAll("message-error");
            System.err.println("Erreur : " + e.getMessage());
            e.printStackTrace();
        }
    }
    
    /**
     * Crée des fichiers d'exemple dans le dossier sélectionné
     */
    @FXML
    private void creerFichiers() {
        if (dossierCourant == null) {
            lblMessage.setText("Veuillez d'abord sélectionner un dossier");
            lblMessage.getStyleClass().setAll("message-warning");
            return;
        }
        
        try {
            // Créer plusieurs fichiers d'exemple
            File fichier1 = new File(dossierCourant, "exemple_texte.txt");
            File fichier2 = new File(dossierCourant, "exemple_data.txt");
            File fichier3 = new File(dossierCourant, "exemple_info.txt");
            
            java.nio.file.Files.write(fichier1.toPath(), 
                "Ceci est un fichier d'exemple créé par l'application JavaFX.".getBytes());
            java.nio.file.Files.write(fichier2.toPath(), 
                "Données d'exemple : ligne 1\nDonnées d'exemple : ligne 2".getBytes());
            java.nio.file.Files.write(fichier3.toPath(), 
                "Informations :\n- Fichier créé automatiquement\n- Date : " + 
                new java.util.Date().toString().getBytes());
            
            lblMessage.setText("3 fichiers créés avec succès dans le dossier sélectionné !");
            lblMessage.getStyleClass().setAll("message-success");
            
            // Actualiser l'affichage
            listerContenu();
            
            System.out.println("Fichiers créés dans : " + dossierCourant.getAbsolutePath());
            
        } catch (Exception e) {
            lblMessage.setText("Erreur lors de la création : " + e.getMessage());
            lblMessage.getStyleClass().setAll("message-error");
            System.err.println("Erreur : " + e.getMessage());
            e.printStackTrace();
        }
    }
    
    public void setStagePrincipal(Stage stage) {
        this.stagePrincipal = stage;
    }
}
Explication :
dossierCourant : Variable pour stocker le dossier sélectionné
selectionnerDossier() : Ouvre le DirectoryChooser et stocke le résultat
listerContenu() : Liste tous les fichiers et dossiers avec leurs tailles
listerImages() : Filtre et affiche uniquement les fichiers images
creerFichiers() : Crée des fichiers d'exemple dans le dossier sélectionné
• Les tailles sont formatées en octets, Ko ou Mo pour une meilleure lisibilité
• Après création de fichiers, le contenu est automatiquement rafraîchi
Important :
  • ✅ Toujours vérifier si le dossier sélectionné n'est pas null (l'utilisateur peut annuler)
  • ✅ Utiliser try-catch pour gérer les erreurs lors de la lecture/écriture
  • ✅ Vérifier que listFiles() ne retourne pas null (dossier vide ou inaccessible)
  • ✅ Utiliser isDirectory() et isFile() pour distinguer les dossiers des fichiers
  • ✅ Le DirectoryChooser utilise le dialogue natif du système d'exploitation

💡 Points clés à retenir

  • DirectoryChooser : Dialogue natif pour sélectionner un dossier
  • showDialog() : Affiche le dialogue et retourne le dossier sélectionné
  • setInitialDirectory() : Pour définir le répertoire initial
  • Résultat null : Si l'utilisateur annule, le résultat est null
  • File.listFiles() : Pour lister le contenu d'un dossier
  • isDirectory() : Pour vérifier si c'est un dossier
  • isFile() : Pour vérifier si c'est un fichier
  • new File(dossier, "nomFichier") : Pour créer un fichier dans un dossier
Conseil : Testez le DirectoryChooser en sélectionnant différents dossiers et en listant leur contenu. Essayez de créer des fichiers dans un dossier sélectionné. C'est très utile pour créer des applications qui gèrent des dossiers et des fichiers en masse !