Swing:滚动到 JScrollPane 的底部,以当前视口位置为条件
我试图模仿 Adium 和我见过的大多数其他聊天客户端的功能,其中当新消息进入时滚动条会前进到底部,但前提是您已经在那里。换句话说,如果您向上滚动几行并正在阅读,当收到新消息时,它不会将您的位置跳到屏幕底部;而是将您的位置跳到屏幕底部。那会很烦人。但是,如果您滚动到底部,程序会正确地假设您希望始终看到最新的消息,因此会相应地自动滚动。
我曾花了一段时间试图模仿这一点。该平台似乎不惜一切代价打击这种行为。我能做的最好的事情如下:
在构造函数中:
JTextArea chatArea = new JTextArea();
JScrollPane chatAreaScrollPane = new JScrollPane(chatArea);
// We will manually handle advancing chat window
DefaultCaret caret = (DefaultCaret) chatArea.getCaret();
caret.setUpdatePolicy(DefaultCaret.NEVER_UPDATE);
在处理传入的新文本的方法中:
boolean atBottom = isViewAtBottom();
// Append the text using styles etc to the chatArea
if (atBottom) {
scrollViewportToBottom();
}
public boolean isAtBottom() {
// Is the last line of text the last line of text visible?
Adjustable sb = chatAreaScrollPane.getVerticalScrollBar();
int val = sb.getValue();
int lowest = val + sb.getVisibleAmount();
int maxVal = sb.getMaximum();
boolean atBottom = maxVal == lowest;
return atBottom;
}
private void scrollToBottom() {
chatArea.setCaretPosition(chatArea.getDocument().getLength());
}
现在,这可以工作,但它很卡顿并且不理想,原因有两个。
- 通过设置插入符位置,用户在聊天区域中可能进行的任何选择都将被删除。我可以想象,如果他尝试复制/粘贴,这会非常令人恼火。
- 由于滚动窗格的前进是在插入文本之后发生的,因此滚动条有一瞬间位于错误的位置,然后它在视觉上跳向末尾。这并不理想。
在你问之前,是的,我已经阅读了这篇关于 文本区域 的博客文章滚动,但默认滚动到底部的行为不是我想要的。
其他相关的(但在我看来,在这方面并不完全有帮助)问题: 在 jscrollpane 上设置滚动条 使 JScrollPane 自动一直向下滚动。
非常感谢在这方面的任何帮助。
编辑:
根据 Devon_C_Miller 的建议,我有一种改进的滚动到底部的方法,解决了问题 #1。
private void scrollToBottom() {
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
try {
int endPosition = chatArea.getDocument().getLength();
Rectangle bottom = chatArea.modelToView(endPosition);
chatArea.scrollRectToVisible(bottom);
}
catch (BadLocationException e) {
System.err.println("Could not scroll to " + e);
}
}
});
}
我仍然有问题#2。
I am attempting to mimic the functionality of Adium and most other chat clients I've seen, wherein the scrollbars advance to the bottom when new messages come in, but only if you're already there. In other words, if you've scrolled a few lines up and are reading, when a new message comes in it won't jump your position to the bottom of the screen; that would be annoying. But if you're scrolled to the bottom, the program rightly assumes that you want to see the most recent messages at all times, and so auto-scrolls accordingly.
I have had a bear of a time trying to mimic this; the platform seems to fight this behavior at all costs. The best I can do is as follows:
In constructor:
JTextArea chatArea = new JTextArea();
JScrollPane chatAreaScrollPane = new JScrollPane(chatArea);
// We will manually handle advancing chat window
DefaultCaret caret = (DefaultCaret) chatArea.getCaret();
caret.setUpdatePolicy(DefaultCaret.NEVER_UPDATE);
In method that handles new text coming in:
boolean atBottom = isViewAtBottom();
// Append the text using styles etc to the chatArea
if (atBottom) {
scrollViewportToBottom();
}
public boolean isAtBottom() {
// Is the last line of text the last line of text visible?
Adjustable sb = chatAreaScrollPane.getVerticalScrollBar();
int val = sb.getValue();
int lowest = val + sb.getVisibleAmount();
int maxVal = sb.getMaximum();
boolean atBottom = maxVal == lowest;
return atBottom;
}
private void scrollToBottom() {
chatArea.setCaretPosition(chatArea.getDocument().getLength());
}
Now, this works, but it's janky and not ideal for two reasons.
- By setting the caret position, whatever selection the user may have in the chat area is erased. I can imagine this would be very irritating if he's attempting to copy/paste.
- Since the advancement of the scroll pane occurs after the text is inserted, there is a split second where the scrollbar is in the wrong position, and then it visually jumps towards the end. This is not ideal.
Before you ask, yes I've read this blog post on Text Area Scrolling, but the default scroll to bottom behavior is not what I want.
Other related (but to my mind, not completely helpful in this regard) questions:
Setting scroll bar on a jscrollpane
Making a JScrollPane automatically scroll all the way down.
Any help in this regard would be very much appreciated.
Edit:
As per Devon_C_Miller's advice, I have an improved way of scrolling to the bottom, solving issue #1.
private void scrollToBottom() {
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
try {
int endPosition = chatArea.getDocument().getLength();
Rectangle bottom = chatArea.modelToView(endPosition);
chatArea.scrollRectToVisible(bottom);
}
catch (BadLocationException e) {
System.err.println("Could not scroll to " + e);
}
}
});
}
I still have problem #2.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(7)
看一下 scrollRectToVisible(矩形 r) 和 modelToView(int pos)
这应该可以让您找到所需的内容,而不会干扰用户的选择。
至于滚动条,尝试强制滚动和追加发生在不同的事件上。例如:
Take a look at the scrollRectToVisible(Rectangle r) and modelToView(int pos)
That should get you what you're looking for without disturbing the user's selection.
As for the scrollbar, try forcing the scroll and the append into occur on different events. For example:
根据之前的答案和进一步的 Google 搜索,我做了一个测试,其中线程连续将字符串附加到 JScrollPane 中的 JTextArea。我认为在这里使用 invokeLater 很重要,因为在滚动到该值之前,需要使用新的最大值更新 JScrollBar。使用invokeLater,AWT 线程将处理这个问题。
Based on previous answers and further Google'ing I made a test in which a Thread continously appends Strings to a JTextArea in a JScrollPane. I think it is important to use invokeLater here, since the JScrollBar needs to be updated with its new maximum value before we scroll to that value. Using invokeLater, the AWT Thread will take care of this.
有点晚了,但我发现了一些东西: http://www.camick.com/java /source/SmartScroller.java
本质上,您可以使用以下代码:
这里是 SmartScroller 类:
A little late but here is something i found: http://www.camick.com/java/source/SmartScroller.java
In essence you can use following code:
And here the SmartScroller class:
查看此示例。
尽管如此,我会更改代码以使用以下更有效的代码:
Check out this example.
Although, I would change the code to use the following which is more efficient:
在过去的几天里,我一直在尝试不同的方法来解决这个问题。我发现的最佳解决方案是替换 JScrollPane 视口的布局管理器并在那里实现所有所需的滚动行为。跳动的滚动条旋钮消失了,而且速度非常快——快到它创造了另一个我必须解决的竞争条件。详细信息在这里: http ://www.java-forums.org/awt-swing/56808-scroll-bar-knob-jumpy-when-component-grinding.html
I've spent the last few days trying different approaches to this exact problem. The best solution I've found is to replace the layout manager of JScrollPane's viewport and implement all the desired scrolling behavior there. The jumpy scrollbar knob is gone, and it's blazing fast -- so fast that it created another race condition I had to work around. Details are here: http://www.java-forums.org/awt-swing/56808-scroll-bar-knob-jumpy-when-component-growing.html
您可以在此处找到完整的详细信息:如何让JTextPane仅在滚动条位于底部且滚动锁定关闭时自动滚动?
You can find the full details here: How to make JTextPane autoscroll only when scroll bar is at bottom and scroll lock is off?