↑
CHAPITRE 7.3

Communication inter-fenĂȘtres

MaĂźtriser la communication et le passage de donnĂ©es entre fenĂȘtres dans JavaFX
Dans cette section, vous allez apprendre comment faire communiquer diffĂ©rentes fenĂȘtres entre elles dans JavaFX. Vous dĂ©couvrirez comment passer des objets d'une fenĂȘtre Ă  une autre, comment rĂ©cupĂ©rer des donnĂ©es depuis une fenĂȘtre modale, et comment synchroniser l'Ă©tat entre plusieurs fenĂȘtres. Ces compĂ©tences sont essentielles pour crĂ©er des applications complexes avec plusieurs fenĂȘtres qui partagent des donnĂ©es.

7.3Communication inter-fenĂȘtres

7.3.1 – Passage d'objets

Dans une application avec plusieurs fenĂȘtres, il est souvent nĂ©cessaire de passer des donnĂ©es d'une fenĂȘtre Ă  une autre. Par exemple, vous pouvez vouloir ouvrir une fenĂȘtre de modification avec les donnĂ©es d'un Ă©lĂ©ment sĂ©lectionnĂ©, ou rĂ©cupĂ©rer des informations saisies dans une fenĂȘtre modale. JavaFX offre plusieurs mĂ©thodes pour faire communiquer les fenĂȘtres entre elles.

Illustration du passage d'objets entre fenĂȘtres
Exemple d'utilisation du passage d'objets entre fenĂȘtres

🎯 Pourquoi passer des objets entre fenĂȘtres ?

Le passage d'objets entre fenĂȘtres est utile dans de nombreux cas :

  • FenĂȘtre de modification : Ouvrir une fenĂȘtre avec les donnĂ©es d'un Ă©lĂ©ment Ă  modifier
  • FenĂȘtre de dĂ©tail : Afficher les dĂ©tails d'un Ă©lĂ©ment sĂ©lectionnĂ© dans une nouvelle fenĂȘtre
  • FenĂȘtre de saisie : RĂ©cupĂ©rer des donnĂ©es saisies dans une fenĂȘtre modale
  • Configuration : Passer des paramĂštres ou des configurations Ă  une fenĂȘtre secondaire
  • État partagĂ© : Synchroniser l'Ă©tat entre plusieurs fenĂȘtres
Principe : Il existe plusieurs mĂ©thodes pour passer des objets entre fenĂȘtres. La mĂ©thode la plus simple et la plus courante est de passer l'objet directement au contrĂŽleur de la fenĂȘtre secondaire via une mĂ©thode setter.

📝 MĂ©thode 1 : Passer un objet simple (String, Integer, etc.)

Commençons par un exemple simple : passer une chaĂźne de caractĂšres d'une fenĂȘtre principale Ă  une fenĂȘtre secondaire.

Exemple : FenĂȘtre principale vers fenĂȘtre secondaire

CrĂ©ons une fenĂȘtre principale qui ouvre une fenĂȘtre secondaire et lui passe un message :

MainController.java (fenĂȘtre principale)
package application;

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

public class MainController {
    
    @FXML
    private TextField txtMessage; // Champ pour saisir un message
    
    private Stage stagePrincipal;
    
    @FXML
    private void ouvrirFenetreSecondaire() {
        try {
            // Récupérer le message saisi
            String message = txtMessage.getText();
            
            // Charger la fenĂȘtre secondaire
            FXMLLoader loader = new FXMLLoader(getClass().getResource("fenetreSecondaire.fxml"));
            Scene scene = new Scene(loader.load());
            
            // Créer le Stage
            Stage stageSecondaire = new Stage();
            stageSecondaire.setScene(scene);
            stageSecondaire.setTitle("FenĂȘtre secondaire");
            stageSecondaire.setWidth(400);
            stageSecondaire.setHeight(300);
            stageSecondaire.initOwner(stagePrincipal);
            
            // Récupérer le contrÎleur et lui passer le message
            FenetreSecondaireController controller = loader.getController();
            controller.setMessage(message); // Passer le message
            
            stageSecondaire.show();
            
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    public void setStagePrincipal(Stage stage) {
        this.stagePrincipal = stage;
    }
}
FenetreSecondaireController.java
package application;

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

public class FenetreSecondaireController {
    
    @FXML
    private Label lblMessage; // Label pour afficher le message
    
    private Stage stage;
    
    /**
     * MĂ©thode appelĂ©e pour recevoir le message de la fenĂȘtre principale
     */
    public void setMessage(String message) {
        if (lblMessage != null) {
            lblMessage.setText("Message reçu : " + message);
        }
    }
    
    @FXML
    private void fermer() {
        if (stage != null) {
            stage.close();
        }
    }
    
    public void setStage(Stage stage) {
        this.stage = stage;
    }
}
Explication :
‱ Dans la fenĂȘtre principale, on rĂ©cupĂšre le message depuis le TextField
‱ On charge la fenĂȘtre secondaire avec FXMLLoader
‱ On rĂ©cupĂšre le contrĂŽleur avec loader.getController()
‱ On appelle la mĂ©thode setMessage() pour passer le message
‱ La fenĂȘtre secondaire affiche le message dans son Label

📝 MĂ©thode 2 : Passer un objet personnalisĂ©

Pour passer des objets plus complexes, créez une classe qui représente vos données, puis passez une instance de cette classe :

Exemple : Classe Personne

CrĂ©ons une classe Personne et passons une instance Ă  une fenĂȘtre de dĂ©tail :

Personne.java
package application;

public class Personne {
    private String nom;
    private String prenom;
    private int age;
    private String email;
    
    public Personne(String nom, String prenom, int age, String email) {
        this.nom = nom;
        this.prenom = prenom;
        this.age = age;
        this.email = email;
    }
    
    // Getters
    public String getNom() { return nom; }
    public String getPrenom() { return prenom; }
    public int getAge() { return age; }
    public String getEmail() { return email; }
    
    // Setters
    public void setNom(String nom) { this.nom = nom; }
    public void setPrenom(String prenom) { this.prenom = prenom; }
    public void setAge(int age) { this.age = age; }
    public void setEmail(String email) { this.email = email; }
    
    @Override
    public String toString() {
        return prenom + " " + nom + " (" + age + " ans)";
    }
}
MainController.java (avec liste de personnes)
package application;

import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.control.ListView;
import javafx.stage.Stage;
import java.util.ArrayList;
import java.util.List;

public class MainController {
    
    @FXML
    private ListView<Personne> listViewPersonnes;
    
    private Stage stagePrincipal;
    private List<Personne> personnes;
    
    @FXML
    private void initialize() {
        // Initialiser la liste de personnes
        personnes = new ArrayList<>();
        personnes.add(new Personne("Dupont", "Jean", 25, "jean@exemple.com"));
        personnes.add(new Personne("Martin", "Marie", 30, "marie@exemple.com"));
        personnes.add(new Personne("Bernard", "Pierre", 28, "pierre@exemple.com"));
        
        // Afficher dans la ListView
        listViewPersonnes.getItems().addAll(personnes);
    }
    
    @FXML
    private void ouvrirDetails() {
        // Récupérer la personne sélectionnée
        Personne personneSelectionnee = listViewPersonnes.getSelectionModel().getSelectedItem();
        
        if (personneSelectionnee == null) {
            System.out.println("Aucune personne sélectionnée");
            return;
        }
        
        try {
            // Charger la fenĂȘtre de dĂ©tails
            FXMLLoader loader = new FXMLLoader(getClass().getResource("fenetreDetails.fxml"));
            Scene scene = new Scene(loader.load());
            
            Stage stageDetails = new Stage();
            stageDetails.setScene(scene);
            stageDetails.setTitle("Détails de la personne");
            stageDetails.setWidth(400);
            stageDetails.setHeight(350);
            stageDetails.initOwner(stagePrincipal);
            
            // Passer l'objet Personne au contrĂŽleur
            FenetreDetailsController controller = loader.getController();
            controller.setPersonne(personneSelectionnee); // Passer l'objet
            
            stageDetails.show();
            
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    public void setStagePrincipal(Stage stage) {
        this.stagePrincipal = stage;
    }
}
FenetreDetailsController.java
package application;

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

public class FenetreDetailsController {
    
    @FXML
    private Label lblNom;
    
    @FXML
    private Label lblPrenom;
    
    @FXML
    private Label lblAge;
    
    @FXML
    private Label lblEmail;
    
    private Stage stage;
    private Personne personne;
    
    /**
     * Méthode appelée pour recevoir l'objet Personne
     */
    public void setPersonne(Personne personne) {
        this.personne = personne;
        
        // Afficher les informations de la personne
        if (personne != null) {
            lblNom.setText("Nom : " + personne.getNom());
            lblPrenom.setText("Prénom : " + personne.getPrenom());
            lblAge.setText("Âge : " + personne.getAge() + " ans");
            lblEmail.setText("Email : " + personne.getEmail());
        }
    }
    
    @FXML
    private void fermer() {
        if (stage != null) {
            stage.close();
        }
    }
    
    public void setStage(Stage stage) {
        this.stage = stage;
    }
}
Explication :
‱ On crĂ©e une classe Personne avec des propriĂ©tĂ©s et des getters/setters
‱ Dans la fenĂȘtre principale, on a une ListView qui affiche une liste de personnes
‱ Quand l'utilisateur sĂ©lectionne une personne et clique sur "Voir dĂ©tails", on rĂ©cupĂšre la personne sĂ©lectionnĂ©e
‱ On passe l'objet Personne complet au contrĂŽleur de la fenĂȘtre de dĂ©tails
‱ La fenĂȘtre de dĂ©tails affiche toutes les informations de la personne

📝 MĂ©thode 3 : RĂ©cupĂ©rer des donnĂ©es depuis une fenĂȘtre modale

Pour rĂ©cupĂ©rer des donnĂ©es depuis une fenĂȘtre modale (comme un formulaire de saisie), stockez les donnĂ©es dans le contrĂŽleur et rĂ©cupĂ©rez-les aprĂšs la fermeture de la fenĂȘtre :

Exemple : FenĂȘtre modale de saisie

FenetreSaisieController.java (fenĂȘtre modale)
package application;

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

public class FenetreSaisieController {
    
    @FXML
    private TextField txtNom;
    
    @FXML
    private TextField txtPrenom;
    
    @FXML
    private TextField txtAge;
    
    private Stage stage;
    private Personne personneCreee; // Objet Ă  retourner
    private boolean valide = false;
    
    /**
     * RécupÚre l'objet Personne créé (ou null si annulé)
     */
    public Personne getPersonne() {
        return personneCreee;
    }
    
    /**
     * Vérifie si l'utilisateur a validé
     */
    public boolean estValide() {
        return valide;
    }
    
    @FXML
    private void valider() {
        String nom = txtNom.getText().trim();
        String prenom = txtPrenom.getText().trim();
        String ageStr = txtAge.getText().trim();
        
        // Validation
        if (nom.isEmpty() || prenom.isEmpty() || ageStr.isEmpty()) {
            return; // Ne pas fermer si les champs sont vides
        }
        
        try {
            int age = Integer.parseInt(ageStr);
            
            // Créer l'objet Personne
            personneCreee = new Personne(nom, prenom, age, "");
            valide = true;
            
            // Fermer la fenĂȘtre
            if (stage != null) {
                stage.close();
            }
            
        } catch (NumberFormatException e) {
            // L'Ăąge n'est pas un nombre valide
            System.err.println("Âge invalide : " + ageStr);
        }
    }
    
    @FXML
    private void annuler() {
        valide = false;
        personneCreee = null;
        if (stage != null) {
            stage.close();
        }
    }
    
    public void setStage(Stage stage) {
        this.stage = stage;
    }
}
MainController.java (récupération des données)
package application;

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

@FXML
private void ouvrirFenetreSaisie() {
    try {
        FXMLLoader loader = new FXMLLoader(getClass().getResource("fenetreSaisie.fxml"));
        Scene scene = new Scene(loader.load());
        
        Stage stageSaisie = new Stage();
        stageSaisie.setScene(scene);
        stageSaisie.setTitle("Ajouter une personne");
        stageSaisie.setWidth(400);
        stageSaisie.setHeight(300);
        stageSaisie.initOwner(stagePrincipal);
        stageSaisie.initModality(Modality.APPLICATION_MODAL);
        
        FenetreSaisieController controller = loader.getController();
        controller.setStage(stageSaisie);
        
        // Afficher et attendre la fermeture
        stageSaisie.showAndWait();
        
        // Récupérer les données aprÚs la fermeture
        if (controller.estValide()) {
            Personne nouvellePersonne = controller.getPersonne();
            if (nouvellePersonne != null) {
                // Ajouter Ă  la liste
                personnes.add(nouvellePersonne);
                listViewPersonnes.getItems().add(nouvellePersonne);
                
                System.out.println("Personne ajoutée : " + nouvellePersonne);
            }
        } else {
            System.out.println("Saisie annulée");
        }
        
    } catch (Exception e) {
        e.printStackTrace();
    }
}
Explication :
‱ La fenĂȘtre modale stocke les donnĂ©es dans une variable privĂ©e (personneCreee)
‱ Elle expose une mĂ©thode getPersonne() pour rĂ©cupĂ©rer les donnĂ©es
‱ Elle expose aussi estValide() pour savoir si l'utilisateur a validĂ© ou annulĂ©
‱ Dans la fenĂȘtre principale, aprĂšs showAndWait(), on rĂ©cupĂšre les donnĂ©es
‱ On vĂ©rifie si l'utilisateur a validĂ© avant d'utiliser les donnĂ©es

📝 MĂ©thode 4 : Passer et modifier un objet (rĂ©fĂ©rence)

Si vous passez un objet par rĂ©fĂ©rence, les modifications dans la fenĂȘtre secondaire seront visibles dans la fenĂȘtre principale :

Exemple : Modification d'une personne

FenetreModificationController.java
package application;

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

public class FenetreModificationController {
    
    @FXML
    private TextField txtNom;
    
    @FXML
    private TextField txtPrenom;
    
    @FXML
    private TextField txtAge;
    
    private Stage stage;
    private Personne personne; // Référence à l'objet original
    
    /**
     * Reçoit l'objet Personne à modifier
     */
    public void setPersonne(Personne personne) {
        this.personne = personne;
        
        // Pré-remplir les champs avec les valeurs actuelles
        if (personne != null) {
            txtNom.setText(personne.getNom());
            txtPrenom.setText(personne.getPrenom());
            txtAge.setText(String.valueOf(personne.getAge()));
        }
    }
    
    @FXML
    private void enregistrer() {
        if (personne == null) {
            return;
        }
        
        // Modifier directement l'objet (car c'est une référence)
        personne.setNom(txtNom.getText().trim());
        personne.setPrenom(txtPrenom.getText().trim());
        
        try {
            int age = Integer.parseInt(txtAge.getText().trim());
            personne.setAge(age);
        } catch (NumberFormatException e) {
            System.err.println("Âge invalide");
            return;
        }
        
        // Fermer la fenĂȘtre
        if (stage != null) {
            stage.close();
        }
    }
    
    @FXML
    private void annuler() {
        if (stage != null) {
            stage.close();
        }
    }
    
    public void setStage(Stage stage) {
        this.stage = stage;
    }
}
MainController.java (modification)
package application;

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

@FXML
private void modifierPersonne() {
    Personne personneSelectionnee = listViewPersonnes.getSelectionModel().getSelectedItem();
    
    if (personneSelectionnee == null) {
        System.out.println("Aucune personne sélectionnée");
        return;
    }
    
    try {
        FXMLLoader loader = new FXMLLoader(getClass().getResource("fenetreModification.fxml"));
        Scene scene = new Scene(loader.load());
        
        Stage stageModification = new Stage();
        stageModification.setScene(scene);
        stageModification.setTitle("Modifier la personne");
        stageModification.setWidth(400);
        stageModification.setHeight(300);
        stageModification.initOwner(stagePrincipal);
        stageModification.initModality(Modality.APPLICATION_MODAL);
        
        FenetreModificationController controller = loader.getController();
        controller.setStage(stageModification);
        controller.setPersonne(personneSelectionnee); // Passer la référence
        
        stageModification.showAndWait();
        
        // L'objet a été modifié directement (par référence)
        // RafraĂźchir l'affichage de la ListView
        listViewPersonnes.refresh();
        
        System.out.println("Personne modifiée : " + personneSelectionnee);
        
    } catch (Exception e) {
        e.printStackTrace();
    }
}
Important :
‱ Quand vous passez un objet par rĂ©fĂ©rence, les modifications sont directement appliquĂ©es Ă  l'objet original
‱ C'est utile pour la modification, mais attention : les changements sont immĂ©diats
‱ Si vous voulez annuler les modifications, vous devez sauvegarder une copie avant de modifier
‱ Utilisez listView.refresh() pour rafraüchir l'affichage aprùs modification

🔍 Comprendre : Passage par rĂ©fĂ©rence vs Passage d'objet

Il est important de bien comprendre comment Java gĂšre le passage d'objets entre mĂ©thodes et entre fenĂȘtres. Cette section clarifie un concept fondamental qui peut prĂȘter Ă  confusion.

Comment Java fonctionne avec les objets

En Java, il existe deux types de données :

  • Types primitifs (int, double, boolean, etc.) : PassĂ©s par valeur (une copie est créée)
  • Objets (String, Personne, ArrayList, etc.) : PassĂ©s par rĂ©fĂ©rence (on passe l'adresse mĂ©moire, pas une copie)
RÚgle importante : En Java, TOUS les objets sont toujours passés par référence. Il n'existe pas de "passage par valeur" pour les objets. Quand vous passez un objet à une méthode, vous passez une référence (adresse mémoire) vers cet objet, pas une copie de l'objet.

Qu'est-ce qu'une référence ?

Une rĂ©fĂ©rence est comme une adresse qui indique oĂč se trouve l'objet en mĂ©moire. Quand vous passez un objet Ă  une mĂ©thode, vous ne copiez pas l'objet, vous donnez simplement l'adresse de l'objet.

// Dans MainController
Personne personne = new Personne("Dupont", "Jean", 25, "jean@exemple.com");
controller.setPersonne(personne); // On passe la RÉFÉRENCE, pas une copie

Cela signifie que :

  • La variable personne dans MainController et la variable personne dans FenetreModificationController pointent vers le mĂȘme objet en mĂ©moire
  • Si vous modifiez l'objet dans une fenĂȘtre, les modifications sont immĂ©diatement visibles dans l'autre fenĂȘtre
  • Il n'y a qu'un seul objet en mĂ©moire, partagĂ© entre les deux fenĂȘtres

Représentation visuelle

Voici comment cela fonctionne en mémoire :

MainController :
personne ──┐
           │
           └──> [Objet Personne en mĂ©moire]
                              │
                              │ (mĂȘme objet)
                              │
FenetreModificationController:│
personne ─────────────────────┘

Les deux variables pointent vers le mĂȘme objet. C'est pourquoi les modifications dans une fenĂȘtre affectent l'autre.

Différence avec une copie

Si vous vouliez passer une copie de l'objet (pour éviter que les modifications affectent l'original), vous devriez créer explicitement une copie :

// Créer une copie (exemple - pas fait automatiquement)
Personne copie = new Personne(
    personne.getNom(),
    personne.getPrenom(),
    personne.getAge(),
    personne.getEmail()
);
controller.setPersonne(copie); // Maintenant c'est une copie différente

// Maintenant :
// - personne (original) et copie sont deux objets différents
// - Modifier copie ne modifie pas personne
// - Modifier personne ne modifie pas copie
Exemple concret :
Imaginez que vous avez une boĂźte (l'objet Personne) et deux personnes (les deux contrĂŽleurs) qui ont la clĂ© de cette boĂźte. Si une personne modifie le contenu de la boĂźte, l'autre personne verra immĂ©diatement les changements car elles ouvrent la mĂȘme boĂźte. C'est le passage par rĂ©fĂ©rence.

Si vous voulez que chaque personne ait sa propre boßte, vous devez créer une copie de la boßte. C'est ce qu'on ferait avec une copie explicite.

Dans le contexte de nos exemples

Dans les méthodes présentées précédemment :

  • MĂ©thodes 1, 2, 3 : Vous passez un objet (toujours par rĂ©fĂ©rence en Java), mais vous ne modifiez pas l'objet original. Vous l'utilisez en lecture seule ou vous crĂ©ez un nouvel objet.
  • MĂ©thode 4 : Vous passez un objet par rĂ©fĂ©rence et vous le modifiez directement, donc les modifications affectent l'objet original partout oĂč il est utilisĂ©.
Résumé important :
  • ✅ En Java, tous les objets sont passĂ©s par rĂ©fĂ©rence (pas de copie automatique)
  • ✅ Quand vous modifiez un objet passĂ© par rĂ©fĂ©rence, vous modifiez l'original
  • ✅ Si vous voulez Ă©viter de modifier l'original, crĂ©ez une copie explicite avant de passer l'objet
  • ✅ Les types primitifs (int, double, etc.) sont passĂ©s par valeur (copie automatique)

💡 Points clĂ©s Ă  retenir

  • Passage simple : Utilisez des mĂ©thodes setter dans le contrĂŽleur pour passer des donnĂ©es
  • Objets personnalisĂ©s : CrĂ©ez des classes pour reprĂ©senter vos donnĂ©es complexes
  • RĂ©cupĂ©ration depuis modale : Stockez les donnĂ©es dans le contrĂŽleur et rĂ©cupĂ©rez-les aprĂšs showAndWait()
  • Modification par rĂ©fĂ©rence : Les modifications sur un objet passĂ© par rĂ©fĂ©rence affectent l'original
  • Validation : VĂ©rifiez toujours si l'utilisateur a validĂ© avant d'utiliser les donnĂ©es
  • Getter/Setter : Exposez des mĂ©thodes getter/setter dans vos contrĂŽleurs pour passer/rĂ©cupĂ©rer des donnĂ©es
Conseil : Testez chaque mĂ©thode de passage d'objets avec des exemples simples. Commencez par passer une String, puis un objet simple, puis un objet complexe. C'est la meilleure façon de comprendre comment les donnĂ©es circulent entre les fenĂȘtres !

7.3.2 – Mise à jour du parent

Dans certaines situations, une fenĂȘtre secondaire ou modale doit pouvoir mettre Ă  jour la fenĂȘtre parente aprĂšs avoir effectuĂ© une action. Par exemple, aprĂšs avoir modifiĂ© ou supprimĂ© un Ă©lĂ©ment dans une fenĂȘtre de dĂ©tail, vous voulez que la liste dans la fenĂȘtre principale soit automatiquement mise Ă  jour. Il existe plusieurs mĂ©thodes pour rĂ©aliser cela.

🎯 Pourquoi mettre à jour le parent ?

La mise à jour du parent est nécessaire dans plusieurs cas :

  • Modification d'un Ă©lĂ©ment : AprĂšs modification, la liste principale doit reflĂ©ter les changements
  • Suppression d'un Ă©lĂ©ment : L'Ă©lĂ©ment doit disparaĂźtre de la liste principale
  • Ajout d'un Ă©lĂ©ment : Le nouvel Ă©lĂ©ment doit apparaĂźtre dans la liste
  • Changement d'Ă©tat : Mettre Ă  jour l'affichage selon les actions effectuĂ©es

📝 MĂ©thode 1 : Passer une rĂ©fĂ©rence au contrĂŽleur parent

La mĂ©thode la plus simple est de passer une rĂ©fĂ©rence au contrĂŽleur parent Ă  la fenĂȘtre secondaire. Ainsi, la fenĂȘtre secondaire peut appeler des mĂ©thodes du parent pour mettre Ă  jour l'affichage.

Exemple : Modification avec mise Ă  jour automatique

MainController.java
package application;

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

public class MainController {
    
    @FXML
    private ListView<Personne> listViewPersonnes;
    
    private Stage stagePrincipal;
    private List<Personne> personnes;
    
    @FXML
    private void modifierPersonne() {
        Personne personneSelectionnee = listViewPersonnes.getSelectionModel().getSelectedItem();
        
        if (personneSelectionnee == null) {
            return;
        }
        
        try {
            FXMLLoader loader = new FXMLLoader(getClass().getResource("fenetreModification.fxml"));
            Scene scene = new Scene(loader.load());
            
            Stage stageModification = new Stage();
            stageModification.setScene(scene);
            stageModification.setTitle("Modifier la personne");
            stageModification.setWidth(400);
            stageModification.setHeight(350);
            stageModification.initOwner(stagePrincipal);
            stageModification.initModality(Modality.APPLICATION_MODAL);
            
            FenetreModificationController controller = loader.getController();
            controller.setStage(stageModification);
            controller.setPersonne(personneSelectionnee);
            controller.setParentController(this); // Passer la référence au parent
            
            stageModification.showAndWait();
            
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    /**
     * MĂ©thode appelĂ©e par la fenĂȘtre secondaire pour mettre Ă  jour l'affichage
     */
    public void actualiserListe() {
        listViewPersonnes.refresh();
        System.out.println("Liste actualisée");
    }
    
    /**
     * Méthode pour supprimer une personne de la liste
     */
    public void supprimerPersonne(Personne personne) {
        personnes.remove(personne);
        listViewPersonnes.getItems().remove(personne);
        System.out.println("Personne supprimée : " + personne);
    }
}
FenetreModificationController.java
package application;

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

public class FenetreModificationController {
    
    @FXML
    private TextField txtNom;
    
    @FXML
    private TextField txtPrenom;
    
    @FXML
    private TextField txtAge;
    
    private Stage stage;
    private Personne personne;
    private MainController parentController; // Référence au contrÎleur parent
    
    public void setPersonne(Personne personne) {
        this.personne = personne;
        if (personne != null) {
            txtNom.setText(personne.getNom());
            txtPrenom.setText(personne.getPrenom());
            txtAge.setText(String.valueOf(personne.getAge()));
        }
    }
    
    /**
     * Définit le contrÎleur parent
     */
    public void setParentController(MainController parent) {
        this.parentController = parent;
    }
    
    @FXML
    private void enregistrer() {
        if (personne == null) {
            return;
        }
        
        // Modifier l'objet
        personne.setNom(txtNom.getText().trim());
        personne.setPrenom(txtPrenom.getText().trim());
        
        try {
            int age = Integer.parseInt(txtAge.getText().trim());
            personne.setAge(age);
        } catch (NumberFormatException e) {
            return;
        }
        
        // Mettre Ă  jour le parent
        if (parentController != null) {
            parentController.actualiserListe(); // Appeler la méthode du parent
        }
        
        // Fermer la fenĂȘtre
        if (stage != null) {
            stage.close();
        }
    }
    
    @FXML
    private void supprimer() {
        if (personne == null || parentController == null) {
            return;
        }
        
        // Demander confirmation (optionnel)
        // ...
        
        // Supprimer via le parent
        parentController.supprimerPersonne(personne);
        
        // Fermer la fenĂȘtre
        if (stage != null) {
            stage.close();
        }
    }
    
    @FXML
    private void annuler() {
        if (stage != null) {
            stage.close();
        }
    }
    
    public void setStage(Stage stage) {
        this.stage = stage;
    }
}
Explication :
‱ Le contrĂŽleur parent expose des mĂ©thodes publiques pour mettre Ă  jour l'affichage
‱ La fenĂȘtre secondaire reçoit une rĂ©fĂ©rence au contrĂŽleur parent via setParentController()
‱ AprĂšs modification, la fenĂȘtre secondaire appelle parentController.actualiserListe()
‱ Le parent peut aussi exposer d'autres mĂ©thodes comme supprimerPersonne()

📝 MĂ©thode 2 : Utiliser une interface de callback

Pour une approche plus flexible et dĂ©couplĂ©e, vous pouvez utiliser une interface de callback. Cela permet Ă  la fenĂȘtre secondaire de notifier le parent sans connaĂźtre son type exact.

Exemple : Interface de callback

UpdateListener.java (interface)
package application;

public interface UpdateListener {
    /**
     * Méthode appelée quand une personne est modifiée
     */
    void onPersonneModifiee(Personne personne);
    
    /**
     * Méthode appelée quand une personne est supprimée
     */
    void onPersonneSupprimee(Personne personne);
    
    /**
     * Méthode appelée quand une personne est ajoutée
     */
    void onPersonneAjoutee(Personne personne);
}
MainController.java (implémente l'interface)
package application;

public class MainController implements UpdateListener {
    
    @FXML
    private ListView<Personne> listViewPersonnes;
    
    private List<Personne> personnes;
    
    @Override
    public void onPersonneModifiee(Personne personne) {
        listViewPersonnes.refresh();
        System.out.println("Personne modifiée : " + personne);
    }
    
    @Override
    public void onPersonneSupprimee(Personne personne) {
        personnes.remove(personne);
        listViewPersonnes.getItems().remove(personne);
        System.out.println("Personne supprimée : " + personne);
    }
    
    @Override
    public void onPersonneAjoutee(Personne personne) {
        personnes.add(personne);
        listViewPersonnes.getItems().add(personne);
        System.out.println("Personne ajoutée : " + personne);
    }
    
    @FXML
    private void modifierPersonne() {
        Personne personneSelectionnee = listViewPersonnes.getSelectionModel().getSelectedItem();
        
        if (personneSelectionnee == null) {
            return;
        }
        
        try {
            FXMLLoader loader = new FXMLLoader(getClass().getResource("fenetreModification.fxml"));
            Scene scene = new Scene(loader.load());
            
            Stage stageModification = new Stage();
            stageModification.setScene(scene);
            stageModification.initOwner(stagePrincipal);
            stageModification.initModality(Modality.APPLICATION_MODAL);
            
            FenetreModificationController controller = loader.getController();
            controller.setStage(stageModification);
            controller.setPersonne(personneSelectionnee);
            controller.setUpdateListener(this); // Passer le listener
            
            stageModification.showAndWait();
            
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
FenetreModificationController.java (utilise le listener)
package application;

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

public class FenetreModificationController {
    
    @FXML
    private TextField txtNom;
    
    @FXML
    private TextField txtPrenom;
    
    @FXML
    private TextField txtAge;
    
    private Stage stage;
    private Personne personne;
    private UpdateListener updateListener; // Interface de callback
    
    public void setPersonne(Personne personne) {
        this.personne = personne;
        if (personne != null) {
            txtNom.setText(personne.getNom());
            txtPrenom.setText(personne.getPrenom());
            txtAge.setText(String.valueOf(personne.getAge()));
        }
    }
    
    /**
     * Définit le listener pour les mises à jour
     */
    public void setUpdateListener(UpdateListener listener) {
        this.updateListener = listener;
    }
    
    @FXML
    private void enregistrer() {
        if (personne == null) {
            return;
        }
        
        // Modifier l'objet
        personne.setNom(txtNom.getText().trim());
        personne.setPrenom(txtPrenom.getText().trim());
        
        try {
            int age = Integer.parseInt(txtAge.getText().trim());
            personne.setAge(age);
        } catch (NumberFormatException e) {
            return;
        }
        
        // Notifier le parent via l'interface
        if (updateListener != null) {
            updateListener.onPersonneModifiee(personne);
        }
        
        // Fermer la fenĂȘtre
        if (stage != null) {
            stage.close();
        }
    }
    
    @FXML
    private void supprimer() {
        if (personne == null || updateListener == null) {
            return;
        }
        
        // Notifier le parent
        updateListener.onPersonneSupprimee(personne);
        
        // Fermer la fenĂȘtre
        if (stage != null) {
            stage.close();
        }
    }
    
    public void setStage(Stage stage) {
        this.stage = stage;
    }
}
Avantages de cette approche :
‱ DĂ©couplage : La fenĂȘtre secondaire ne connaĂźt pas le type exact du parent
‱ FlexibilitĂ© : Plusieurs classes peuvent implĂ©menter l'interface
‱ TestabilitĂ© : Facile de crĂ©er un mock pour les tests
‱ MaintenabilitĂ© : Les changements dans le parent n'affectent pas la fenĂȘtre secondaire

📝 MĂ©thode 3 : Utiliser des propriĂ©tĂ©s observables

JavaFX fournit des propriétés observables qui permettent de notifier automatiquement les observateurs lors des changements. C'est particuliÚrement utile pour synchroniser plusieurs vues.

Exemple : ObservableList

package application;

import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.scene.control.ListView;

public class MainController {
    
    @FXML
    private ListView<Personne> listViewPersonnes;
    
    // Utiliser ObservableList au lieu de List
    private ObservableList<Personne> personnes;
    
    @FXML
    private void initialize() {
        // Créer une ObservableList
        personnes = FXCollections.observableArrayList();
        personnes.add(new Personne("Dupont", "Jean", 25, "jean@exemple.com"));
        personnes.add(new Personne("Martin", "Marie", 30, "marie@exemple.com"));
        
        // Lier directement la ListView Ă  l'ObservableList
        listViewPersonnes.setItems(personnes);
        
        // La ListView se met Ă  jour automatiquement quand la liste change !
    }
    
    @FXML
    private void ajouterPersonne() {
        // Ajouter directement Ă  l'ObservableList
        Personne nouvelle = new Personne("Nouveau", "Nom", 20, "nouveau@exemple.com");
        personnes.add(nouvelle); // La ListView se met Ă  jour automatiquement
    }
    
    @FXML
    private void supprimerPersonne() {
        Personne selectionnee = listViewPersonnes.getSelectionModel().getSelectedItem();
        if (selectionnee != null) {
            personnes.remove(selectionnee); // La ListView se met Ă  jour automatiquement
        }
    }
}
Avantage : Avec ObservableList, la ListView se met à jour automatiquement quand vous ajoutez, supprimez ou modifiez des éléments dans la liste. Vous n'avez pas besoin d'appeler refresh() manuellement !

💡 Points clĂ©s Ă  retenir

  • RĂ©fĂ©rence au parent : Passez une rĂ©fĂ©rence au contrĂŽleur parent pour permettre la mise Ă  jour
  • Interface de callback : Utilisez une interface pour dĂ©coupler la fenĂȘtre secondaire du parent
  • ObservableList : Utilisez ObservableList pour des mises Ă  jour automatiques de la ListView
  • MĂ©thodes publiques : Exposez des mĂ©thodes publiques dans le contrĂŽleur parent pour les mises Ă  jour
  • Notification : Notifiez toujours le parent aprĂšs une action importante (modification, suppression, ajout)
Important :
  • ✅ VĂ©rifiez toujours que la rĂ©fĂ©rence au parent n'est pas null avant de l'utiliser
  • ✅ Utilisez listView.refresh() si vous modifiez les propriĂ©tĂ©s d'un objet dans la liste
  • ✅ PrĂ©fĂ©rez ObservableList pour des mises Ă  jour automatiques
  • ✅ L'interface de callback est plus flexible et maintenable que la rĂ©fĂ©rence directe

7.3.3 – Bonnes pratiques

Pour crĂ©er des applications JavaFX robustes et maintenables avec plusieurs fenĂȘtres, il est important de suivre certaines bonnes pratiques. Cette section rĂ©sume les recommandations essentielles pour bien structurer la communication entre fenĂȘtres.

🎯 1. SĂ©paration des responsabilitĂ©s

Chaque contrÎleur doit avoir une responsabilité claire et limitée :

  • ContrĂŽleur principal : GĂšre la liste principale et la navigation vers les autres fenĂȘtres
  • ContrĂŽleur de fenĂȘtre secondaire : GĂšre uniquement sa propre fenĂȘtre et ses interactions
  • Classe modĂšle : ReprĂ©sente les donnĂ©es (ex: Personne) sans logique d'interface
Principe : Un contrÎleur ne doit pas connaßtre les détails d'implémentation d'un autre contrÎleur. Utilisez des interfaces ou des méthodes publiques bien définies pour la communication.

🎯 2. Gestion des rĂ©fĂ©rences null

Toujours vérifier que les références ne sont pas null avant de les utiliser :

// ❌ MAUVAIS : Pas de vĂ©rification
parentController.actualiserListe();

// ✅ BON : VĂ©rification avant utilisation
if (parentController != null) {
    parentController.actualiserListe();
}

// ✅ ENCORE MIEUX : VĂ©rification avec message d'erreur
if (parentController == null) {
    System.err.println("Erreur : Référence au parent manquante");
    return;
}
parentController.actualiserListe();

🎯 3. Validation des donnĂ©es

Validez toujours les donnĂ©es avant de les utiliser ou de les passer Ă  d'autres fenĂȘtres :

@FXML
private void valider() {
    String nom = txtNom.getText().trim();
    String prenom = txtPrenom.getText().trim();
    String ageStr = txtAge.getText().trim();
    
    // Validation
    if (nom.isEmpty() || prenom.isEmpty() || ageStr.isEmpty()) {
        // Afficher un message d'erreur
        lblMessage.setText("Veuillez remplir tous les champs");
        lblMessage.getStyleClass().setAll("message-error");
        return; // Ne pas fermer la fenĂȘtre
    }
    
    try {
        int age = Integer.parseInt(ageStr);
        if (age < 0 || age > 150) {
            lblMessage.setText("Âge invalide (0-150)");
            return;
        }
        
        // Créer l'objet seulement si la validation réussit
        Personne personne = new Personne(nom, prenom, age, email);
        // ...
        
    } catch (NumberFormatException e) {
        lblMessage.setText("Âge doit ĂȘtre un nombre");
        return;
    }
}

🎯 4. Gestion des erreurs

Utilisez des blocs try-catch pour gérer les erreurs lors du chargement des FXML et des opérations sur les fichiers :

@FXML
private void ouvrirFenetre() {
    try {
        FXMLLoader loader = new FXMLLoader(getClass().getResource("fenetre.fxml"));
        Scene scene = new Scene(loader.load());
        
        Stage stage = new Stage();
        stage.setScene(scene);
        stage.show();
        
    } catch (IOException e) {
        // Afficher un message d'erreur Ă  l'utilisateur
        Alert alert = new Alert(AlertType.ERROR);
        alert.setTitle("Erreur");
        alert.setHeaderText("Impossible d'ouvrir la fenĂȘtre");
        alert.setContentText("Erreur : " + e.getMessage());
        alert.showAndWait();
        
        // Logger l'erreur pour le débogage
        System.err.println("Erreur lors du chargement : " + e.getMessage());
        e.printStackTrace();
    }
}

🎯 5. Utilisation d'ObservableList

Préférez ObservableList à List pour les listes affichées dans des ListView :

// ❌ MAUVAIS : List normale
private List<Personne> personnes = new ArrayList<>();

// ✅ BON : ObservableList
private ObservableList<Personne> personnes = FXCollections.observableArrayList();

// Lier Ă  la ListView
listViewPersonnes.setItems(personnes);

// Les modifications sont automatiquement reflétées dans la ListView
personnes.add(nouvellePersonne); // Mise Ă  jour automatique
personnes.remove(personne);      // Mise Ă  jour automatique

🎯 6. Nettoyage des ressources

Fermez toujours les fenĂȘtres et libĂ©rez les ressources quand elles ne sont plus nĂ©cessaires :

@FXML
private void fermer() {
    // Nettoyer les ressources si nécessaire
    // Par exemple : fermer des connexions, sauvegarder des données, etc.
    
    if (stage != null) {
        stage.close();
    }
    
    // Optionnel : Réinitialiser les références
    parentController = null;
    personne = null;
}

🎯 7. Documentation du code

Documentez vos mĂ©thodes publiques, surtout celles utilisĂ©es pour la communication entre fenĂȘtres :

/**
 * Met Ă  jour l'affichage de la liste principale.
 * Cette mĂ©thode est appelĂ©e par les fenĂȘtres secondaires
 * aprĂšs une modification, suppression ou ajout.
 */
public void actualiserListe() {
    listViewPersonnes.refresh();
}

/**
 * Définit le contrÎleur parent pour permettre la mise à jour.
 * 
 * @param parent Le contrÎleur parent qui sera notifié des changements
 */
public void setParentController(MainController parent) {
    this.parentController = parent;
}

🎯 8. Utilisation de constantes

DĂ©finissez des constantes pour les valeurs rĂ©utilisĂ©es (tailles de fenĂȘtres, messages, etc.) :

public class FenetreModificationController {
    
    // Constantes pour les dimensions
    private static final double FENETRE_LARGEUR = 400.0;
    private static final double FENETRE_HAUTEUR = 350.0;
    
    // Constantes pour les messages
    private static final String MESSAGE_ERREUR_AGE = "Âge invalide";
    private static final String MESSAGE_CHAMPS_VIDES = "Veuillez remplir tous les champs";
    
    @FXML
    private void ouvrirFenetre() {
        Stage stage = new Stage();
        stage.setWidth(FENETRE_LARGEUR);
        stage.setHeight(FENETRE_HAUTEUR);
        // ...
    }
}

🎯 9. Éviter les dĂ©pendances circulaires

Évitez les dĂ©pendances circulaires entre contrĂŽleurs. Utilisez des interfaces ou des Ă©vĂ©nements pour dĂ©coupler :

ProblÚme : Si MainController dépend de FenetreController et FenetreController dépend de MainController, vous avez une dépendance circulaire qui rend le code difficile à maintenir.
// ❌ MAUVAIS : DĂ©pendance circulaire
// MainController importe FenetreController
// FenetreController importe MainController

// ✅ BON : Utiliser une interface
// MainController implémente UpdateListener
// FenetreController utilise UpdateListener (interface)
// Pas de dépendance directe entre les deux contrÎleurs

🎯 10. Tests et dĂ©bogage

Ajoutez des messages de débogage pour suivre le flux d'exécution :

@FXML
private void enregistrer() {
    System.out.println("Début de l'enregistrement");
    
    if (personne == null) {
        System.err.println("Erreur : personne est null");
        return;
    }
    
    // ... logique ...
    
    if (parentController != null) {
        System.out.println("Notification du parent");
        parentController.actualiserListe();
    } else {
        System.err.println("Attention : parentController est null");
    }
    
    System.out.println("Fin de l'enregistrement");
}

💡 Checklist des bonnes pratiques

Vérifiez que vous respectez ces points :
  • ✅ Chaque contrĂŽleur a une responsabilitĂ© claire
  • ✅ Toutes les rĂ©fĂ©rences sont vĂ©rifiĂ©es avant utilisation
  • ✅ Les donnĂ©es sont validĂ©es avant utilisation
  • ✅ Les erreurs sont gĂ©rĂ©es avec try-catch
  • ✅ ObservableList est utilisĂ© pour les ListView
  • ✅ Les ressources sont nettoyĂ©es correctement
  • ✅ Le code est documentĂ© (JavaDoc)
  • ✅ Les constantes sont utilisĂ©es pour les valeurs rĂ©utilisĂ©es
  • ✅ Pas de dĂ©pendances circulaires
  • ✅ Messages de dĂ©bogage pour le suivi
Conseil final : Commencez simple, puis ajoutez progressivement les bonnes pratiques. Il vaut mieux avoir un code qui fonctionne simplement qu'un code complexe qui ne fonctionne pas. Vous pouvez toujours améliorer et refactoriser plus tard !