Java Swing:滚动到JScrollPane的底部,以当前视口位置为条件
我试图模仿Adium和我见过的大多数其他聊天客户端的功能,当新消息进来时,滚动条会移到底部,但只有当你已经在那里的时候。换句话说,如果你已经滚动了几行并且正在阅读,当一条新消息进来时,它不会将你的位置跳到屏幕底部;那会很烦人。但是如果滚动到底部,程序会正确地假设您希望随时查看最新的消息,因此自动滚动 我花了一段时间试图模仿这一点;该平台似乎不惜一切代价打击这种行为。我能做的最好的事情如下: 在构造函数中:Java Swing:滚动到JScrollPane的底部,以当前视口位置为条件,java,user-interface,swing,jscrollpane,Java,User Interface,Swing,Jscrollpane,我试图模仿Adium和我见过的大多数其他聊天客户端的功能,当新消息进来时,滚动条会移到底部,但只有当你已经在那里的时候。换句话说,如果你已经滚动了几行并且正在阅读,当一条新消息进来时,它不会将你的位置跳到屏幕底部;那会很烦人。但是如果滚动到底部,程序会正确地假设您希望随时查看最新的消息,因此自动滚动 我花了一段时间试图模仿这一点;该平台似乎不惜一切代价打击这种行为。我能做的最好的事情如下: 在构造函数中: JTextArea chatArea = new JTextArea(); JScroll
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());
}
现在,这是可行的,但它很简陋,不理想,原因有二
if (atBottom) {
// append new line & do scroll
SwingUtilities.invokerLater(new Runnable(){
public void run() {
// append text
}});
} else {
// append newline
// append text
}
看看这个
尽管如此,我还是会更改代码以使用以下更高效的代码:
caret.setDot(textArea.getDocument().getLength());
基于前面的回答和进一步的谷歌搜索,我做了一个测试,其中一个线程在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;
}
}
过去几天我一直在尝试不同的方法来解决这个问题。我找到的最佳解决方案是替换JScrollPane视口的布局管理器,并在那里实现所有所需的滚动行为。跳跃的滚动条旋钮不见了,它燃烧得很快——如此之快,以至于它创造了另一个我不得不解决的比赛条件。详情如下:
您可以在这里找到完整的详细信息:有点晚了,但我发现了一些东西: 本质上,您可以使用以下代码:
JScrollPane scrollPane = new JScrollPane(myOversizedComponent);
// Injects smartscroll behaviour to your scrollpane
new SmartScroller(scrollPane);
这里是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;
}
}
该示例再次放弃选择。此外,滚动条仍在跳跃。我没有看到“滚动条跳跃”。如果找到所选内容,则始终可以更改代码以不重置插入符号位置。然后视口将不会自动滚动,但我认为这是您想要的行为。如果用户当前正在选择文本,滚动不应该是自动的。我不明白你对不同事件的意思。。。我将滚动作为一个单独的事件。拆分滚动和追加的目的是让GUI有机会在滚动和添加文本之间重新绘制。这允许滚动条在文本出现之前移动到底部。我正在拆分滚动条和追加,但显然追加必须在滚动之前发生,否则追加文本时我们将不在视图底部。请查看我上面发布的代码中的注释。添加一个换行符并在一个事件中滚动,然后在invokeLater中添加没有前面换行符的文本。您可以在此处查看我的答案:
DefaultCaret caret = (DefaultCaret)textArea.getCaret();
caret.setUpdatePolicy(DefaultCaret.ALWAYS_UPDATE);
JScrollPane scrollPane = new JScrollPane(myOversizedComponent);
// Injects smartscroll behaviour to your scrollpane
new SmartScroller(scrollPane);
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;
}
}