Java中的“智能”自动滚动?
注意:我重新问这个问题是因为我需要一个答案,而答案中有一个断开的链接 作为学校项目的一部分,我正在编写一个简单的聊天程序,我希望用户能够在消息到达底部时自动跟踪消息,但如果他们正在查看更高的内容,则不必担心再次向上滚动。目前,我的代码只是滚动到底部,无论发生什么。下面是基本代码,我真的不知道如何将其转换为SSCCE。如果有人可以,请随时这样做Java中的“智能”自动滚动?,java,swing,concurrency,jscrollpane,jtextarea,Java,Swing,Concurrency,Jscrollpane,Jtextarea,注意:我重新问这个问题是因为我需要一个答案,而答案中有一个断开的链接 作为学校项目的一部分,我正在编写一个简单的聊天程序,我希望用户能够在消息到达底部时自动跟踪消息,但如果他们正在查看更高的内容,则不必担心再次向上滚动。目前,我的代码只是滚动到底部,无论发生什么。下面是基本代码,我真的不知道如何将其转换为SSCCE。如果有人可以,请随时这样做 JTextArea jta = new JTextArea(50, 50); JScrollPane scroll = new JScrollPane(j
JTextArea jta = new JTextArea(50, 50);
JScrollPane scroll = new JScrollPane(jta);
try {
int iter = 0;
while (true) { //Not a for loop because it should not end after x iterations
Thread.sleep(500);
jta.append("Test text #" + iter);
iter++;
//Check if they are at the bottom and if so scroll down to the new bottom.
}
} catch (InterruptedException ex) {
System.out.println("Error!");
}
不确定这是否是最好的方法,但这里有一些代码,每当用户向上滚动时,这些代码就会停止向下滚动。当他滚动到底部时,自动滚动再次开始 这个想法是通过比较文本区域的高度和滚动窗格中可见矩形的位置和高度来检查用户是否已上移。如果匹配,则表示滚动在底部,用户希望自动滚动。在另一种情况下,我们强制可视矩形在每次textarea更改时保持不变 小型:
不确定这是否是最好的方法,但这里有一些代码,每当用户向上滚动时,这些代码就会停止向下滚动。当他滚动到底部时,自动滚动再次开始 这个想法是通过比较文本区域的高度和滚动窗格中可见矩形的位置和高度来检查用户是否已上移。如果匹配,则表示滚动在底部,用户希望自动滚动。在另一种情况下,我们强制可视矩形在每次textarea更改时保持不变 小型:
老苹果MacOS的方式是有可能有几个自动分裂窗格,可以从下面向上撕开,然后再次下降
他们将在同一样式的文档上创建多个视图文本框,其中只有底部的一个会自动滚动。旧的苹果MacOS方式是可以有几个自动拆分窗格,可以从下面向上撕开,然后再次放下 他们将在同一个StyledDocument上创建多个视图文本框,其中只有底部的文本框会自动滚动。编辑: 我用一个更灵活的版本替换了下面的代码,该版本可以在JScrollPane中的任何组件上工作。签出: 以下是一些可重用代码,可供包含JTextArea或JTextPane的任何滚动窗格使用:
import java.awt.*;
import java.awt.event.*;
import java.util.Date;
import javax.swing.*;
import javax.swing.text.*;
public class ScrollControl implements AdjustmentListener
{
private JScrollBar scrollBar;
private JTextComponent textComponent;
private int previousExtent = -1;
public ScrollControl(JScrollPane scrollPane)
{
Component view = scrollPane.getViewport().getView();
if (! (view instanceof JTextComponent))
throw new IllegalArgumentException("Scrollpane must contain a JTextComponent");
textComponent = (JTextComponent)view;
scrollBar = scrollPane.getVerticalScrollBar();
scrollBar.addAdjustmentListener( this );
}
@Override
public void adjustmentValueChanged(final AdjustmentEvent e)
{
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
checkScrollBar(e);
}
});
}
private void checkScrollBar(AdjustmentEvent e)
{
// The scroll bar model contains information needed to determine the
// caret update policy.
JScrollBar scrollBar = (JScrollBar)e.getSource();
BoundedRangeModel model = scrollBar.getModel();
int value = model.getValue();
int extent = model.getExtent();
int maximum = model.getMaximum();
DefaultCaret caret = (DefaultCaret)textComponent.getCaret();
// When the size of the viewport changes there is no need to change the
// caret update policy.
if (previousExtent != extent)
{
// When the height of a scrollpane is decreased the scrollbar is
// moved up from the bottom for some reason. Reposition the
// scrollbar at the bottom
if (extent < previousExtent
&& caret.getUpdatePolicy() == DefaultCaret.UPDATE_WHEN_ON_EDT)
{
scrollBar.setValue( maximum );
}
previousExtent = extent;
return;
}
// Text components will not scroll to the bottom of a scroll pane when
// a bottom inset is used. Therefore the location of the scrollbar,
// the height of the viewport, and the bottom inset value must be
// considered when determining if the scrollbar is at the bottom.
int bottom = textComponent.getInsets().bottom;
if (value + extent + bottom < maximum)
{
if (caret.getUpdatePolicy() != DefaultCaret.NEVER_UPDATE)
caret.setUpdatePolicy(DefaultCaret.NEVER_UPDATE);
}
else
{
if (caret.getUpdatePolicy() != DefaultCaret.UPDATE_WHEN_ON_EDT)
{
caret.setDot(textComponent.getDocument().getLength());
caret.setUpdatePolicy(DefaultCaret.UPDATE_WHEN_ON_EDT);
}
}
}
private static void createAndShowUI()
{
JPanel center = new JPanel( new GridLayout(1, 2) );
String text = "1\n2\n3\n4\n5\n6\n7\n8\n9\n0\n";
final JTextArea textArea = new JTextArea();
textArea.setText( text );
textArea.setEditable( false );
center.add( createScrollPane( textArea ) );
System.out.println(textArea.getInsets());
final JTextPane textPane = new JTextPane();
textPane.setText( text );
textPane.setEditable( false );
center.add( createScrollPane( textPane ) );
textPane.setMargin( new Insets(5, 3, 7, 3) );
System.out.println(textPane.getInsets());
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(center, BorderLayout.CENTER);
frame.setSize(500, 200);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
Timer timer = new Timer(2000, new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
try
{
Date now = new Date();
textArea.getDocument().insertString(textArea.getDocument().getLength(), "\n" + now.toString(), null);
textPane.getDocument().insertString(textPane.getDocument().getLength(), "\n" + now.toString(), null);
}
catch (BadLocationException e1) {}
}
});
timer.start();
}
private static JComponent createScrollPane(JComponent component)
{
JScrollPane scrollPane = new JScrollPane(component);
new ScrollControl( scrollPane );
return scrollPane;
}
public static void main(String[] args)
{
EventQueue.invokeLater(new Runnable()
{
public void run()
{
createAndShowUI();
}
});
}
}
编辑:
已更新代码,以便在滚动窗格的高度降低时也能正常工作。出于某种原因,默认行为是将滚动条上移一行,这意味着添加新文本时滚动条将不再位于底部。编辑:
我用一个更灵活的版本替换了下面的代码,该版本可以在JScrollPane中的任何组件上工作。签出:
以下是一些可重用代码,可供包含JTextArea或JTextPane的任何滚动窗格使用:
import java.awt.*;
import java.awt.event.*;
import java.util.Date;
import javax.swing.*;
import javax.swing.text.*;
public class ScrollControl implements AdjustmentListener
{
private JScrollBar scrollBar;
private JTextComponent textComponent;
private int previousExtent = -1;
public ScrollControl(JScrollPane scrollPane)
{
Component view = scrollPane.getViewport().getView();
if (! (view instanceof JTextComponent))
throw new IllegalArgumentException("Scrollpane must contain a JTextComponent");
textComponent = (JTextComponent)view;
scrollBar = scrollPane.getVerticalScrollBar();
scrollBar.addAdjustmentListener( this );
}
@Override
public void adjustmentValueChanged(final AdjustmentEvent e)
{
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
checkScrollBar(e);
}
});
}
private void checkScrollBar(AdjustmentEvent e)
{
// The scroll bar model contains information needed to determine the
// caret update policy.
JScrollBar scrollBar = (JScrollBar)e.getSource();
BoundedRangeModel model = scrollBar.getModel();
int value = model.getValue();
int extent = model.getExtent();
int maximum = model.getMaximum();
DefaultCaret caret = (DefaultCaret)textComponent.getCaret();
// When the size of the viewport changes there is no need to change the
// caret update policy.
if (previousExtent != extent)
{
// When the height of a scrollpane is decreased the scrollbar is
// moved up from the bottom for some reason. Reposition the
// scrollbar at the bottom
if (extent < previousExtent
&& caret.getUpdatePolicy() == DefaultCaret.UPDATE_WHEN_ON_EDT)
{
scrollBar.setValue( maximum );
}
previousExtent = extent;
return;
}
// Text components will not scroll to the bottom of a scroll pane when
// a bottom inset is used. Therefore the location of the scrollbar,
// the height of the viewport, and the bottom inset value must be
// considered when determining if the scrollbar is at the bottom.
int bottom = textComponent.getInsets().bottom;
if (value + extent + bottom < maximum)
{
if (caret.getUpdatePolicy() != DefaultCaret.NEVER_UPDATE)
caret.setUpdatePolicy(DefaultCaret.NEVER_UPDATE);
}
else
{
if (caret.getUpdatePolicy() != DefaultCaret.UPDATE_WHEN_ON_EDT)
{
caret.setDot(textComponent.getDocument().getLength());
caret.setUpdatePolicy(DefaultCaret.UPDATE_WHEN_ON_EDT);
}
}
}
private static void createAndShowUI()
{
JPanel center = new JPanel( new GridLayout(1, 2) );
String text = "1\n2\n3\n4\n5\n6\n7\n8\n9\n0\n";
final JTextArea textArea = new JTextArea();
textArea.setText( text );
textArea.setEditable( false );
center.add( createScrollPane( textArea ) );
System.out.println(textArea.getInsets());
final JTextPane textPane = new JTextPane();
textPane.setText( text );
textPane.setEditable( false );
center.add( createScrollPane( textPane ) );
textPane.setMargin( new Insets(5, 3, 7, 3) );
System.out.println(textPane.getInsets());
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(center, BorderLayout.CENTER);
frame.setSize(500, 200);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
Timer timer = new Timer(2000, new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
try
{
Date now = new Date();
textArea.getDocument().insertString(textArea.getDocument().getLength(), "\n" + now.toString(), null);
textPane.getDocument().insertString(textPane.getDocument().getLength(), "\n" + now.toString(), null);
}
catch (BadLocationException e1) {}
}
});
timer.start();
}
private static JComponent createScrollPane(JComponent component)
{
JScrollPane scrollPane = new JScrollPane(component);
new ScrollControl( scrollPane );
return scrollPane;
}
public static void main(String[] args)
{
EventQueue.invokeLater(new Runnable()
{
public void run()
{
createAndShowUI();
}
});
}
}
编辑:
已更新代码,以便在滚动窗格的高度降低时也能正常工作。出于某种原因,默认行为是将滚动条向上移动一行,这意味着添加新文本时滚动条将不再位于底部。最好在答案下方留下一条注释,并保留断开的链接,询问回答者可以修复链接/提供另一个例子。如果你打算稍后分享你的发现,为什么现在发布问题?顺便问一下,你有没有试着了解发布代码背后的想法?为了获得更好的帮助,请尽快发布一个简短的、可运行的、可编译的、非代码截断的锁定事件调度线程,更多的是在Oracles教程Concurrency in SwingAh中,我刚刚看到你确实在原始答案下留下了评论。但我认为这个答案的作者应该得到比17分钟多一点的时间。根本不能保证他/她从那时起就一直在线,更不用说找到一篇有解决方案的新帖子了。旁注:JTextArea和JScrollPane的创建必须在事件调度线程EDT上进行。在EDT上有一个whiletrue循环将阻止此线程,这将确保您永远看不到附加的文本。对于这段代码,最好使用javax.swing.Timer。也不再有文件证明append是线程安全的,这使得Swing Timer成为一个更好的选择。最好在答案下面留下一条评论,并附上断开的链接,询问回答者可以修复链接/提供另一个例子。如果你以后要分享你的发现,为什么现在就发布问题?顺便问一下,你有没有试着了解发布代码背后的想法?为了获得更好的帮助,请尽快发布一个简短的、可运行的、可编译的、非代码截断的锁定事件调度线程,更多的是在Oracles教程Concurrency in SwingAh中,我刚刚看到你确实在原始答案下留下了评论。但是我认为t的作者
答案应该比17分钟多一点时间。根本不能保证他/她从那时起就一直在线,更不用说找到一篇有解决方案的新帖子了。旁注:JTextArea和JScrollPane的创建必须在事件调度线程EDT上进行。在EDT上有一个whiletrue循环将阻止此线程,这将确保您永远看不到附加的文本。对于这段代码,最好使用javax.swing.Timer。也不再有文件证明append是线程安全的,这使得Swing计时器成为一个更好的选择我应该为所有平台,而不仅仅是任何特定的平台,这就是我选择使用Java的原因。我应该为所有平台,而不仅仅是任何特定的平台,这就是我选择使用Java的原因。我实际上在一个线程中实现了它,所以计时器有点冗余。然而,这看起来像我想要的。我马上回来拿测试结果……是的,这看起来正是我想要的!谢谢显然,我需要对它进行一些编辑,以确保它在我的程序中正常工作,但除此之外,它是完美的。@NickHartley计时器只是用来模拟循环/传入数据的。此外,Swing计时器执行EDT上的所有操作,避免了需要受保护的UI调用,尽管在本例中,这是不必要的,因为JTextArea.append是线程安全的。我认为这是为了模拟循环,因为它会一次又一次地重复。然而,我不知道计时器在EDT上运行。。。这可能在将来派上用场。再次感谢SSCCE和提供的信息:无意冒犯,但我认为将来遇到这个问题的人会希望看到一个为他们做这一切并提供解释的预制类,正如下面camickr所说的那样。我只希望我能接受两个答案,因为这一个也很棒。我实际上在一个线程中实现了这一点,所以计时器有点多余。然而,这看起来像我想要的。我马上回来拿测试结果……是的,这看起来正是我想要的!谢谢显然,我需要对它进行一些编辑,以确保它在我的程序中正常工作,但除此之外,它是完美的。@NickHartley计时器只是用来模拟循环/传入数据的。此外,Swing计时器执行EDT上的所有操作,避免了需要受保护的UI调用,尽管在本例中,这是不必要的,因为JTextArea.append是线程安全的。我认为这是为了模拟循环,因为它会一次又一次地重复。然而,我不知道计时器在EDT上运行。。。这可能在将来派上用场。再次感谢SSCCE和提供的信息:无意冒犯,但我认为将来遇到这个问题的人会希望看到一个为他们做这一切并提供解释的预制类,正如下面camickr所说的那样。我只希望我能接受两个答案,因为这一个也很好。制作这个很好,但我如何使用它呢?我会创建这个类的一个对象,然后这个类做其他的事情吗?或者我必须做些别的事情才能让它工作?不过,这似乎是解决我问题的另一个很好的办法,如果我能弄明白,我可能会接受这个办法。遗憾的是,我不能同时接受这两种情况……一旦创建了JTextArea并将其添加到JScrollPane中,就只有一行代码可以使用它:new ScrollControl scrollPane;所有的计时器代码只是为了证明这个类是有效的。在您的应用程序中,您只需像当前一样将文本添加到文本区域,滚动将自动进行。此外,您可能希望在周一再次查看。我计划发布这个类的一个更通用的版本,它可以在任何滚动窗格上工作。那就是滚动窗格可以包含JTextArea,JTextPane,JList,JTable…哇。这真是太棒了,制作一个只需要一行代码的类,可以处理几乎所有可以滚动的东西。。。顺便说一句,您可以使用`标记来显示代码,就像它在“帮助”中所说的那样。它只是让我们更容易分辨出它是代码,而不是一个强调的普通语句。这是一个伟大的工作,但我该如何使用它呢?我会创建这个类的一个对象,然后这个类做其他的事情吗?或者我必须做些别的事情才能让它工作?不过,这似乎是解决我问题的另一个很好的办法,如果我能弄明白,我可能会接受这个办法。遗憾的是,我不能同时接受这两种情况……一旦创建了JTextArea并将其添加到JScrollPane中,就只有一行代码可以使用它:new ScrollControl scrollPane;所有的计时器代码只是为了证明这个类是有效的。在您的应用程序中,您只需像当前一样将文本添加到文本区域,滚动将自动进行。此外,您可能希望在周一再次查看。我计划发布这个类的一个更通用的版本,它可以在任何滚动窗格上工作。那就是滚动窗格可以包含JTextArea,JTextPane,JList,JTable…哇。这真是太棒了,使一个类只需要一行代码就可以使用almos t所有可以滚动的内容。。。顺便说一句,您可以使用`标记来显示代码,就像它在“帮助”中所说的那样。它只是让我们更容易分辨出它是代码,而不是强调的普通语句。