JavaFX和RxJava- TableView无限调用setCellValueFactory()

时间:2015-06-12 13:30:36

标签: javafx java-8 reactive-programming rx-java reactfx

我遇到TableView的问题,并使用ReactFXsetCellValueFactory的反应绑定。EventStream。驱动绑定的Observable来自RxJava EventStream,但在下面的代码中,您会找到一种将其转换为TableView的方法。

但是,System.out.println从不显示除列的初始绑定值之外的任何内容。当我向setCellValueFactory()主体添加setCellValueFactory()时,我发现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成功将单个值发送到EventStream然后绑定?

这是我的SSCCE。

Observable

更新

我认为我在下面提出的解决方案中遇到了问题。如果除Platform平台之外的其他线程上发出任何rxToProperty,则不会向该属性填充任何值。

我尝试通过检查调用Property的线程是否是平台线程然后将其放在Platform线程上来解决这个问题,但这不起作用并再次导致无限循环。我不知道财产的安全性是否会破坏事物。

但是如何在多个线程上发出一个Observable以安全地填充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); } } ?这是我更新的SSCCE显示此行为。 &#34; X&#34;列永远不会填充,因为它是多线程的,但是&#34; Y&#34;列,因为它保留在Platform线程上。

it

1 个答案:

答案 0 :(得分: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线程的后面,因此永远不会进入绑定?但仍然没有发出任何东西,表值保持在-1,即初始Binding值。

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无效的原因,我会将其标记为已接受的答案。

<强> 更新

Observables在非FX线程上发出一些问题,并且值永远不会填充到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