Using ReactFX to resize stage when nodes become invisible?

242 views Asked by At

I have a JavaFX dashboard that hides and shows components based on complicated contexts, so ReactFX is a great utility for this.

I created some nested closures by looping through each node, creating an EventStream off eachvisibleProperty(), and then subscribing an operation to switch the managedProperty() and call sizeToScene(). Although my solution works it does not feel very clean. I feel like I should be using a flatmap or something. Is there a more purely reactive way to implement this?

    gridPane.getChildren().stream().forEach(c -> {
         EventStreams.changesOf(c.visibleProperty()).subscribe(b -> {
             c.managedProperty().set(b.getNewValue());
             primaryStage.sizeToScene();
         });
    });
1

There are 1 answers

2
Tomas Mikula On BEST ANSWER

I will assume that the child list of your gridPane is fixed, since in your code you just iterate through it once.

First, why not bind managedProperty of each child to its visibleProperty?

gridPane.getChildren().stream().forEach(c -> {
    c.managedProperty().bind(c.visibleProperty());
});

To get notified when any child changes its visibility, you can construct and observe a single EventStream:

LiveList.map(gridPane.getChildren(), c -> EventStreams.valuesOf(c.visibleProperty()))
        .reduce((es1, es2) -> EventStreams.merge(es1, es2))
        .orElseConst(EventStreams.never()) // for the case of no children
        .values().flatMap(Function.identity())
        .subscribe(b -> primaryStage.sizeToScene());

Since we assume the child list is fixed, you can get away with something slightly simpler:

gridPane.getChildren().stream().map(c -> EventStreams.valuesOf(c.visibleProperty()))
        .reduce((es1, es2) -> EventStreams.merge(es1, es2))
        .orElse(EventStreams.never()) // for the case of no children
        .subscribe(b -> primaryStage.sizeToScene());

All that said, I would consider finding a solution that does not tamper with managedProperty.
EDIT: For example, filter the list of children by their visible property:

// your (fixed) list of children
List<Node> children0 = ...;

// list of children that triggers list changes when children change their visibility
ObservableList<Node> children = FXCollections.observableList(
        children0, ch -> new Observable[]{ ch.visibleProperty() });

// children filtered by visibility
ObservableList<Node> visibleChildren = children.filtered(Node::isVisible);

// bind GridPane's children to visible children
Bindings.bindContent(gridPane.getChildren(), visibleChildren);

You might need to store a reference to visibleChildren to prevent it from being garbage collected, due to the use of weak listeners in JavaFX.