4

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:

  • The ScrollPane's vvalue property itself.
    • This locks the ScrollPane to the bottom, which is not desired.
  • The ScrollPane's viewportBounds property.
    • This just doesn't work.
  • The TextFlow's children list.
    • This scrolls to the location that was the bottom before the latest Text was added, strangely. I have tried explicitly requesting layout before scrolling, but this had no effect

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.

DennisW
  • 1,047
  • 1
  • 8
  • 17
  • @DennisW : I actually have one question -> the content inside your BaseGui method, I wrote it in my initialize . And nothing works. Any suggestion. – Nepal12 Apr 13 '15 at 07:53
  • @Nepal12 I can't really say anything without seeing the code. Obviously, both `text` and `textContainer` will have to be initialized before that snippet is executed, but other than that, I'd have to see some code. – DennisW Apr 13 '15 at 07:56
  • @DennisW : I have paster my code here http://jsfiddle.net/nepal12/yy0tdkjk/ – Nepal12 Apr 13 '15 at 08:01
  • @Nepal12 I can't see any obvious mistakes. When you say 'nothing works', do you mean that you get an exception or that it simply doesn't display any text? – DennisW Apr 13 '15 at 08:08
  • Text is displayed properly but I neither have any scrolling nor any scroll bar is seen. – Nepal12 Apr 13 '15 at 08:10
  • @Nepal12 I suggest you post a new question with a more complete section of code, I don't see anything wrong with what you have shown so far. – DennisW Apr 13 '15 at 08:14

1 Answers1

4

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.

Tomas Mikula
  • 6,357
  • 22
  • 37