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.
đŻ 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
đ 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;
}
}
âą 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;
}
}
⹠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();
}
}
âą 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();
}
}
⹠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)
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
personnedans MainController et la variablepersonnedans 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
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Ă©.
- â 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
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;
}
}
⹠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;
}
}
âą 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
}
}
}
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
ObservableListpour 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)
- â
Vérifiez toujours que la référence au parent n'est pas
nullavant de l'utiliser - â
Utilisez
listView.refresh()si vous modifiez les propriĂ©tĂ©s d'un objet dans la liste - â
Préférez
ObservableListpour 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
đŻ 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 :
// â 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
- â 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