21

我正在尝试使用通过事件处理程序放入的新文本让 TextArea 自动滚动到底部。每个新条目只是一长串文本,每个条目由换行符分隔。我尝试了一个更改处理程序,它将 setscrolltop 设置为 Double.MIN_VALUE 但无济于事。关于如何做到这一点的任何想法?

4

6 回答 6

32

您必须向TextArea元素添加一个侦听器以在其值更改时滚动到底部:

@FXML private TextArea txa; 

...

txa.textProperty().addListener(new ChangeListener<Object>() {
    @Override
    public void changed(ObservableValue<?> observable, Object oldValue,
            Object newValue) {
        txa.setScrollTop(Double.MAX_VALUE); //this will scroll to the bottom
        //use Double.MIN_VALUE to scroll to the top
    }
});

但是当您使用该方法时不会触发此侦听器setText(text),因此如果您想在setText(text)使用它之后触发appendText(text)它:

txa.setText("Text into the textArea"); //does not trigger the listener
txa.appendText("");  //this will trigger the listener and will scroll the
                     //TextArea to the bottom

这听起来更像是一个错误,一旦setText()应该触发changed侦听器,但它不会。这是我自己使用的解决方法,希望对您有所帮助。

于 2013-12-13T13:53:12.050 回答
12

txa.appendText("") 将在没有监听器的情况下滚动到底部。如果您想向后滚动并且文本不断更新,这将成为一个问题。txa.setText("") 将滚动条放回顶部,同样的问题也适用。

我的解决方案是扩展 TextArea 类,将 FXML 标签从 textArea 修改为 LogTextArea。这在哪里起作用,它显然会导致场景构建器出现问题,因为它不知道这个组件是什么

import javafx.scene.control.TextArea;
import javafx.scene.text.Font;

public class LogTextArea extends TextArea {

private boolean pausedScroll = false;
private double scrollPosition = 0;

public LogTextArea() {
    super();
}

public void setMessage(String data) {
    if (pausedScroll) {
        scrollPosition = this.getScrollTop();
        this.setText(data);
        this.setScrollTop(scrollPosition);
    } else {
        this.setText(data);
        this.setScrollTop(Double.MAX_VALUE);
    }
}

public void pauseScroll(Boolean pause) {
    pausedScroll = pause;
}

}
于 2015-01-02T18:46:31.460 回答
8

在不使用 appendText 的情况下替代那个奇怪的 setText 错误

textArea.selectPositionCaret(textArea.getLength());
textArea.deselect(); //removes the highlighting
于 2015-05-15T16:26:33.973 回答
7

我没有足够的声誉来发表评论,但想为未来的读者提供一些见解,了解为什么 setText 似乎没有触发听众,但 appendText 确实如此,就像数学的回答一样。

我自己在遇到类似问题时找到了这个答案,并查看了代码。这是目前谷歌搜索中“javafx textarea settext scroll”的最高结果。

setText 确实触发了侦听器。根据TextInputControl(TextArea的超类)中doSet方法的javadoc:

     * doSet is called whenever the setText() method was called directly
     * on the TextInputControl, or when the text property was bound,
     * unbound, or reacted to a binding invalidation. It is *not* called
     * when modifications to the content happened indirectly, such as
     * through the replaceText / replaceSelection methods.

在 doSet 方法中,调用了 updateText(),TextArea 将覆盖它:

  @Override final void textUpdated() {
        setScrollTop(0);
        setScrollLeft(0);
    }  

因此,当您按照 Math 的答案在侦听器中设置滚动量时,会发生以下情况:

  1. TextProperty 已更新
  2. 调用了您的侦听器,并设置了滚动
  3. doSet 被称为
  4. 调用 textUpdated
  5. 滚动条被设置回左上角

然后,当您附加“”时,

  1. TextProperty 已更新
  2. 调用了您的侦听器,并设置了滚动

上面的 javadoc 很清楚为什么会出现这种情况 - doSet 仅在使用 setText 时调用。事实上,appendText 调用 insertText 调用 replaceText - 并且 javadoc 进一步指出 replaceText 不会触发对 doSet 的调用。

这种行为相当令人恼火,特别是因为这些都是最终方法,乍一看并不明显 - 但不是错误。

于 2018-12-29T17:48:22.543 回答
2

我要添加到 jamesarbrown 的响应中的一个附录是使用布尔属性代替,以便您可以从 FXML 中访问它。像这样的东西。

import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.scene.control.TextArea;

public class LogTextArea extends TextArea {
    private final BooleanProperty pausedScrollProperty = new SimpleBooleanProperty(false);
    private double scrollPosition = 0;

    public LogTextArea() {
        super();
    }

    public void setMessage(String data) {
        if (isPausedScroll()) {
            scrollPosition = this.getScrollTop();
            this.setText(data);
            this.setScrollTop(scrollPosition);
        } else {
            this.setText(data);
            this.setScrollTop(Double.MAX_VALUE);
        }
    }

    public final BooleanProperty pausedScrollProperty() { return pausedScrollProperty; }
    public final boolean isPausedScroll() { return pausedScrollProperty.getValue(); }
    public final void setPausedScroll(boolean value) { pausedScrollProperty.setValue(value); }
}

但是,这个答案的问题在于,如果您被大量输入淹没(就像从 IO 流中检索日志时可能发生的那样),javaFX 线程将锁定,因为 TextArea 获取了太多数据。

于 2016-02-23T19:26:37.197 回答
0

正如马修发布的那样,setText电话是问题所在。一个简单的解决方法是调用clearappendText然后调用setScrollTop. 上面的其他建议对我来说效果不佳,延迟时间足够长,但行为不可靠。

  textAreaListener = (observable, oldValue, newValue) -> {
            textArea.clear();
            textArea.appendText(newValue);
            textArea.setScrollTop(Double.MAX_VALUE);
        };
于 2022-01-16T01:14:52.630 回答