java Swing:滚动到 JScrollPane 的底部,以当前视口位置为条件
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/2670124/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Swing: Scroll to bottom of JScrollPane, conditional on current viewport location
提问by I82Much
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.
我试图模仿 Adium 和我见过的大多数其他聊天客户端的功能,其中当新消息进来时滚动条会前进到底部,但前提是您已经在那里。换句话说,如果你向上滚动几行并正在阅读,当有新消息进来时,它不会将你的位置跳到屏幕底部;那会很烦人。但是,如果您滚动到底部,则程序正确地假定您希望始终查看最新消息,因此会相应地自动滚动。
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 jscrollpaneMaking a JScrollPane automatically scroll all the way down.
其他相关(但在我看来,在这方面并不完全有用)问题: 在 jscrollpane 上设置滚动条使 JScrollPane 自动一直向下滚动。
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.
根据 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);
}
}
});
}
I still have problem #2.
我仍然有问题#2。
采纳答案by Devon_C_Miller
Take a look at the scrollRectToVisible(Rectangle r)and modelToView(int pos)
看一下scrollRectToVisible(Rectangle r)和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:
至于滚动条,尝试强制滚动和追加发生在不同的事件上。例如:
if (atBottom) {
// append new line & do scroll
SwingUtilities.invokerLater(new Runnable(){
public void run() {
// append text
}});
} else {
// append newline
// append text
}
回答by Emil
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.
基于以前的答案和进一步的谷歌搜索,我做了一个测试,其中一个线程不断地将字符串附加到 JScrollPane 中的 JTextArea。我认为在这里使用 invokeLater 很重要,因为在我们滚动到该值之前,需要使用新的最大值更新 JScrollBar。使用 invokeLater,AWT 线程会处理这个问题。
private class AppendThread extends Thread {
public void run() {
while (true) {
String s = "random number = " + Math.random() + "\n";
boolean scrollDown = textAreaBottomIsVisible();
textArea.append(s);
if (scrollDown) {
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
JScrollBar bar = scrollPane.getVerticalScrollBar();
bar.setValue(bar.getMaximum());
}
}
);
}
try {
Thread.sleep(1000);
} catch (Exception e) {
System.err.println("exception = " + e);
}
}
}
private boolean textAreaBottomIsVisible() {
Adjustable sb = scrollPane.getVerticalScrollBar();
int val = sb.getValue();
int lowest = val + sb.getVisibleAmount();
int maxVal = sb.getMaximum();
boolean atBottom = maxVal == lowest;
return atBottom;
}
}
回答by desperateCoder
A little late but here is something i found: http://www.camick.com/java/source/SmartScroller.java
有点晚了,但我发现了一些东西:http: //www.camick.com/java/source/SmartScroller.java
In essence you can use following code:
本质上,您可以使用以下代码:
JScrollPane scrollPane = new JScrollPane(myOversizedComponent);
// Injects smartscroll behaviour to your scrollpane
new SmartScroller(scrollPane);
And here the SmartScroller class:
这里是 SmartScroller 类:
import java.awt.Component;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.text.*;
/**
* The SmartScroller will attempt to keep the viewport positioned based on
* the users interaction with the scrollbar. The normal behaviour is to keep
* the viewport positioned to see new data as it is dynamically added.
*
* Assuming vertical scrolling and data is added to the bottom:
*
* - when the viewport is at the bottom and new data is added,
* then automatically scroll the viewport to the bottom
* - when the viewport is not at the bottom and new data is added,
* then do nothing with the viewport
*
* Assuming vertical scrolling and data is added to the top:
*
* - when the viewport is at the top and new data is added,
* then do nothing with the viewport
* - when the viewport is not at the top and new data is added, then adjust
* the viewport to the relative position it was at before the data was added
*
* Similiar logic would apply for horizontal scrolling.
*/
public class SmartScroller implements AdjustmentListener
{
public final static int HORIZONTAL = 0;
public final static int VERTICAL = 1;
public final static int START = 0;
public final static int END = 1;
private int viewportPosition;
private JScrollBar scrollBar;
private boolean adjustScrollBar = true;
private int previousValue = -1;
private int previousMaximum = -1;
/**
* Convenience constructor.
* Scroll direction is VERTICAL and viewport position is at the END.
*
* @param scrollPane the scroll pane to monitor
*/
public SmartScroller(JScrollPane scrollPane)
{
this(scrollPane, VERTICAL, END);
}
/**
* Convenience constructor.
* Scroll direction is VERTICAL.
*
* @param scrollPane the scroll pane to monitor
* @param viewportPosition valid values are START and END
*/
public SmartScroller(JScrollPane scrollPane, int viewportPosition)
{
this(scrollPane, VERTICAL, viewportPosition);
}
/**
* Specify how the SmartScroller will function.
*
* @param scrollPane the scroll pane to monitor
* @param scrollDirection indicates which JScrollBar to monitor.
* Valid values are HORIZONTAL and VERTICAL.
* @param viewportPosition indicates where the viewport will normally be
* positioned as data is added.
* Valid values are START and END
*/
public SmartScroller(JScrollPane scrollPane, int scrollDirection, int viewportPosition)
{
if (scrollDirection != HORIZONTAL
&& scrollDirection != VERTICAL)
throw new IllegalArgumentException("invalid scroll direction specified");
if (viewportPosition != START
&& viewportPosition != END)
throw new IllegalArgumentException("invalid viewport position specified");
this.viewportPosition = viewportPosition;
if (scrollDirection == HORIZONTAL)
scrollBar = scrollPane.getHorizontalScrollBar();
else
scrollBar = scrollPane.getVerticalScrollBar();
scrollBar.addAdjustmentListener( this );
// Turn off automatic scrolling for text components
Component view = scrollPane.getViewport().getView();
if (view instanceof JTextComponent)
{
JTextComponent textComponent = (JTextComponent)view;
DefaultCaret caret = (DefaultCaret)textComponent.getCaret();
caret.setUpdatePolicy(DefaultCaret.NEVER_UPDATE);
}
}
@Override
public void adjustmentValueChanged(final AdjustmentEvent e)
{
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
checkScrollBar(e);
}
});
}
/*
* Analyze every adjustment event to determine when the viewport
* needs to be repositioned.
*/
private void checkScrollBar(AdjustmentEvent e)
{
// The scroll bar listModel contains information needed to determine
// whether the viewport should be repositioned or not.
JScrollBar scrollBar = (JScrollBar)e.getSource();
BoundedRangeModel listModel = scrollBar.getModel();
int value = listModel.getValue();
int extent = listModel.getExtent();
int maximum = listModel.getMaximum();
boolean valueChanged = previousValue != value;
boolean maximumChanged = previousMaximum != maximum;
// Check if the user has manually repositioned the scrollbar
if (valueChanged && !maximumChanged)
{
if (viewportPosition == START)
adjustScrollBar = value != 0;
else
adjustScrollBar = value + extent >= maximum;
}
// Reset the "value" so we can reposition the viewport and
// distinguish between a user scroll and a program scroll.
// (ie. valueChanged will be false on a program scroll)
if (adjustScrollBar && viewportPosition == END)
{
// Scroll the viewport to the end.
scrollBar.removeAdjustmentListener( this );
value = maximum - extent;
scrollBar.setValue( value );
scrollBar.addAdjustmentListener( this );
}
if (adjustScrollBar && viewportPosition == START)
{
// Keep the viewport at the same relative viewportPosition
scrollBar.removeAdjustmentListener( this );
value = value + maximum - previousMaximum;
scrollBar.setValue( value );
scrollBar.addAdjustmentListener( this );
}
previousValue = value;
previousMaximum = maximum;
}
}
回答by camickr
回答by kjkrum
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
过去几天我一直在尝试解决这个确切问题的不同方法。我发现的最佳解决方案是替换 JScrollPane 视口的布局管理器并在那里实现所有所需的滚动行为。跳动的滚动条旋钮不见了,它的速度非常快——如此之快,它创造了另一个我不得不解决的竞争条件。详细信息在这里:http: //www.java-forums.org/awt-swing/56808-scroll-bar-knob-jumpy-when-component-成长.html
回答by Stephane Grenier
public static void scrollToBottom(JComponent component)
{
Rectangle visibleRect = component.getVisibleRect();
visibleRect.y = component.getHeight() - visibleRect.height;
component.scrollRectToVisible(visibleRect);
}
You can find the full details here: How to make JTextPane autoscroll only when scroll bar is at bottom and scroll lock is off?
您可以在此处找到完整的详细信息:仅当滚动条位于底部且滚动锁定关闭时,如何使 JTextPane 自动滚动?
回答by Narain
DefaultCaret caret = (DefaultCaret)textArea.getCaret();
caret.setUpdatePolicy(DefaultCaret.ALWAYS_UPDATE);

