Emily L.
Emily L.

Reputation: 5931

Sizing a JavaFX stage with a ScrollPane nested in a BorderPane

In my application I have a BorderPane which contains a ScrollPane in the center. The ScrollPane has two logical sections top and bottom.

When I create a stage to show the BorderPane I want the stage to be sized so that only the top section of ScrollPane is shown and the user can scroll down to see the bottom section if they need it.

So I try to bind the prefHeight property of the ScrollPane to the prefHeight property of the top section. Hoping that the ScrollPane will get the right height and the stage will look "Just Right™". For some reason this fails miserably. But when I attach a listener to the prefHeight property it does indeed return the correct height. So I'm flabbergasted as to what is going on.

Interestingly, if I remove the BorderPane it works in the SSCCE but this is not an option in the real case.

EDIT: SSCCE updated

Here is a SSCCE:

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Menu;
import javafx.scene.control.MenuBar;
import javafx.scene.control.ScrollPane;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Region;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class SSCCE extends Application {

    @Override
    public void start(Stage aStage) throws Exception {
        VBox top = new VBox(new ComboBox<>(), new ComboBox<>(), new ComboBox<>());
        top.setStyle("-fx-background-color: pink");
        top.heightProperty().addListener((aObs, aOld, aNew)->{
            System.out.println(aNew.doubleValue());
        });

        Region regionBottom = new Region();
        regionBottom.setPrefHeight(100);
        regionBottom.setPrefWidth(200);
        regionBottom.setStyle("-fx-background-color: red");

        VBox content = new VBox(top, regionBottom);

        ScrollPane scrollPane = new ScrollPane(content);
        scrollPane.setFitToWidth(true);
        scrollPane.prefHeightProperty().bind(top.prefHeightProperty());

        BorderPane root = new BorderPane(scrollPane);
        root.setTop(new MenuBar(new Menu("Foo")));

        Scene scene = new Scene(root);
        aStage.setScene(scene);
        aStage.sizeToScene();
        aStage.show();
        aStage.toFront();
    }

    public static void main(String[] args) {
        launch();
    }
}

Actual result:

enter image description here

Expected result:

enter image description here

Upvotes: 0

Views: 1344

Answers (1)

James_D
James_D

Reputation: 209225

The prefHeightProperty defaults to a sentinel value, indicating that the pref height should be computed. (Region.USE_COMPUTED_SIZE). Thus when you bind the prefHeight of the scroll pane, you effectively instruct it to do the same, which basically means it will get its default size.

Persuading the layout to be performed and measured in order to get the correct size for the scroll pane's preferred height is a bit of a hack. The correct layout sizes depend on CSS, so you must first force the CSS to be applied, and then you can ask for the computed pref height of the top area. You can do the latter with a call to

top.prefHeight(-1);

So the following works:

    ScrollPane scrollPane = new ScrollPane(content);
    scrollPane.setFitToWidth(true);
//    scrollPane.prefHeightProperty().bind(top.heightProperty());


    BorderPane root = new BorderPane(scrollPane);
    root.setTop(new MenuBar(new Menu("Foo")));

    Scene scene = new Scene(root);

    aStage.setScene(scene);

    root.applyCss();
    scrollPane.setPrefHeight(top.prefHeight(-1));
    root.requestLayout();

    aStage.sizeToScene();
    aStage.show();
    aStage.toFront();

(Note the call to aStage.sizeToScene(); is not needed: this is the default behavior anyway.)

Upvotes: 1

Related Questions