我正在将现有的 Swing 项目迁移到 JavaFX。该项目有很多使用PropertyChangeSupport的旧式Java豆,这些都在各种后台线程上更新。
-
我想利用 beans 适配器 (javafx.beans.property.adapter) 来连接新视图。但是,此操作会失败,因为模型未在 FX 应用程序线程上更新。
-
理想情况下,我想保留更新模型的现有代码,而不是包装在 Platform.runLater 中
到目前为止,我的解决方案是使用我自己的类 AsyncBinding,它采用由 bean 适配器创建的现有 ObservableValue,侦听它,并在适当的线程上触发更改,但是这感觉很混乱,必须将其添加到任何地方
- 有没有我缺少的内置方法?
- 我可以采取更清洁的方法吗?
典型视图
public class AsyncIssue extends Application {
public static void main(String[] args) {
launch(args);
}
@Override
public void start(Stage primaryStage) throws Exception {
FooBean bean = new FooBean();
TextField field = new TextField();
field.textProperty().bind(
JavaBeanIntegerPropertyBuilder.create().bean(bean).name("x")
.build().asString("%03d"));
primaryStage.setScene(new Scene(new VBox(field)));
primaryStage.show();
Executors.newScheduledThreadPool(1).scheduleAtFixedRate(() -> {
try {
// simulate background work
bean.setX(bean.getX() + 1);
} catch (Throwable e) {
// Executors consume exception by default
e.printStackTrace();
throw e;
}
}, 0, 1, TimeUnit.SECONDS);
}
}
典型的豆类
public class FooBean {
private final PropertyChangeSupport pcs = new PropertyChangeSupport(this);
public static final String PROPERTY_X = "x";
private int x;
public int getX() {
return x;
}
public void setX(int x) {
int oldValue = this.x;
this.x = x;
pcs.firePropertyChange(PROPERTY_X, oldValue, x);
}
public void addPropertyChangeListener(PropertyChangeListener listener) {
pcs.addPropertyChangeListener(listener);
}
public void removePropertyChangeListener(PropertyChangeListener listener) {
pcs.removePropertyChangeListener(listener);
}
}
到目前为止我最好的解决方案
- 创建一个代理可观察值的实用程序类 AsyncBinding 侦听
- 底层可观察,在正确的线程上调度给侦听器。
用法
field.textProperty().bind(
AsyncBinding.bind(JavaBeanIntegerPropertyBuilder.create()
.bean(bean).name("x").build().asString("%03d")));
异步绑定
public class AsyncBinding<T> implements ObservableValue<T> {
private ObservableValue<T> value;
private InvalidationListener invalidationListener;
private ChangeListener<T> changeListener;
private List<InvalidationListener> invalidationListeners = new ArrayList<InvalidationListener>(
1);
private List<ChangeListener<? super T>> changeListeners = new ArrayList<ChangeListener<? super T>>(
1);
public static <T> ObservableValue<T> bind(ObservableValue<T> toWrap) {
return new AsyncBinding<T>(toWrap);
}
private AsyncBinding(ObservableValue<T> value) {
this.value = value;
invalidationListener = new InvalidationListener() {
@Override
public void invalidated(Observable observable) {
Runnable fire = () -> {
synchronized (invalidationListeners) {
for (InvalidationListener listener : invalidationListeners) {
listener.invalidated(observable);
}
}
};
if (Platform.isFxApplicationThread()) {
fire.run();
} else {
Platform.runLater(fire);
}
}
};
value.addListener(invalidationListener);
changeListener = new ChangeListener<T>() {
@Override
public void changed(ObservableValue<? extends T> observable,
T oldValue, T newValue) {
Runnable fire = () -> {
synchronized (changeListeners) {
for (ChangeListener<? super T> listener : changeListeners) {
listener.changed(observable, oldValue, newValue);
}
}
};
if (Platform.isFxApplicationThread()) {
fire.run();
} else {
Platform.runLater(fire);
}
}
};
value.addListener(changeListener);
}
@Override
public void addListener(InvalidationListener listener) {
invalidationListeners.add(listener);
}
@Override
public void removeListener(InvalidationListener listener) {
invalidationListeners.remove(listener);
}
@Override
public void addListener(ChangeListener<? super T> listener) {
changeListeners.add(listener);
}
@Override
public void removeListener(ChangeListener<? super T> listener) {
changeListeners.remove(listener);
}
@Override
public T getValue() {
return value.getValue();
}
}