2015-06-12 4 views
2

Я столкнулся с проблемой с 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); 
    } 
} 
+0

Я собираюсь сделать предположение, что Binding постоянно признан недействительным. Я посмотрю, могу ли я запустить еще несколько экспериментов, чтобы убедиться, что RxJava, ReactFX или я несут ответственность за это поведение. – tmn

ответ

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() заставило бесконечный цикл уйти. Возможно, испущенное значение все время бросалось в обратную сторону потока графического интерфейса, поэтому никогда не попадало в привязку? Но все же ничего не испускалось, а значения таблицы остались на -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 потоках, и значение никогда не заселение к Property. Я обнаружил, что это можно решить, используя cache(), чтобы сохранить последнее значение, и оно будет re = испускать вызывающий поток, который подписывается, который будет потоком 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(); 
    } 

FINAL UPDATE Томас Микула дал некоторые очень полезный анализ для такого поведения, а также решение, по проекту ReactFX GitHub.

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

+1

Обнаружено интересное поведение, и найдено, что ReactFX может и не быть проблемой. Я также обнаружил, что мой 'rxToProperty()' выше не работает с несколькими потоками. См. Вопрос об обновлениях выше. – tmn

 Смежные вопросы

  • Нет связанных вопросов^_^