JavaFX ScrollPane setVvalue() not working as intended

3.2k views Asked by At

In my application I have a ScrollPane with a VBox that potentially contains anywhere from 0 to 1000 panes at a set height of 90 each. Each pane has related items that could loaded from a button inside that clears the VBox and adds its children along with a 'Return to Results' button that loads the previous list and should return to the same place in the ScrollPane. However, the setVvalue() doesn't appear to be working. The panes and their children are of the same class.

package application;

import java.util.ArrayList;
import java.util.List;

import javafx.application.Application;
import javafx.application.Platform;
import javafx.geometry.Pos;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ScrollPane;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;


public class Main extends Application {

    public VBox resultsBox;
    public ScrollPane scroll;
    public List<CustomClass> results = new ArrayList<CustomClass>();
    public Button returnToResults;

    public class CustomClass extends HBox {
        public List<CustomClass> items;

        public boolean hasItems() {
            return items != null && !items.isEmpty();
        }

        public CustomClass(String text, boolean hasItems) {
            setMinHeight(90);
            setPrefHeight(90);
            setMaxHeight(90);
            setMaxWidth(Double.MAX_VALUE);
            setAlignment(Pos.CENTER);
            setStyle("-fx-border-color: red");

            Button children = new Button("View Children "+text);
            children.setDisable(!hasItems);
            getChildren().add(children);
            children.setOnAction(e -> {
                viewChildren(this);
            });

            if(hasItems) {
                items = new ArrayList<CustomClass>();
                for(int i=0; i<10; i++) {
                    items.add(new CustomClass(text+"."+i, false));
                }
            }
        }
    }

    public double vValue = 0.0;

    public void viewChildren(CustomClass cc) {
        Platform.runLater(() -> {
            vValue = scroll.getVvalue();
            System.out.println("0. "+vValue);
            resultsBox.getChildren().clear();
            resultsBox.getChildren().addAll(cc.items);
            returnToResults.setDisable(false);
        });
    }

    public void returnToResults() {
        Platform.runLater(() -> {
            resultsBox.getChildren().clear();
            resultsBox.getChildren().addAll(results);
            System.out.println("1. "+vValue+"    "+scroll.getVvalue());
            scroll.setVvalue(vValue);
            System.out.println("2. "+vValue+" -> "+scroll.getVvalue()); // Sets correctly.
            returnToResults.setDisable(true);
        });
    }

    public void start(Stage stage) throws Exception {

        resultsBox = new VBox(5);
        resultsBox.setFillWidth(true);
        resultsBox.setAlignment(Pos.TOP_CENTER);

        scroll = new ScrollPane(resultsBox);
        scroll.setMaxWidth(Double.MAX_VALUE);
        scroll.setFitToWidth(true);

        for(int i=0; i<100; i++) {
            results.add(new CustomClass("#"+i, true));
        }

        resultsBox.getChildren().addAll(results);

        returnToResults = new Button("Return to Results");
        returnToResults.setStyle("-fx-base: green");
        returnToResults.setDisable(true);
        returnToResults.setOnAction(e -> {
            returnToResults();
        });

        BorderPane root = new BorderPane();
        root.setCenter(scroll);
        root.setBottom(returnToResults);

        Scene scene = new Scene(root,400,400);
        stage.setScene(scene);
        stage.show();
    }

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

The setVvalue() works and changes the ScrollPane back to its old value when printing it out but the ScrollPane itself does not go anywhere and stays at the top. Does it have to do with removing and adding Nodes to the VBox? Why is the ScrollPane not moving when setting its Vvalue?

2

There are 2 answers

3
James_D On BEST ANSWER

At the time you call scroll.setVvalue(vValue), the scroll pane hasn't performed the layout since its content has changed, and consequently hasn't "remeasured" the size of its content. So the scroll bar "thumb" is being repositioned by interpreting the vvalue in the context of the previous layout. You can fix this with

public void returnToResults() {
    Platform.runLater(() -> {
        vbox.getChildren.clear();
        vbox.getChildren.addAll(results);

        scroll.layout();

        scroll.setVvalue(vValue); 
        returnToResults.setDisable(true);
    });
}

Aside: Are these methods being called from a background thread? Why are their contents wrapped in a Platform.runLater(...)?

1
Moritz Schmidt On

In my case calling the layout() method didn't work. I also had to call applyCss() to my current scene.

The relevant FXML:

<ScrollPane fx:id="messageScrollPane" fitToWidth="true" fitToHeight="true">
    VBox fx:id="messageBox"/>
</ScrollPane>

I wanted the scrollpane to automatically scroll down if I add a new Label to

messageBox 

So my method to achieve this is the following:

public void addMessage(String message) {
    messageBox.getChildren().add(new Label(message));

    currentScene.applyCss();
    currentScene.layout();

    messageScrollPane.setVvalue(1.0);
}

Hope it helps someone.