有没有办法在 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 }
是否有现有的方法来绑定 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 }