Existe-t-il un moyen de lier le contenu d'une ListProperty dans JavaFX?


Est-il un moyen de lier le contenu de ListProperty? Considérez ce qui suit:

private final ListProperty<Worker<?>> workers = new SimpleListProperty<>(FXCollections.observableArrayList());
public ListProperty<Worker<?>> workersProperty() {return workers;}
public ObservableList<Worker<?>> getWorkers() {return workers.get();}
public void setWorkers(ObservableList<Worker<?>> workers) {this.workers.set(workers);}

private final ObservableList<Worker<?>> stateWatchedWorkers = FXCollections.observableArrayList(
        new Callback<Worker<?>, Observable[]>() {
            @Override
            public Observable[] call(final Worker<?> param) {
                return new Observable[]{
                        param.stateProperty()
                };
            }
        }
);

Ce que je veux faire, c'est lier le contenu de la liste stateWatchedWorkers aux travailleurs. Si la liste entière workers est remplacée, je souhaite que stateWatchedWorkers soit mis à jour pour correspondre au contenu de la nouvelle liste. Autrement dit, je veux que la liste stateWatchedWorkers soit mappée à la liste actuelle détenue par la propriété workers.

Author: Ryan J, 2015-02-02

2 answers

, Vous pouvez faire

Bindings.bindContent(stateWatchedWorkers, workers);

(puisque ListProperty<T> implémente ObservableList<T>, en déléguant les méthodes ObservableList à la liste encapsulée, etc.).

Voici un exemple complet:

import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import javafx.application.Application;
import javafx.beans.Observable;
import javafx.beans.binding.Bindings;
import javafx.beans.property.ListProperty;
import javafx.beans.property.SimpleListProperty;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener.Change;
import javafx.collections.ObservableList;
import javafx.concurrent.Service;
import javafx.concurrent.Task;
import javafx.concurrent.Worker;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;

public class ListContentBindingExample extends Application {

    private static final Random RNG = new Random();
    private static final ExecutorService exec = Executors.newFixedThreadPool(5, r -> {
        Thread t = new Thread(r);
        t.setDaemon(true);
        return t ;
    });

    @Override
    public void start(Stage primaryStage) {
        final ListProperty<Worker<?>> workers = new SimpleListProperty<>(FXCollections.observableArrayList());

        final ObservableList<Worker<?>> stateWatchedWorkers = FXCollections.observableArrayList(worker -> new Observable[] { worker.stateProperty() });
        Bindings.bindContent(stateWatchedWorkers, workers);

        Button newWorker = new Button("New Worker") ;
        newWorker.setOnAction(event -> workers.add(createTask()));

        Button replaceAllWorkers = new Button("New Worker List");
        replaceAllWorkers.setOnAction(event -> {
            ObservableList<Worker<?>> newWorkers = FXCollections.observableArrayList();
            for (int i=0; i<=10; i++) {
                newWorkers.add(createTask());
            }
            workers.set(newWorkers);
        });

        ListView<Worker<?>> workerView = new ListView<>();
        workerView.setCellFactory(listView -> new ListCell<Worker<?>>() {
            @Override
            public void updateItem(Worker<?> worker, boolean empty) {
                super.updateItem(worker, empty);
                if (empty) {
                    setText(null);
                } else {
                    setText(worker.getState().toString());
                }
            }
        });
        workerView.setItems(stateWatchedWorkers);

        workers.get().addListener((Change<? extends Worker<?>> change) -> stateWatchedWorkers.forEach(w -> System.out.println(w.getState())));
        stateWatchedWorkers.addListener((Change<? extends Worker<?>> change) -> stateWatchedWorkers.forEach(w -> System.out.println(w.getState())));

        HBox buttons = new HBox(5, newWorker, replaceAllWorkers);
        buttons.setAlignment(Pos.CENTER);
        buttons.setPadding(new Insets(10));

        primaryStage.setScene(new Scene(new BorderPane(workerView, null, null, buttons, null), 250, 400));
        primaryStage.show();
    }

    private Worker<Void> createTask() {
        Service<Void> service = new Service<Void>() {

            @Override
            protected Task<Void> createTask() {
                return new Task<Void>() {
                    @Override
                    public Void call() throws Exception {
                        Thread.sleep(1000 + RNG.nextInt(2000));
                        return null ;
                    }
                };
            }

        };

        service.setExecutor(exec);
        service.start();
        return service ;
    }

    public static void main(String[] args) {
        launch(args);
    }
}
 5
Author: James_D, 2015-02-02 20:42:43

L'indice de James_D sur ListProperty déléguer à son ObservableList est la bonne réponse. Voici un autre exemple de code qui montre la liaison du contenu de ListProperty "fonctionne juste".

import javafx.beans.Observable;
import javafx.beans.binding.Bindings;
import javafx.beans.property.*;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.util.Callback;

public class ListPropertyContentBinding {
    /* This is a quick test to see how ListProperty notifies about list changes
     * when the entire list is swapped.
     *
     * Conclusion: When the backing list is changed, ListProperty will perform
     * notification indicating that all items from the original list were
     * removed and that all items from the updated (new) list were added.
     */

    ObservableList<Person> oldPeople = FXCollections.observableArrayList();
    ObservableList<Person> newPeople = FXCollections.observableArrayList();

    // A list property that is used to hold and swap the lists.
    ListProperty<Person> people = new SimpleListProperty<>(oldPeople);

    // A list that includes an extractor.  This list will be bound to people and
    // is expected to additionally notify whenever the name of a person in the
    // list changes.
    ObservableList<Person> nameWatchedPeople = FXCollections.observableArrayList(
            new Callback<Person, Observable[]>() {
                @Override
                public Observable[] call(final Person person) {
                    return new Observable[]{
                            person.name
                    };
                }
            });

    public ListPropertyContentBinding() {
        Bindings.bindContent(nameWatchedPeople, people);

        nameWatchedPeople.addListener((ListChangeListener<Person>) change -> {
            while (change.next()) {
                System.out.println("    " + change.toString());
            }
        });

        Person john = new Person("john");
        System.out.println("Adding john to oldPeople. Expect 1 change.");
        oldPeople.add(john);

        System.out.println("Changing john's name to joe. Expect 1 change.");
        john.name.set("joe");

        Person jane = new Person("jane");
        System.out.println("Adding jane to newPeople. Expect 0 changes.");
        newPeople.add(jane);

        System.out.println("Updating people to newPeople. Expect ? changes.");
        people.set(newPeople);

        Person jacob = new Person("jacob");
        System.out.println("Adding jacob to oldPeople. Expect 0 changes.");
        oldPeople.add(jacob);

        System.out.println("Adding jacob to newPeople. Expect 1 change.");
        newPeople.add(jacob);

        System.out.println("Changing jacob's name to jack. Expect 1 change.");
        jacob.name.set("jack");

        System.out.println("Updating people to oldPeople. Expect ? changes.");
        System.out.println(" oldPeople: " + oldPeople.toString());
        System.out.println(" newPeople : " + newPeople.toString());
        people.set(oldPeople);
    }

    public static void main(String[] args) {
        new ListPropertyContentBinding();
    }

    class Person {
        private final StringProperty name = new SimpleStringProperty();

        public Person(String name) {
            this.name.set(name);
        }

        @Override
        public String toString() {
            return name.get();
        }
    }
}

Et voici la sortie de l'exemple ci-dessus:

Adding john to oldPeople. Expect 1 change.
    { [john] added at 0 }
Changing john's name to joe. Expect 1 change.
    { updated at range [0, 1) }
Adding jane to newPeople. Expect 0 changes.
Updating people to newPeople. Expect ? changes.
    { [joe] removed at 0 }
    { [jane] added at 0 }
Adding jacob to oldPeople. Expect 0 changes.
Adding jacob to newPeople. Expect 1 change.
    { [jacob] added at 1 }
Changing jacob's name to jack. Expect 1 change.
    { updated at range [1, 2) }
Updating people to oldPeople. Expect ? changes.
 oldPeople: [joe, jack]
 newPeople : [jane, jack]
    { [jane, jack] removed at 0 }
    { [joe, jack] added at 0 }
 1
Author: Ryan J, 2015-02-02 23:53:11