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 调用,这可能需要一段时间才能完成冻结应用程序,直到它完成。