简体   繁体   中英

Asynchronous bindings to existing java beans in JavaFX

I'm migrating an existing Swing project to JavaFX. The project has a lot of old style Java beans using PropertyChangeSupport, these are all updated on various background threads.

  • I'd like to make use of the beans adapters (javafx.beans.property.adapter) to hook up new views. However this fails because the models are not updated on the FX application thread.

  • Ideally I'd like to leave the existing code that updates the models alone rather than wrapping in Platform.runLater

My solution so far is using my own class AsyncBinding that takes an existing ObservableValue created by the beans adapters, listens to it, and fires changes on the appropriate thread, however this feels messy having to add it everywhere

  • Is there any built in way of doing this that I'm missing?
  • Is there a cleaner approach I could take?

Typical View

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

    }
}

Typical Bean

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

My best solution so far

  • Create a utility class AsyncBinding which proxies ObservableValue
  • Listen to underlying observable, dispatch to listeners on correct thread.

Usage

field.textProperty().bind(
        AsyncBinding.bind(JavaBeanIntegerPropertyBuilder.create()
                .bean(bean).name("x").build().asString("%03d")));

AsyncBinding

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM