Search code examples
javajavafxautoscrollscrollpane

ScrollPane javafx auto scrolling (setting vvalue to 1.0) only scrolls to the item before the last


So, the situation is... I have a vbox inside a scrollpane. I am adding hbox's into the vbox and then calling vbox.setVvalue(1.0) after every insert.

However say, there are 5 hbox's, the scroller only makes it so that the last visible item is the 4th hbox - with one hbox below what is currently seen(needing to be scrolled down to be visible).

I've found a solution which is to bind the scrollpane's vvalue property to the vbox's heightproperty like so: scrollPane.vvalueProperty().bind(vbox.heightProperty()) which i assume changes the vvalue to the max every time the vbox height is changed (i.e when a new hbox is added).

However, i still would like to improve my knowledge and why the first (setting the vvalue of the scrollpane after every insert) is different from binding the properties. Thanks!


Solution

  • Setting the new vvalue happens before the layout pass caused by modifying the VBox, but the result applied before the layout pass. Since the formula for the y coordinate of the top that are shown in the viewport is

    top = max(0, vvalue * (contentHeight - viewportHeight))
    

    and during the layout pass the content's top left is kept in place, you see the bottom of the old content at the bottom of the viewport.

    To fix this you could manually trigger a layout pass on the ScrollPane using

    scrollPane.applyCss();
    scrollPane.layout();
    

    Example

    @Override
    public void start(Stage primaryStage) {
        VBox content = new VBox();
        ScrollPane scrollPane = new ScrollPane(content);
        VBox.setVgrow(scrollPane, Priority.ALWAYS);
        Button button = new Button("fill");
        button.setOnAction(evt -> {
            for (int i = 0; i < 20; i++) {
                content.getChildren().add(new Text(Integer.toString(i)));
            }
            System.out.println("content size before layout: " + content.getHeight());
    
            // manually layout scrollPane
            scrollPane.applyCss();
            scrollPane.layout();
    
            System.out.println("content size after layout: " + content.getHeight());
            scrollPane.setVvalue(1d);
        });
    
        Scene scene = new Scene(new VBox(button, scrollPane), 200, 200);
        primaryStage.setScene(scene);
        primaryStage.show();
    }