我正在尝试使用通过事件处理程序放入的新文本让 TextArea 自动滚动到底部。每个新条目只是一长串文本,每个条目由换行符分隔。我尝试了一个更改处理程序,它将 setscrolltop 设置为 Double.MIN_VALUE 但无济于事。关于如何做到这一点的任何想法?
6 回答
您必须向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
侦听器,但它不会。这是我自己使用的解决方法,希望对您有所帮助。
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;
}
}
在不使用 appendText 的情况下替代那个奇怪的 setText 错误
textArea.selectPositionCaret(textArea.getLength());
textArea.deselect(); //removes the highlighting
我没有足够的声誉来发表评论,但想为未来的读者提供一些见解,了解为什么 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 的答案在侦听器中设置滚动量时,会发生以下情况:
- TextProperty 已更新
- 调用了您的侦听器,并设置了滚动
- doSet 被称为
- 调用 textUpdated
- 滚动条被设置回左上角
然后,当您附加“”时,
- TextProperty 已更新
- 调用了您的侦听器,并设置了滚动
上面的 javadoc 很清楚为什么会出现这种情况 - doSet 仅在使用 setText 时调用。事实上,appendText 调用 insertText 调用 replaceText - 并且 javadoc 进一步指出 replaceText 不会触发对 doSet 的调用。
这种行为相当令人恼火,特别是因为这些都是最终方法,乍一看并不明显 - 但不是错误。
我要添加到 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 获取了太多数据。
正如马修发布的那样,setText
电话是问题所在。一个简单的解决方法是调用clear
,appendText
然后调用setScrollTop
. 上面的其他建议对我来说效果不佳,延迟时间足够长,但行为不可靠。
textAreaListener = (observable, oldValue, newValue) -> {
textArea.clear();
textArea.appendText(newValue);
textArea.setScrollTop(Double.MAX_VALUE);
};