javajavafxtextflow

Autoscroll JavaFX TextFlow


I have a JavaFX TextFlow wrapped in a ScrollPane, and I am trying to get it to automatically scroll to the bottom whenever a new Text is added to the TextFlow.

I have tried attaching listeners maxing the ScrollPane's vvalue to:

For an extra big thank you, I'd like to scroll such that if the new addition is too large to display at once, the ScrollPane scrolls such that the new addition is at the top, and the user should scroll manually to see the 'overflow'. As I said, this would be an added bonus, just scrolling to the bottom would be just fine as I don't expect such large additions (regularly).

And no, I'm not switching to a TextArea, in which this would be relatively simple. I want to be able to easily add regular, bold and italic text to the TextFlow, and TextArea doesn't support this. I also tried Thomas Mikula's RichTextFX, but

  1. It kept throwing StackOverflowErrors on internal code without explanation.
  2. I don't really want to use third-party libraries for this project.

So any solution that will work with TextFlow would be greatly appreciated.

EDIT: Solution, as requested:

private ScrollPane textContainer;
private TextFlow text;

public BaseGui() {
    //....
    text.getChildren().addListener(
                (ListChangeListener<Node>) ((change) -> {
                    text.layout();
                    textContainer.layout();
                    textContainer.setVvalue(1.0f);
                }));
    textContainer.setContent(text);
    //....
}

public void appendBold(String msg) { //similar for italic and regular
    append(msg, "-fx-font-weight: bold");
}

private synchronized void append(String msg, String style) {
    Platform.runLater(() -> {
        Text t = new Text(msg);
        t.setFont(segoe(13));
        if (!style.equals("")) {
            t.setStyle(style);
        }
        text.getChildren().add(t);
    });
}

It won't win any awards for code style, but as this is a personal project I don't really care.


Solution

  • Modify your 3rd approach to use layout() instead of requestLayout().

    requestLayout() marks the layout as dirty and causes a re-layout on the next pulse. In the code

    requestLayout();
    doSomethingThatDependsOnLayout();
    

    doSomethingThatDependsOnLayout() will see the old layout.

    layout() performs the layout immediately (synchronously), but only if the layout is dirty. (In your case, change of TextFlow's text marks its layout as dirty.) In the code

    layout();
    doSomethingThatDependsOnLayout();
    

    doSomethingThatDependsOnLayout() will see the new layout.