javafxscrollpane

ScrollPane.setVvalue() does not update scrollbar in javafx


I have a program where I can insert something in a textfield and then after pressing the enter button, it will be displayed as a label in a VBox. My layout looks like this: A tab with inside a borderpane with on the bottom a hbox containing a textfield and a button and at the top a scrollpane containing a vbox full of labels. Screenshot

This is the code:

        Tab consoleTab = new Tab("Console");
        consoleTab.setClosable(false);
        BorderPane consoleContent = new BorderPane();
        
        TextField commandEntry = new TextField();
        commandEntry.setPromptText("Enter command...");
        Button exe = new Button("Enter");
        HBox input = new HBox(5, commandEntry, exe);
        VBox outputL = new VBox();
        ScrollPane output = new ScrollPane();
        output.setMinHeight(365);
        output.setMaxHeight(365);
        output.setContent(outputL);
        EventHandler<ActionEvent> customEvent = e -> {
            String in = commandEntry.getText();
            if (in.equals("")) return;
            Label inserted = new Label("> "+in);
            inserted.setStyle("-fx-font-weight: bold");
            outputL.getChildren().add(inserted);
            commandEntry.setText("");
            Command cmd = new Command(in, outputL);
            cmd.execute(true);
            output.setVvalue(1); // This does not work
        };
        commandEntry.setOnAction(customEvent);
        exe.setOnAction(customEvent);
        consoleContent.setTop(output);
        consoleContent.setBottom(input);
        consoleContent.setPadding(new Insets(5, 5, 5, 5));
        consoleTab.setContent(consoleContent);

And this is the Command.java class:

public class Command {
    private String command;
    private VBox vbox;
    
    public static final String NEW_FILE = "new_file";
    public static final String OPEN_FILE = "open";
    public static final String SAVE_FILE = "save";
    public static final String LIST_FILES = "list";
    public static final String HELP = "help";
    
    public Command(String command, VBox v){
        this.command = command;
        this.vbox = v;
    }
    
    public void execute(boolean layout){
        String[] args = this.command.split(" ");
        String cmd = args[0];
        String outputText = "";
        switch (cmd){
            case NEW_FILE:
                break;
            case OPEN_FILE:
                outputText = "File opened";
                break;
            case SAVE_FILE:
                break;
            case LIST_FILES:
                outputText = "Files listed";
                break;
            case HELP:
                outputText = "Available commands:\nOPEN: open <file-name>\nLIST: list";
                break;
            default:
                outputText = "Command not found, type help to get the list of available commands";
                break;
        }
        if (layout){
            makeLayout(outputText);
        }
    }
    
    private void makeLayout(String outputText){
        this.vbox.getChildren().add(new Label(outputText));
    }
}

The problem is that when I call the setVvalue(1.0) method of the scrollpane, this is not setting the scrollbar at the bottom.
I have tried with using output.setContent(outputL) before output.setVvalue(1.0) but nothing changes.
Thanks for any help


Solution

  • Generate a layout pass before setting the scroll value. To generate a layout pass see:


    // change the content of the scroll pane
    // . . .
    
    // generate a layout pass on the scroll pane.
    scrollPane.applyCss();
    scrollPane.layout();
    
    // scroll to the bottom of the scroll pane.
    scrollPane.setVvalue(scrollPane.getVmax());
    

    Why this works

    When the layout pass occurs, the vValue of the scroll pane will change to keep the currently visible area displayed rather than the new area. If you then set the vValue to the maximum value, it will change from the value calculated in the layout pass to the maximum value, scrolling the pane to the bottom of the visible content.

    Sample code

    This is just a code snippet to demonstrate the approach, not an executable application.

    I did test the approach with the example code in the original question, and it worked fine.

    public void start(Stage stage) {
        VBox content = new VBox();
        final ScrollPane scrollPane = new ScrollPane();
        scrollPane.setContent(content);
    
        Button append = new Button("Append");
        append.setOnAction(e -> appendToScrollPane(scrollPane));
        
        VBox layout = new VBox(scrollPane, append);
        stage.setScene(new Scene(layout));
        stage.show();
    }
    
    public void appendToScrollPane(ScrollPane scrollPane) {
        // ... actions which add content to the scroll pane ...
    
        // generate a layout pass on the scroll pane.
        scrollPane.applyCss();
        scrollPane.layout();
    
        // scroll to the bottom of the scroll pane.
        scrollPane.setVvalue(scrollPane.getVmax());
    }