Я столкнулся с проблемой с TableView
и с использованием реактивных привязок от ReactFX для setCellValueFactory
. Водяные привязки EventStream
берутся из RxJavaObservable
, однако в приведенном ниже коде вы найдете метод, который преобразует его в EventStream
.JavaFX и RxJava- TableView бесконечно называются setCellValueFactory()
Однако TableView
никогда не показывает ничего, кроме начальных значений привязки для столбцов. Когда я добавил в тело 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);
}
}
UPDATE
Я думаю, что я нашел проблему с решением я предложил ниже. Если какой-либо Observable
испускается на других потоках, кроме потока платформы, значение не будет занесено в свойство.
Я попытался исправить это, установив, что поток, вызывающий rxToProperty
, был потоком платформы, прежде чем поместить его в поток платформы, и это не сработало и снова вызвало бесконечный цикл. Я не знаю, подрывает ли потолок безопасность собственности.
Но как я могу получить 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);
}
}
Я собираюсь сделать предположение, что Binding постоянно признан недействительным. Я посмотрю, могу ли я запустить еще несколько экспериментов, чтобы убедиться, что RxJava, ReactFX или я несут ответственность за это поведение. – tmn