↑
CHAPITRE 6.1

Collections observables

Maîtriser les collections observables et la liaison de données dans JavaFX
Dans cette section, vous allez découvrir les collections observables de JavaFX, en particulier ObservableList. Les collections observables sont essentielles pour créer des interfaces utilisateur réactives qui se mettent à jour automatiquement lorsque les données changent. Vous apprendrez à créer, manipuler et lier des ObservableList à vos composants JavaFX.

6.1Collections observables

6.1.1 – ObservableList

ObservableList est une interface JavaFX qui étend l'interface List de Java et ajoute la capacité d'observer les changements dans la liste. Quand vous modifiez une ObservableList (ajout, suppression, modification d'éléments), tous les composants JavaFX qui l'utilisent sont automatiquement mis à jour.

🎯 Pourquoi utiliser ObservableList ?

Contrairement Ă  une List normale, une ObservableList permet :

  • Mise Ă  jour automatique : Les composants JavaFX (ListView, TableView, etc.) se mettent Ă  jour automatiquement quand la liste change
  • Écoute des changements : Vous pouvez Ă©couter les modifications pour rĂ©agir aux changements
  • Liaison de donnĂ©es : CrĂ©er des liens bidirectionnels entre les donnĂ©es et l'interface
  • Moins de code : Plus besoin d'appeler manuellement refresh() sur les composants
Important : Utilisez toujours ObservableList au lieu de List quand vous travaillez avec des composants JavaFX comme ListView, TableView, ou ComboBox. C'est la clé pour avoir des interfaces réactives !

📝 Créer une ObservableList

Pour créer une ObservableList, utilisez la classe FXCollections :

Exemple 1 : Créer une ObservableList vide

package application;

import javafx.collections.FXCollections;
import javafx.collections.ObservableList;

public class MainController {
    
    // Créer une ObservableList vide
    private ObservableList<String> items = FXCollections.observableArrayList();
    
    // Ou avec initialisation
    private ObservableList<String> noms = FXCollections.observableArrayList(
        "Jean", "Marie", "Pierre", "Sophie"
    );
}
Explication :
• FXCollections.observableArrayList() : Crée une ObservableList vide
• FXCollections.observableArrayList(...) : Crée une ObservableList avec des éléments initiaux
• Le type entre <> définit le type des éléments (String, Personne, etc.)

Exemple 2 : Créer une ObservableList à partir d'une List existante

package application;

import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import java.util.ArrayList;
import java.util.List;

public class MainController {
    
    public void exempleConversion() {
        // Liste normale
        List<String> listeNormale = new ArrayList<>();
        listeNormale.add("Élément 1");
        listeNormale.add("Élément 2");
        listeNormale.add("Élément 3");
        
        // Convertir en ObservableList
        ObservableList<String> observableList = FXCollections.observableArrayList(listeNormale);
        
        // Maintenant observableList est une ObservableList avec les mêmes éléments
    }
}

📝 Utiliser ObservableList avec ListView

L'utilisation principale d'ObservableList est avec des composants comme ListView. Voici comment lier une ObservableList Ă  une ListView :

Exemple ObservableList avec ListView JavaFX
Exemple d'utilisation d'ObservableList avec ListView

Exemple complet : ListView avec ObservableList

MainController.java
package application;

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

public class MainController {
    
    @FXML
    private ListView<String> listViewItems;
    
    @FXML
    private TextField txtNouvelItem;
    
    @FXML
    private Button btnAjouter;
    
    // ObservableList pour stocker les éléments
    private ObservableList<String> items;
    
    @FXML
    private void initialize() {
        // Créer l'ObservableList avec des éléments initiaux
        items = FXCollections.observableArrayList(
            "Pomme", "Banane", "Orange", "Fraise"
        );
        
        // Lier l'ObservableList Ă  la ListView
        listViewItems.setItems(items);
        
        // Maintenant, toute modification de 'items' sera automatiquement
        // reflétée dans la ListView !
    }
    
    @FXML
    private void ajouterItem() {
        String nouvelItem = txtNouvelItem.getText().trim();
        
        if (!nouvelItem.isEmpty()) {
            // Ajouter Ă  l'ObservableList
            items.add(nouvelItem);
            
            // La ListView se met Ă  jour AUTOMATIQUEMENT !
            // Pas besoin d'appeler listViewItems.refresh()
            
            txtNouvelItem.clear();
        }
    }
    
    @FXML
    private void supprimerItem() {
        String itemSelectionne = listViewItems.getSelectionModel().getSelectedItem();
        
        if (itemSelectionne != null) {
            // Supprimer de l'ObservableList
            items.remove(itemSelectionne);
            
            // La ListView se met Ă  jour AUTOMATIQUEMENT !
        }
    }
}
Explication :
• items est une ObservableList<String>
• listViewItems.setItems(items) : Lie la ListView à l'ObservableList
• Quand vous ajoutez/supprimez dans items, la ListView se met à jour automatiquement
• Pas besoin d'appeler refresh() manuellement !

📝 Écouter les changements dans une ObservableList

Vous pouvez écouter les changements dans une ObservableList en ajoutant un ListChangeListener :

Exemple : Écouter les modifications

@FXML
private void initialize() {
    items = FXCollections.observableArrayList();
    listViewItems.setItems(items);
    
    // Ajouter un listener pour écouter les changements
    items.addListener((javafx.collections.ListChangeListener.Change<? extends String> change) -> {
        while (change.next()) {
            if (change.wasAdded()) {
                System.out.println("Éléments ajoutés : " + change.getAddedSubList());
            }
            if (change.wasRemoved()) {
                System.out.println("Éléments supprimés : " + change.getRemoved());
            }
            if (change.wasReplaced()) {
                System.out.println("Éléments remplacés");
            }
        }
    });
    
    // Maintenant, chaque fois que vous modifiez 'items',
    // le listener sera appelé automatiquement
}
Types de changements détectés :
• wasAdded() : Des éléments ont été ajoutés
• wasRemoved() : Des éléments ont été supprimés
• wasReplaced() : Des éléments ont été remplacés
• wasPermutated() : L'ordre des éléments a changé
• getAddedSubList() : Récupère les éléments ajoutés
• getRemoved() : Récupère les éléments supprimés

📝 Méthodes utiles d'ObservableList

ObservableList hérite de toutes les méthodes de List, plus quelques méthodes supplémentaires :

Méthodes principales

ObservableList<String> items = FXCollections.observableArrayList();

// Méthodes de List (héritées)
items.add("Élément");                    // Ajouter un élément
items.add(0, "Premier");                 // Ajouter à un index spécifique
items.remove("Élément");                 // Supprimer un élément
items.remove(0);                         // Supprimer Ă  un index
items.clear();                           // Vider la liste
items.size();                            // Taille de la liste
items.get(0);                            // Récupérer un élément
items.contains("Élément");               // Vérifier si contient
items.isEmpty();                         // Vérifier si vide

// Méthodes spécifiques à ObservableList
items.addListener(listener);             // Ajouter un listener
items.removeListener(listener);          // Supprimer un listener

// Méthodes utilitaires
items.setAll("A", "B", "C");            // Remplacer tout le contenu
items.addAll("D", "E", "F");            // Ajouter plusieurs éléments
items.removeAll("A", "B");               // Supprimer plusieurs éléments
items.retainAll("C", "D");               // Garder seulement certains éléments

📝 Exemple complet : Gestionnaire de tâches

Créons un exemple complet d'un gestionnaire de tâches utilisant ObservableList :

MainController.java
package application;

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

public class MainController {
    
    @FXML
    private ListView<String> listViewTaches;
    
    @FXML
    private TextField txtNouvelleTache;
    
    @FXML
    private Button btnAjouter;
    
    @FXML
    private Button btnSupprimer;
    
    @FXML
    private Label lblNombreTaches;
    
    // ObservableList pour les tâches
    private ObservableList<String> taches;
    
    @FXML
    private void initialize() {
        // Créer l'ObservableList
        taches = FXCollections.observableArrayList();
        
        // Lier Ă  la ListView
        listViewTaches.setItems(taches);
        
        // Écouter les changements pour mettre à jour le compteur
        taches.addListener((javafx.collections.ListChangeListener.Change<? extends String> change) -> {
            while (change.next()) {
                // Mettre Ă  jour le compteur automatiquement
                lblNombreTaches.setText("Nombre de tâches : " + taches.size());
            }
        });
        
        // Initialiser le compteur
        lblNombreTaches.setText("Nombre de tâches : " + taches.size());
    }
    
    @FXML
    private void ajouterTache() {
        String nouvelleTache = txtNouvelleTache.getText().trim();
        
        if (!nouvelleTache.isEmpty()) {
            taches.add(nouvelleTache);
            txtNouvelleTache.clear();
            // Le compteur se met à jour automatiquement grâce au listener !
        }
    }
    
    @FXML
    private void supprimerTache() {
        String tacheSelectionnee = listViewTaches.getSelectionModel().getSelectedItem();
        
        if (tacheSelectionnee != null) {
            taches.remove(tacheSelectionnee);
            // Le compteur se met Ă  jour automatiquement !
        }
    }
    
    @FXML
    private void supprimerToutes() {
        taches.clear();
        // Le compteur se met Ă  jour automatiquement !
    }
}
Points importants :
• L'ObservableList est liée à la ListView avec setItems()
• Un listener met à jour automatiquement le compteur de tâches
• Toutes les modifications (ajout, suppression, clear) déclenchent automatiquement la mise à jour
• Pas besoin de code manuel pour synchroniser la vue avec les données

📝 ObservableList avec objets personnalisés

Vous pouvez utiliser ObservableList avec n'importe quel type d'objet, pas seulement des String :

Exemple : ObservableList de Personne

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;
    
    // ObservableList d'objets Personne
    private ObservableList<Personne> personnes;
    
    @FXML
    private void initialize() {
        // Créer l'ObservableList avec des Personnes
        personnes = FXCollections.observableArrayList(
            new Personne("Dupont", "Jean", 25, "jean@exemple.com"),
            new Personne("Martin", "Marie", 30, "marie@exemple.com"),
            new Personne("Bernard", "Pierre", 28, "pierre@exemple.com")
        );
        
        // Lier Ă  la ListView
        listViewPersonnes.setItems(personnes);
    }
    
    @FXML
    private void ajouterPersonne() {
        Personne nouvelle = new Personne("Nouveau", "Nom", 20, "nouveau@exemple.com");
        personnes.add(nouvelle);
        // La ListView se met Ă  jour automatiquement !
    }
    
    @FXML
    private void modifierPersonne() {
        Personne selectionnee = listViewPersonnes.getSelectionModel().getSelectedItem();
        
        if (selectionnee != null) {
            // Modifier l'objet
            selectionnee.setNom("Nom modifié");
            
            // Pour que la ListView détecte le changement dans l'objet,
            // il faut utiliser refresh() ou une ObservableList avec extractor
            listViewPersonnes.refresh();
        }
    }
}
Important :
• ObservableList détecte les ajouts/suppressions automatiquement
• Mais si vous modifiez les propriétés d'un objet dans la liste, la ListView ne se met pas à jour automatiquement
• Dans ce cas, utilisez listView.refresh() ou créez une ObservableList avec un extractor (voir section suivante)

💡 Points clés à retenir

  • ObservableList : Liste qui notifie automatiquement les changements
  • FXCollections.observableArrayList() : MĂ©thode pour crĂ©er une ObservableList
  • setItems() : Lie une ObservableList Ă  un composant JavaFX (ListView, TableView, etc.)
  • Mise Ă  jour automatique : Les composants se mettent Ă  jour quand la liste change
  • addListener() : Pour Ă©couter les changements dans la liste
  • MĂ©thodes hĂ©ritĂ©es : Toutes les mĂ©thodes de List sont disponibles (add, remove, clear, etc.)
  • Types personnalisĂ©s : Fonctionne avec n'importe quel type d'objet
Conseil : Utilisez toujours ObservableList au lieu de List quand vous travaillez avec des composants JavaFX. C'est la différence entre une interface qui se met à jour automatiquement et une interface qui nécessite des appels manuels à refresh() !

6.1.2 – Ajout / suppression / mise à jour

Cette section détaille les différentes méthodes pour manipuler une ObservableList : ajouter, supprimer et mettre à jour des éléments. Vous découvrirez les différentes approches selon vos besoins et les bonnes pratiques pour chaque opération.

📝 Ajouter des éléments

Il existe plusieurs façons d'ajouter des éléments à une ObservableList :

Méthode 1 : Ajouter un élément (add)

ObservableList<String> items = FXCollections.observableArrayList();

// Ajouter un élément à la fin
items.add("Nouvel élément");

// Ajouter à un index spécifique
items.add(0, "Premier élément"); // Ajoute au début
items.add(2, "Élément à l'index 2");

Méthode 2 : Ajouter plusieurs éléments (addAll)

// Ajouter plusieurs éléments à la fin
items.addAll("Élément 1", "Élément 2", "Élément 3");

// Ajouter plusieurs éléments à un index spécifique
items.addAll(0, "A", "B", "C"); // Ajoute A, B, C au début

// Ajouter depuis une autre collection
List<String> autresElements = Arrays.asList("X", "Y", "Z");
items.addAll(autresElements);

Méthode 3 : Remplacer tout le contenu (setAll)

// Remplacer tout le contenu de la liste
items.setAll("Nouveau 1", "Nouveau 2", "Nouveau 3");

// Cela déclenche un changement "replaced" dans le listener
// Utile pour réinitialiser complètement la liste
Quand utiliser chaque méthode :
• add() : Pour ajouter un élément à la fois
• addAll() : Pour ajouter plusieurs éléments en une seule opération (plus efficace)
• setAll() : Pour remplacer complètement le contenu (réinitialisation)

📝 Supprimer des éléments

Méthode 1 : Supprimer un élément spécifique (remove)

// Supprimer par valeur
items.remove("Élément à supprimer");

// Supprimer par index
items.remove(0); // Supprime le premier élément

// Supprimer le dernier élément
items.remove(items.size() - 1);

Méthode 2 : Supprimer plusieurs éléments (removeAll)

// Supprimer plusieurs éléments spécifiques
items.removeAll("Élément 1", "Élément 2", "Élément 3");

// Supprimer depuis une autre collection
List<String> aSupprimer = Arrays.asList("X", "Y");
items.removeAll(aSupprimer);

Méthode 3 : Vider complètement (clear)

// Vider toute la liste
items.clear();

// Utile pour réinitialiser ou nettoyer

Méthode 4 : Supprimer conditionnellement (removeIf)

// Supprimer tous les éléments qui correspondent à une condition
items.removeIf(element -> element.length() < 5); // Supprime les éléments de moins de 5 caractères

// Exemple avec objets personnalisés
ObservableList<Personne> personnes = FXCollections.observableArrayList();
personnes.removeIf(p -> p.getAge() < 18); // Supprime les mineurs

Méthode 5 : Garder seulement certains éléments (retainAll)

// Garder seulement les éléments spécifiés
items.retainAll("Élément 1", "Élément 2"); // Garde seulement ces deux éléments

// Tous les autres éléments sont supprimés
Performance :
• removeAll() avec plusieurs éléments est plus efficace que plusieurs appels à remove()
• removeIf() est optimisé pour les suppressions conditionnelles
• Utilisez clear() plutôt que removeAll() pour vider complètement

📝 Mettre à jour des éléments

Méthode 1 : Remplacer un élément (set)

// Remplacer un élément à un index spécifique
items.set(0, "Nouveau premier élément");

// Cela déclenche un changement "replaced" dans le listener

Méthode 2 : Modifier les propriétés d'un objet

Si vous modifiez les propriétés d'un objet dans la liste, la ListView ne se met pas à jour automatiquement. Vous devez utiliser refresh() :

ObservableList<Personne> personnes = FXCollections.observableArrayList();
ListView<Personne> listView = new ListView<>();
listView.setItems(personnes);

// Modifier une propriété d'un objet
Personne personne = personnes.get(0);
personne.setNom("Nouveau nom");

// La ListView ne se met pas Ă  jour automatiquement !
// Il faut appeler refresh()
listView.refresh();

// Ou utiliser un extractor (voir section 6.1.3)

Méthode 3 : Remplacer tous les éléments (replaceAll)

// Remplacer tous les éléments selon une transformation
items.replaceAll(element -> element.toUpperCase()); // Met tout en majuscules

// Exemple avec objets
personnes.replaceAll(p -> {
    p.setAge(p.getAge() + 1); // Augmente l'âge de tous
    return p;
});

📝 Opérations en lot (batch operations)

Pour effectuer plusieurs modifications sans déclencher plusieurs notifications, vous pouvez utiliser des opérations en lot :

// Méthode 1 : Utiliser beginChange() et endChange()
items.beginChange();
try {
    items.add("Élément 1");
    items.add("Élément 2");
    items.remove("Ancien élément");
    // Toutes ces opérations sont groupées en une seule notification
} finally {
    items.endChange();
}

// Méthode 2 : Utiliser setAll() pour remplacer tout
// Plus simple et plus efficace pour un remplacement complet
items.setAll("Nouveau 1", "Nouveau 2", "Nouveau 3");
Conseil : Utilisez setAll() plutôt que clear() suivi de addAll() pour remplacer tout le contenu. C'est plus efficace et déclenche une seule notification.

📝 Exemple complet : Gestion avancée d'une liste

Exemple gestion avancée d'une liste JavaFX
Exemple de gestion avancée d'une liste avec filtrage, tri et opérations avancées
package application;

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

public class MainController {
    
    @FXML
    private ListView<String> listViewItems;
    
    @FXML
    private TextField txtRecherche;
    
    private ObservableList<String> items;
    private ObservableList<String> itemsComplets; // Liste complète pour la recherche
    
    @FXML
    private void initialize() {
        // Liste complète
        itemsComplets = FXCollections.observableArrayList(
            "Pomme", "Banane", "Orange", "Fraise", "Cerise", "Kiwi"
        );
        
        // Liste filtrée (affichée dans la ListView)
        items = FXCollections.observableArrayList(itemsComplets);
        listViewItems.setItems(items);
    }
    
    @FXML
    private void ajouterItem() {
        String nouvelItem = txtNouvelItem.getText().trim();
        if (!nouvelItem.isEmpty()) {
            // Ajouter à la liste complète
            itemsComplets.add(nouvelItem);
            
            // Si l'élément correspond au filtre, l'ajouter aussi à la liste affichée
            if (txtRecherche.getText().trim().isEmpty() || 
                nouvelItem.toLowerCase().contains(txtRecherche.getText().toLowerCase())) {
                items.add(nouvelItem);
            }
        }
    }
    
    @FXML
    private void supprimerItem() {
        String selectionne = listViewItems.getSelectionModel().getSelectedItem();
        if (selectionne != null) {
            // Supprimer des deux listes
            items.remove(selectionne);
            itemsComplets.remove(selectionne);
        }
    }
    
    @FXML
    private void filtrer() {
        String recherche = txtRecherche.getText().toLowerCase().trim();
        
        if (recherche.isEmpty()) {
            // Afficher tous les éléments
            items.setAll(itemsComplets);
        } else {
            // Filtrer selon le critère
            items.setAll(
                itemsComplets.stream()
                    .filter(item -> item.toLowerCase().contains(recherche))
                    .collect(java.util.stream.Collectors.toList())
            );
        }
    }
    
    @FXML
    private void trier() {
        // Trier la liste
        FXCollections.sort(items);
    }
    
    @FXML
    private void supprimerDoublons() {
        // Créer une nouvelle liste sans doublons
        items.setAll(items.stream().distinct().collect(java.util.stream.Collectors.toList()));
    }
}

💡 Points clés à retenir

  • add() / addAll() : Pour ajouter des Ă©lĂ©ments
  • remove() / removeAll() / removeIf() : Pour supprimer des Ă©lĂ©ments
  • set() : Pour remplacer un Ă©lĂ©ment Ă  un index
  • setAll() : Pour remplacer tout le contenu (plus efficace que clear + addAll)
  • clear() : Pour vider complètement la liste
  • refresh() : NĂ©cessaire si vous modifiez les propriĂ©tĂ©s d'objets dans la liste
  • OpĂ©rations en lot : Utilisez beginChange()/endChange() pour grouper les modifications
Important :
  • âś… Utilisez addAll() plutĂ´t que plusieurs add() pour ajouter plusieurs Ă©lĂ©ments
  • âś… Utilisez setAll() plutĂ´t que clear() + addAll() pour remplacer
  • âś… Appelez listView.refresh() après avoir modifiĂ© les propriĂ©tĂ©s d'objets
  • âś… Utilisez removeIf() pour les suppressions conditionnelles

6.1.3 – Binding liste ↔ vue

Le binding (liaison) permet de créer des connexions automatiques entre les données et l'interface utilisateur. JavaFX offre plusieurs mécanismes de binding pour synchroniser les ObservableList avec les composants de l'interface, y compris la détection des changements dans les propriétés des objets.

🎯 Types de binding

Il existe deux types principaux de binding :

  • Binding unidirectionnel : Les donnĂ©es → Vue (les changements dans la liste mettent Ă  jour la vue)
  • Binding bidirectionnel : Les donnĂ©es ↔ Vue (les changements dans la liste ou la vue se synchronisent)

📝 Binding unidirectionnel (liste → vue)

Le binding unidirectionnel est le plus courant. Il est automatique quand vous utilisez setItems() :

ObservableList<String> items = FXCollections.observableArrayList();
ListView<String> listView = new ListView<>();

// Binding unidirectionnel : items → listView
listView.setItems(items);

// Maintenant, toute modification de 'items' met Ă  jour automatiquement 'listView'
items.add("Nouvel élément"); // La ListView se met à jour automatiquement
items.remove(0);              // La ListView se met Ă  jour automatiquement
items.clear();                 // La ListView se met Ă  jour automatiquement
Note : Le binding unidirectionnel avec setItems() est automatique et ne nécessite aucune configuration supplémentaire. C'est la méthode la plus simple et la plus utilisée.

📝 Détecter les changements dans les objets (Extractor)

Par défaut, ObservableList détecte seulement les ajouts/suppressions d'éléments, pas les modifications des propriétés des objets. Pour détecter les changements dans les propriétés des objets, vous devez utiliser un extractor :

Exemple : ObservableList avec extractor

package application;

import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.beans.value.ObservableValue;
import javafx.util.Callback;

public class MainController {
    
    private ObservableList<Personne> personnes;
    
    @FXML
    private void initialize() {
        // Créer une ObservableList avec un extractor
        // L'extractor indique quelles propriétés observer
        personnes = FXCollections.observableArrayList(new Callback<Personne, ObservableValue[]>() {
            @Override
            public ObservableValue[] call(Personne personne) {
                // Retourner les propriétés à observer
                return new ObservableValue[] {
                    personne.nomProperty(),      // Observer le nom
                    personne.prenomProperty(),   // Observer le prénom
                    personne.ageProperty()        // Observer l'âge
                };
            }
        });
        
        // Version lambda (plus simple)
        personnes = FXCollections.observableArrayList(personne -> new ObservableValue[] {
            personne.nomProperty(),
            personne.prenomProperty(),
            personne.ageProperty()
        });
        
        listViewPersonnes.setItems(personnes);
    }
}

Classe Personne avec propriétés observables

Pour que l'extractor fonctionne, votre classe doit utiliser des propriétés observables JavaFX :

package application;

import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;

public class Personne {
    private StringProperty nom = new SimpleStringProperty();
    private StringProperty prenom = new SimpleStringProperty();
    private IntegerProperty age = new SimpleIntegerProperty();
    
    // Constructeur
    public Personne(String nom, String prenom, int age) {
        this.nom.set(nom);
        this.prenom.set(prenom);
        this.age.set(age);
    }
    
    // Getters pour les propriétés (nécessaires pour l'extractor)
    public StringProperty nomProperty() { return nom; }
    public StringProperty prenomProperty() { return prenom; }
    public IntegerProperty ageProperty() { return age; }
    
    // Getters classiques
    public String getNom() { return nom.get(); }
    public String getPrenom() { return prenom.get(); }
    public int getAge() { return age.get(); }
    
    // Setters
    public void setNom(String nom) { this.nom.set(nom); }
    public void setPrenom(String prenom) { this.prenom.set(prenom); }
    public void setAge(int age) { this.age.set(age); }
}
Avantage de l'extractor :
• Quand vous modifiez une propriété d'un objet dans la liste, la ListView se met à jour automatiquement
• Plus besoin d'appeler refresh() manuellement
• La détection est automatique et efficace

📝 Binding avec plusieurs vues

Vous pouvez lier la mĂŞme ObservableList Ă  plusieurs composants :

ObservableList<String> items = FXCollections.observableArrayList("A", "B", "C");

ListView<String> listView1 = new ListView<>();
ListView<String> listView2 = new ListView<>();
ComboBox<String> comboBox = new ComboBox<>();

// Lier la mĂŞme liste Ă  plusieurs composants
listView1.setItems(items);
listView2.setItems(items);
comboBox.setItems(items);

// Toute modification de 'items' met Ă  jour les 3 composants automatiquement !
items.add("D"); // Les 3 composants se mettent Ă  jour

📝 Binding bidirectionnel avec sélection

Pour créer un binding bidirectionnel entre la sélection de la ListView et une propriété, vous devez utiliser des listeners car selectedItemProperty() est en lecture seule (ReadOnlyObjectProperty) :

import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;

public class MainController {
    
    private ObjectProperty<Personne> personneSelectionnee = new SimpleObjectProperty<>();
    
    @FXML
    private void initialize() {
        listViewPersonnes.setItems(personnes);
        
        // Note : selectedItemProperty() est ReadOnly, donc on utilise des listeners
        // pour créer un binding bidirectionnel manuel
        
        // ListView → Propriété : Quand la sélection change dans la ListView
        listViewPersonnes.getSelectionModel().selectedItemProperty().addListener((obs, oldVal, newVal) -> {
            personneSelectionnee.set(newVal);
        });
        
        // Propriété → ListView : Quand la propriété change depuis le code
        personneSelectionnee.addListener((obs, oldVal, newVal) -> {
            if (newVal != null) {
                listViewPersonnes.getSelectionModel().select(newVal);
            } else {
                listViewPersonnes.getSelectionModel().clearSelection();
            }
        });
        
        // Maintenant, vous pouvez modifier la sélection depuis le code
        personneSelectionnee.set(personnes.get(0)); // Sélectionne le premier élément
        
        // Et écouter les changements de sélection
        personneSelectionnee.addListener((obs, oldVal, newVal) -> {
            System.out.println("Nouvelle sélection : " + newVal);
        });
    }
}
Important :
• selectedItemProperty() (ListView) retourne un ReadOnlyObjectProperty
• Vous ne pouvez pas utiliser bindBidirectional() avec selectedItemProperty()
• Utilisez des listeners pour créer un binding bidirectionnel manuel avec ListView
• Le ComboBox a un valueProperty() qui peut utiliser bindBidirectional() directement

Exemple : Binding bidirectionnel avec ComboBox

Contrairement Ă  ListView, le ComboBox permet d'utiliser bindBidirectional() directement :

// Avec ComboBox, vous pouvez utiliser bindBidirectional() directement
ObjectProperty<Personne> personneSelectionnee = new SimpleObjectProperty<>();

// Binding bidirectionnel direct avec ComboBox
comboBoxPersonnes.valueProperty()
    .bindBidirectional(personneSelectionnee);

// Maintenant, les changements dans les deux sens sont synchronisés automatiquement
personneSelectionnee.set(personnes.get(0)); // Met Ă  jour le ComboBox
comboBoxPersonnes.setValue(personnes.get(1)); // Met à jour la propriété
Résumé :
• ListView : Utilisez des listeners pour le binding bidirectionnel (selectedItemProperty est ReadOnly)
• ComboBox : Vous pouvez utiliser bindBidirectional() directement (valueProperty n'est pas ReadOnly)

📝 Exemple complet : Application avec binding avancé

Exemple binding liste ↔ vue JavaFX avec extractor
Exemple d'application avec binding avancé, extractor et propriétés observables
package application;

import javafx.fxml.FXML;
import javafx.scene.control.Label;
import javafx.scene.control.ListView;
import javafx.scene.control.TextField;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.beans.value.ObservableValue;
import javafx.util.Callback;

public class MainController {
    
    @FXML
    private ListView<Personne> listViewPersonnes;
    
    @FXML
    private TextField txtNom;
    
    @FXML
    private TextField txtPrenom;
    
    @FXML
    private TextField txtAge;
    
    @FXML
    private Label lblNombrePersonnes;
    
    // ObservableList avec extractor pour détecter les changements dans les objets
    private ObservableList<Personne> personnes;
    
    @FXML
    private void initialize() {
        // Créer ObservableList avec extractor
        personnes = FXCollections.observableArrayList(personne -> 
            new ObservableValue[] {
                personne.nomProperty(),
                personne.prenomProperty(),
                personne.ageProperty()
            }
        );
        
        // Ajouter des personnes initiales
        personnes.addAll(
            new Personne("Dupont", "Jean", 25),
            new Personne("Martin", "Marie", 30)
        );
        
        // Lier Ă  la ListView
        listViewPersonnes.setItems(personnes);
        
        // Binding : sélection → champs de texte
        listViewPersonnes.getSelectionModel().selectedItemProperty().addListener((obs, oldVal, newVal) -> {
            if (newVal != null) {
                txtNom.setText(newVal.getNom());
                txtPrenom.setText(newVal.getPrenom());
                txtAge.setText(String.valueOf(newVal.getAge()));
            }
        });
        
        // Binding : taille de la liste → label
        lblNombrePersonnes.textProperty().bind(
            javafx.beans.binding.Bindings.size(personnes).asString("Nombre de personnes : %d")
        );
        
        // Maintenant :
        // - Modifier une propriété d'une personne met à jour automatiquement la ListView
        // - Sélectionner une personne met à jour les champs de texte
        // - Ajouter/supprimer une personne met Ă  jour le compteur automatiquement
    }
    
    @FXML
    private void modifierPersonne() {
        Personne selectionnee = listViewPersonnes.getSelectionModel().getSelectedItem();
        if (selectionnee != null) {
            // Modifier les propriétés
            selectionnee.setNom(txtNom.getText());
            selectionnee.setPrenom(txtPrenom.getText());
            selectionnee.setAge(Integer.parseInt(txtAge.getText()));
            
            // La ListView se met à jour automatiquement grâce à l'extractor !
            // Pas besoin d'appeler refresh()
        }
    }
}
Avantages du binding avancé :
• Extractor : Détecte automatiquement les changements dans les propriétés des objets
• Binding de sélection : Synchronise la sélection avec les champs de formulaire
• Binding de taille : Met à jour automatiquement les compteurs
• Moins de code : Pas besoin de gérer manuellement les mises à jour

💡 Points clés à retenir

  • setItems() : CrĂ©e un binding unidirectionnel automatique
  • Extractor : Permet de dĂ©tecter les changements dans les propriĂ©tĂ©s des objets
  • PropriĂ©tĂ©s observables : Utilisez StringProperty, IntegerProperty, etc. dans vos classes
  • Binding bidirectionnel : Utilisez des listeners pour ListView (selectedItemProperty est ReadOnly), ou bindBidirectional() pour ComboBox (valueProperty)
  • Plusieurs vues : Une ObservableList peut ĂŞtre liĂ©e Ă  plusieurs composants
  • Binding de taille : Utilisez Bindings.size() pour les compteurs automatiques
Important :
  • âś… Utilisez un extractor si vous modifiez souvent les propriĂ©tĂ©s des objets dans la liste
  • âś… Les propriĂ©tĂ©s observables (StringProperty, etc.) sont nĂ©cessaires pour l'extractor
  • âś… Le binding unidirectionnel avec setItems() est suffisant dans la plupart des cas
  • âś… Pour le binding bidirectionnel avec ListView, utilisez des listeners (selectedItemProperty est ReadOnly)
  • âś… Pour ComboBox, vous pouvez utiliser bindBidirectional() directement avec valueProperty()
Conseil : Commencez avec un binding simple (setItems()). Ajoutez un extractor seulement si vous avez besoin de détecter les changements dans les propriétés des objets. C'est plus simple et plus performant !