2

で問題が発生し、 ReactFXTableViewのリアクティブ バインディングを使用しています。ただし、バインディングの駆動はRxJavaに由来します。以下のコードでは、それを に変換するメソッドが見つかります。setCellValueFactoryEventStream ObservableEventStream

ただし、TableView列の初期バインディング値以外は何も表示されません。ボディに a を追加すると、 が無限ループで呼び出され、発行された値がバインディングに到達しないことがわかりSystem.out.printlnました。setCellValueFactory()setCellValueFactory()

これには本当に困惑しています。この動作を停止し、Observable が単一の値を EventStream に、次に Binding に正常に発行するにはどうすればよいですか?

これが私のSSCCEです。

public class ReactiveTableViewTest extends Application {

    @Override
    public void start(Stage stage) throws Exception {

        Group root = new Group();
        Scene scene = new Scene(root);

        root.getChildren().add(new ReactiveTable(buildSampleData()));

        stage.setScene(scene);
        stage.show();
    }

    private ObservableList<ReactivePoint> buildSampleData() { 
        ObservableList<ReactivePoint> points = FXCollections.observableArrayList();
        points.add(new ReactivePoint(Observable.just(1), Observable.just(2)));
        return points;
    }

    private static final class ReactivePoint {
        private final Observable<Integer> x;
        private final Observable<Integer> y;

        ReactivePoint(Observable<Integer> x, Observable<Integer> y) { 
            this.x = x;
            this.y = y;
        }
        public Observable<Integer> getX() {
            return x;
        }
        public Observable<Integer> getY() { 
            return y;
        }
    }

    private static final class ReactiveTable extends TableView<ReactivePoint> {

        @SuppressWarnings("unchecked")
        private ReactiveTable(ObservableList<ReactivePoint> reactivePoints) { 
            this.setItems(reactivePoints);

            TableColumn<ReactivePoint,Number> xCol = new TableColumn<>("X");
            xCol.setCellValueFactory(cb -> {
                System.out.println("Calling cell value factory for x col");
                return toReactFX(cb.getValue().getX().map(x -> (Number) x)).toBinding(-1); //causes infinite call loop
                //return new SimpleObjectProperty<Number>(1); //works fine
            });

            TableColumn<ReactivePoint,Number> yCol = new TableColumn<>("Y");
            yCol.setCellValueFactory(cb -> {
                System.out.println("Calling cell value factory for y col");
                return toReactFX(cb.getValue().getY().map(y -> (Number) y)).toBinding(-1); //causes infinite call loop
                //return new SimpleObjectProperty<Number>(1); //works fine
            });

            this.getColumns().addAll(xCol, yCol);
        }
    }
    private static <T> EventStream<T> toReactFX(Observable<T> obs) {
        EventSource<T> es = new EventSource<>();
        obs.subscribe(foo -> Platform.runLater(() -> es.push(foo)), e -> e.printStackTrace());
        return es;
    }
    public static void main(String[] args) { 
        launch(args);
    }
}

アップデート

以下で提案したソリューションに問題があると思います。Observableプラットフォーム スレッド以外の他のスレッドで発行された場合、値はプロパティに取り込まれません。

プラットフォーム スレッドに配置する前に、スレッド呼び出しがプラットフォーム スレッドであるかどうかを確認することで、これを修正しようとしましたrxToPropertyが、これは機能せず、無限ループが再び発生しました。Property のスレッドセーフが脱線しているかどうかはわかりません。

しかし、Observable を複数のスレッドで発行して安全にデータを入力するにはどうすればよいPropertyでしょうか? これは、この動作を表示する更新された SSCCE です。「X」列はマルチスレッドであるためデータが取り込まれませんが、「Y」列はプラットフォーム スレッド上にあるためデータが取り込まれます。

public class ReactiveTableViewTest extends Application {

    @Override
    public void start(Stage stage) throws Exception {

        Group root = new Group();
        Scene scene = new Scene(root);

        root.getChildren().add(new ReactiveTable(buildSampleData()));

        stage.setScene(scene);
        stage.show();
    }

    private ObservableList<ReactivePoint> buildSampleData() { 
        ObservableList<ReactivePoint> points = FXCollections.observableArrayList();
        points.add(new ReactivePoint(
                Observable.just(1, 5, 6, 8,2,3,5,2).observeOn(Schedulers.computation()), 
                Observable.just(2,6,8,2,14)
                )
            );
        return points;
    }

    private static final class ReactivePoint {
        private final Observable<Integer> x;
        private final Observable<Integer> y;

        ReactivePoint(Observable<Integer> x, Observable<Integer> y) { 
            this.x = x;
            this.y = y;
        }
        public Observable<Integer> getX() {
            return x;
        }
        public Observable<Integer> getY() { 
            return y;
        }
    }

    private static final class ReactiveTable extends TableView<ReactivePoint> {

        @SuppressWarnings("unchecked")
        private ReactiveTable(ObservableList<ReactivePoint> reactivePoints) { 
            this.setItems(reactivePoints);


            System.out.println("Constructor is happening on FX THREAD: " + Platform.isFxApplicationThread());

            TableColumn<ReactivePoint,Number> xCol = new TableColumn<>("X");
            xCol.setCellValueFactory(cb -> { 
                System.out.println("CellValueFactory for X called on FX THREAD: " + Platform.isFxApplicationThread());
                return rxToProperty(cb.getValue().getX().map(x -> (Number) x));
                }
            );

            TableColumn<ReactivePoint,Number> yCol = new TableColumn<>("Y");
            yCol.setCellValueFactory(cb -> {
                System.out.println("CellValueFactory for Y called on FX THREAD: " + Platform.isFxApplicationThread());
                return rxToProperty(cb.getValue().getY().map(y -> (Number) y));
            }
        );

            this.getColumns().addAll(xCol, yCol);
        }
    }
    private static <T> ObjectProperty<T> rxToProperty(Observable<T> obs) { 
        ObjectProperty<T> property = new SimpleObjectProperty<>();

        obs.subscribe(v -> { 
            if (Platform.isFxApplicationThread()) {
                System.out.println("Emitting " + v + " on FX Thread");
                property.set(v);
            }
            else { 
                System.out.println("Emitting " + v + " on Non-FX Thread");
                Platform.runLater(() -> property.set(v));
            }
        });

        return property;
    }
    public static void main(String[] args) { 
        launch(args);
    }
}
4

1 に答える 1

2

OPコードの問題の正確な根本原因を完全には把握していませんが、解決策を見つけました(誰かが理由を説明できる場合は、それを答えとしてマークします)。私は当初、最初にPlatform.runLater()設定されたように無限ループを引き起こしている可能性があると考えていました (以下を参照)。

private static <T> EventStream<T> toReactFX(Observable<T> obs) {
    EventSource<T> es = new EventSource<>();
    obs.subscribe(foo -> Platform.runLater(() -> es.push(foo)), e -> e.printStackTrace());
    return es;
}

この理論は正しいことが判明しました。を削除するPlatform.runLater()と、無限ループがなくなりました。おそらく、発行された値は GUI スレッドの後ろにスローされ続けたため、Binding に到達しなかったのでしょうか? しかし、まだ何も出力されておらず、テーブルの値は最初の Binding 値である -1 のままでした。

private static <T> EventStream<T> toReactFX(Observable<T> obs) {
    EventSource<T> es = new EventSource<>();
    obs.subscribe(foo -> es.push(foo), e -> e.printStackTrace());
    return es;
}

私はうまくいったものを見つけました。という新しい変換メソッドを作成しrxToProperty()、 の呼び出しをそれに置き換えましたrxToReactFX()。その後、すべてがうまくいったようです。

private static <T> ObjectProperty<T> rxToProperty(Observable<T> obs) { 
    ObjectProperty<T> property = new SimpleObjectProperty<>();
    obs.subscribe(v -> property.set(v));
    return property;
}

そして、ここに新しいTableColumnセットアップがあります。

TableColumn<ReactivePoint,Number> xCol = new TableColumn<>("X");
xCol.setCellValueFactory(cb -> rxToProperty(cb.getValue().getX().map(x -> (Number) x)));

TableColumn<ReactivePoint,Number> yCol = new TableColumn<>("Y");
yCol.setCellValueFactory(cb -> rxToProperty(cb.getValue().getY().map(y -> (Number) y)));

誰かがより良い解決策を持っている場合、またはPlatform.runLater()EventStreamが機能しなかった理由を説明できる場合は、それを受け入れられた回答としてマークします。

アップデート

非 FX スレッドで発行される Observables にいくつかの問題があり、値が に取り込まれませんでしたProperty。これは、最後の値を保持するために使用することで解決できることがわかりましcache()た。サブスクライブする呼び出しスレッド (FX スレッド) で再発行されます。また、いくつかの同期を行い、返された のラッパーのみを読み取りましたProperty

private static <T> ReadOnlyObjectProperty<T> rxToProperty(Observable<T> obs) { 
        ReadOnlyObjectWrapper<T> property = new ReadOnlyObjectWrapper<>();

        obs.cache(1).subscribe(v -> { 
            synchronized(property) { 
                if (Platform.isFxApplicationThread()) {
                    System.out.println("Emitting val " + v + " on FX Thread");
                }
                else { 
                    System.out.println("Emitting val " + v + " on Non-FX Thread");
                }
                property.set(v);
            }
        });

        return property.getReadOnlyProperty();
    }

最終更新 Tomas Mikula は、ReactFX GitHub プロジェクトで、この動作と解決策について非常に役立つ洞察を提供してくれました。

https://github.com/TomasMikula/ReactFX/issues/22

于 2015-06-12T18:45:38.933 に答える