与 JavaFX 中现有 Java Bean 的异步绑定



我正在将现有的 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();
    }
}

最新更新