JavaFX 绑定不传递值
JavaFX bindings not passing on values
过去,我使用了以下支持 class 来包装现有的 ObservableValue
和一个在 FX 事件线程上触发更改的支持......这工作正常。
class AsyncBinding<T> implements ObservableValue<T> {
private List<InvalidationListener> invalidationListeners = new ArrayList<>(1);
private List<ChangeListener<? super T>> changeListeners = new ArrayList<>(1);
private ObservableValue<T> original;
private ChangeListener<T> changeListener;
private InvalidationListener invalidationListener;
public AsyncBinding(ObservableValue<T> original) {
super();
this.original = original;
changeListener = (obs, oldValue, newValue) -> {
Runnable job = () -> {
for (ChangeListener<? super T> listener : changeListeners) {
listener.changed(obs, oldValue, newValue);
}
};
Platform.runLater(job);
};
original.addListener(changeListener);
invalidationListener = obs -> {
Runnable job = () -> {
for (InvalidationListener listener : invalidationListeners) {
listener.invalidated(obs);
}
};
Platform.runLater(job);
};
original.addListener(invalidationListener);
}
@Override
public void addListener(InvalidationListener arg0) {
invalidationListeners.add(arg0);
}
@Override
public void removeListener(InvalidationListener arg0) {
invalidationListeners.remove(arg0);
}
@Override
public void addListener(ChangeListener<? super T> arg0) {
changeListeners.add(arg0);
}
@Override
public void removeListener(ChangeListener<? super T> arg0) {
changeListeners.remove(arg0);
}
@Override
public T getValue() {
return original.getValue();
}
}
但是,如果它与中间版本一起使用 StringBinding
,它将停止更新。以下案例显示了不同的行为。我已经排除了垃圾收集。我只是在 explanation/understanding 的不同行为之后。
案例 1 - 属性 -> 异步 -> 标签。工作正常。
public class Case1 extends Application {
public static void main(String[] args) {
launch(args);
}
// hold references to prevent garbage collection...
private AsyncBinding<String> asyncValue;
@Override
public void start(Stage primaryStage) throws Exception {
Label label = new Label();
primaryStage.setScene(new Scene(label));
primaryStage.show();
ObjectProperty<String> property = new SimpleObjectProperty<>();
asyncValue = new AsyncBinding<>(property);
label.textProperty().bind(asyncValue);
new Thread(() -> {
for (int i = 0; i < 1_000_000; i++) {
property.set("a" + i);
}
}, "background").start();
}
}
案例 2 - 属性 -> 字符串 -> 异步 -> 标签。停止随机更新...
public class Case2 extends Application {
public static void main(String[] args) {
launch(args);
}
// hold references to prevent garbage collection...
private StringBinding stringBinding;
private AsyncBinding<String> asyncValue;
@Override
public void start(Stage primaryStage) throws Exception {
Label label = new Label();
primaryStage.setScene(new Scene(label));
primaryStage.show();
ObjectProperty<String> property = new SimpleObjectProperty<>();
stringBinding = Bindings.createStringBinding(() -> {
return "b" + property.get();
}, property);
asyncValue = new AsyncBinding<>(stringBinding);
label.textProperty().bind(asyncValue);
new Thread(() -> {
for (int i = 0; i < 1_000_000; i++) {
property.set("a" + i);
}
}, "background").start();
}
}
案例 3 - 属性 -> 异步 -> 字符串 -> 标签。工作正常。
public class Case3 extends Application {
public static void main(String[] args) {
launch(args);
}
// hold references to prevent garbage collection...
private StringBinding stringBinding;
private AsyncBinding<String> asyncValue;
@Override
public void start(Stage primaryStage) throws Exception {
Label label = new Label();
primaryStage.setScene(new Scene(label));
primaryStage.show();
ObjectProperty<String> property = new SimpleObjectProperty<>();
asyncValue = new AsyncBinding<>(property);
stringBinding = Bindings.createStringBinding(() -> {
return "b" + property.get();
}, asyncValue);
label.textProperty().bind(stringBinding);
new Thread(() -> {
for (int i = 0; i < 1_000_000; i++) {
property.set("a" + i);
}
}, "background").start();
}
}
经过一些检查,我发现问题似乎是由于一段时间后 java 为后台线程创建了 AsyncBinding.original
绑定的本地副本。后台线程不断更新它的本地副本,但 JavaFX 应用程序线程的值保持不变。
要解决此问题,您可以使用在 ChangeListener
中获得的值并将其分配给一个字段。 Return 来自 getValue
方法的这个值改为:
// field with updated value on the javafx application thread
private T value;
public AsyncBinding(ObservableValue<T> original) {
super();
this.original = original;
changeListener = (obs, oldValue, newValue) -> {
Runnable job = () -> {
value = newValue; // make sure the value on the application thread is the new value
for (InvalidationListener listener : invalidationListeners) {
listener.invalidated(obs);
}
for (ChangeListener<? super T> listener : changeListeners) {
listener.changed(obs, oldValue, newValue);
}
};
Platform.runLater(job);
};
original.addListener(changeListener);
}
...
@Override
public T getValue() {
return value;
}
这样应该最终更新值。但是,这可能需要一段时间,因为您正在进行 1000000 次 Platform.runLater
调用,这可能需要一段时间才能完成冻结应用程序,直到它完成。
过去,我使用了以下支持 class 来包装现有的 ObservableValue
和一个在 FX 事件线程上触发更改的支持......这工作正常。
class AsyncBinding<T> implements ObservableValue<T> {
private List<InvalidationListener> invalidationListeners = new ArrayList<>(1);
private List<ChangeListener<? super T>> changeListeners = new ArrayList<>(1);
private ObservableValue<T> original;
private ChangeListener<T> changeListener;
private InvalidationListener invalidationListener;
public AsyncBinding(ObservableValue<T> original) {
super();
this.original = original;
changeListener = (obs, oldValue, newValue) -> {
Runnable job = () -> {
for (ChangeListener<? super T> listener : changeListeners) {
listener.changed(obs, oldValue, newValue);
}
};
Platform.runLater(job);
};
original.addListener(changeListener);
invalidationListener = obs -> {
Runnable job = () -> {
for (InvalidationListener listener : invalidationListeners) {
listener.invalidated(obs);
}
};
Platform.runLater(job);
};
original.addListener(invalidationListener);
}
@Override
public void addListener(InvalidationListener arg0) {
invalidationListeners.add(arg0);
}
@Override
public void removeListener(InvalidationListener arg0) {
invalidationListeners.remove(arg0);
}
@Override
public void addListener(ChangeListener<? super T> arg0) {
changeListeners.add(arg0);
}
@Override
public void removeListener(ChangeListener<? super T> arg0) {
changeListeners.remove(arg0);
}
@Override
public T getValue() {
return original.getValue();
}
}
但是,如果它与中间版本一起使用 StringBinding
,它将停止更新。以下案例显示了不同的行为。我已经排除了垃圾收集。我只是在 explanation/understanding 的不同行为之后。
案例 1 - 属性 -> 异步 -> 标签。工作正常。
public class Case1 extends Application {
public static void main(String[] args) {
launch(args);
}
// hold references to prevent garbage collection...
private AsyncBinding<String> asyncValue;
@Override
public void start(Stage primaryStage) throws Exception {
Label label = new Label();
primaryStage.setScene(new Scene(label));
primaryStage.show();
ObjectProperty<String> property = new SimpleObjectProperty<>();
asyncValue = new AsyncBinding<>(property);
label.textProperty().bind(asyncValue);
new Thread(() -> {
for (int i = 0; i < 1_000_000; i++) {
property.set("a" + i);
}
}, "background").start();
}
}
案例 2 - 属性 -> 字符串 -> 异步 -> 标签。停止随机更新...
public class Case2 extends Application {
public static void main(String[] args) {
launch(args);
}
// hold references to prevent garbage collection...
private StringBinding stringBinding;
private AsyncBinding<String> asyncValue;
@Override
public void start(Stage primaryStage) throws Exception {
Label label = new Label();
primaryStage.setScene(new Scene(label));
primaryStage.show();
ObjectProperty<String> property = new SimpleObjectProperty<>();
stringBinding = Bindings.createStringBinding(() -> {
return "b" + property.get();
}, property);
asyncValue = new AsyncBinding<>(stringBinding);
label.textProperty().bind(asyncValue);
new Thread(() -> {
for (int i = 0; i < 1_000_000; i++) {
property.set("a" + i);
}
}, "background").start();
}
}
案例 3 - 属性 -> 异步 -> 字符串 -> 标签。工作正常。
public class Case3 extends Application {
public static void main(String[] args) {
launch(args);
}
// hold references to prevent garbage collection...
private StringBinding stringBinding;
private AsyncBinding<String> asyncValue;
@Override
public void start(Stage primaryStage) throws Exception {
Label label = new Label();
primaryStage.setScene(new Scene(label));
primaryStage.show();
ObjectProperty<String> property = new SimpleObjectProperty<>();
asyncValue = new AsyncBinding<>(property);
stringBinding = Bindings.createStringBinding(() -> {
return "b" + property.get();
}, asyncValue);
label.textProperty().bind(stringBinding);
new Thread(() -> {
for (int i = 0; i < 1_000_000; i++) {
property.set("a" + i);
}
}, "background").start();
}
}
经过一些检查,我发现问题似乎是由于一段时间后 java 为后台线程创建了 AsyncBinding.original
绑定的本地副本。后台线程不断更新它的本地副本,但 JavaFX 应用程序线程的值保持不变。
要解决此问题,您可以使用在 ChangeListener
中获得的值并将其分配给一个字段。 Return 来自 getValue
方法的这个值改为:
// field with updated value on the javafx application thread
private T value;
public AsyncBinding(ObservableValue<T> original) {
super();
this.original = original;
changeListener = (obs, oldValue, newValue) -> {
Runnable job = () -> {
value = newValue; // make sure the value on the application thread is the new value
for (InvalidationListener listener : invalidationListeners) {
listener.invalidated(obs);
}
for (ChangeListener<? super T> listener : changeListeners) {
listener.changed(obs, oldValue, newValue);
}
};
Platform.runLater(job);
};
original.addListener(changeListener);
}
...
@Override
public T getValue() {
return value;
}
这样应该最终更新值。但是,这可能需要一段时间,因为您正在进行 1000000 次 Platform.runLater
调用,这可能需要一段时间才能完成冻结应用程序,直到它完成。