有没有办法在 JavaFX 中绑定 ListProperty 的内容?

Is there a way to bind the content of a ListProperty in JavaFX?

是否有现有的方法来绑定 ListProperty 的内容?考虑以下因素:

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()
                };
            }
        }
);

我想做的是将stateWatchedWorkers列表的内容绑定到worker上。如果替换整个 workers 列表,我希望更新 stateWatchedWorkers 以匹配新列表的内容。换句话说,我希望 stateWatchedWorkers 列表映射到 workers 属性.

持有的当前列表

你可以做到

Bindings.bindContent(stateWatchedWorkers, workers);

(因为 ListProperty<T> 通过将 ObservableList 方法委托给包装列表等来实现 ObservableList<T>

这是一个完整的例子:

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);
    }
}

James_D 关于 ListProperty 委托给其 ObservableList 的提示是正确答案。这是显示绑定 ListProperty "just works".

内容的另一个代码示例
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();
        }
    }
}

这是上面示例的输出:

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 }