Search code examples
javafxjavafx-8rx-javareactfx

Using ReactFX to resize stage when nodes become invisible?


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();
         });
    });

Solution

  • 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.