java 保持 JTextArea 滚动位置

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/2039373/
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

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-29 19:05:32  来源:igfitidea点击:

Maintaing JTextArea scroll position

javauser-interfaceswing

提问by Sami

I have a JScrollPane with a JTextArea set as its view port.

我有一个 JScrollPane,其中 JTextArea 设置为它的视口。

I update the (multi line) text shown on the JTextArea continously about once a second. Each time the text updates, JScrollPane goes all the way to the bottom of the text.

我大约每秒一次连续更新 JTextArea 上显示的(多行)文本。每次文本更新时,JScrollPane 都会一直移动到文本底部。

Instead, I'd like to figure out the line number that is currently shown as the first line in the original text, and have that line be the first line shown when the text has been updated (or if the new text doesn't have that many lines, then scroll all the way to the bottom).

相反,我想找出当前在原始文本中显示为第一行的行号,并在文本更新时将该行作为显示的第一行(或者如果新文本没有那么多行,然后一直滚动到底部)。

My first attempt of doing this was to get the current caret position, figure the line based on that, and then set the text area to show that line:

我这样做的第一次尝试是获取当前插入符号位置,根据该位置计算行,然后设置文本区域以显示该行:

    int currentPos = textArea.getCaretPosition();
    int currentLine = 0;
    try {
        for(int i = 0; i < textArea.getLineCount(); i++) {
            if((currentPos >= textArea.getLineStartOffset(i)) && (currentPos < gameStateTextArea.getLineEndOffset(i))) {
                currentLine = i;
                break;
            }
        }
    } catch(Exception e) { }

    textArea.setText(text);

    int newLine = Math.min(currentLine, textArea.getLineCount());
    int newOffset = 0;
    try {
        newOffset = textArea.getLineStartOffset(newLine);
    } catch(Exception e) { }

    textArea.setCaretPosition(newOffset);

This was almost acceptable for my needs, but requires the user to click inside the text area to change the caret position, so that the scrolling will maintain state (which isn't nice).

这几乎可以满足我的需求,但需要用户在文本区域内单击以更改插入符号位置,以便滚动保持状态(这不好)。

How would I do this using the (vertical) scroll position instead ?

我将如何使用(垂直)滚动位置来做到这一点?

采纳答案by Carl Smotricz

This is pieced together, untested, from the API documentation:

这是从 API 文档中拼凑起来的,未经测试:

  • use getViewport()on your JScrollPaneto get a hold of the viewport.
  • use Viewport.getViewPosition()to get the top-left coordinates. These are absolute, not a percentage of scrolled text.
  • use Viewport.addChangeListener()to be notified when the top-left position changes (among other things). You may want to create a mechanism to distinguish user changes from changes your program makes, of course.
  • use Viewport.setViewPosition()to set the top-left position to where it was before the disturbance.
  • getViewport()在您的JScrollPane上使用以获取视口。
  • 用于Viewport.getViewPosition()获取左上角坐标。这些是绝对的,而不是滚动文本的百分比。
  • 用于Viewport.addChangeListener()在左上角位置发生变化时收到通知(除其他外)。当然,您可能希望创建一种机制来区分用户更改和程序所做的更改。
  • 用于Viewport.setViewPosition()将左上角位置设置为干扰前的位置。

Update:

更新:

  • To stop JTextArea from scrolling, you may want to override its getScrollableTracksViewport{Height|Width}()methods to return false.
  • 要阻止 JTextArea 滚动,您可能需要覆盖其getScrollableTracksViewport{Height|Width}()方法以返回false.

Update 2:

更新 2:

The following code does what you want. It's amazing how much trouble I had to go to to get it to work:

以下代码执行您想要的操作。令人惊讶的是,我必须经历多少麻烦才能让它工作:

  • apparently the setViewPositionhas to be postponed using invokeLaterbecause if it's done too early the text update will come after it and nullify its effect.
  • also, for some weird reason perhaps having to do with concurrency, I had to pass the correct value to my Runnableclass in its constructor. I had been using the "global" instance of origand that kept setting my position to 0,0.
  • 显然setViewPosition必须推迟使用,invokeLater因为如果过早完成,文本更新将在它之后出现并使其效果无效。
  • 此外,由于某些可能与并发性有关的奇怪原因,我必须Runnable在其构造函数中将正确的值传递给我的类。我一直在使用 的“全局”实例,orig并且一直将我的位置设置为 0,0。


public class Sami extends JFrame implements ActionListener {

   public static void main(String[] args) {
      (new Sami()).setVisible(true);
   }

   private JTextArea textArea;
   private JScrollPane scrollPane;
   private JButton moreTextButton = new JButton("More text!");
   private StringBuffer text = new StringBuffer("0 Silly random text.\n");
   private Point orig = new Point(0, 0);

   public Sami() {
      setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      getContentPane().setLayout(new BorderLayout());
      this.textArea = new JTextArea() {
         @Override
         public boolean getScrollableTracksViewportHeight() {
            return false;
         }
         @Override
         public boolean getScrollableTracksViewportWidth() {
            return false;
         }
      };
      this.scrollPane = new JScrollPane(this.textArea);
      getContentPane().add(this.scrollPane, BorderLayout.CENTER);
      this.moreTextButton.addActionListener(this);
      getContentPane().add(this.moreTextButton, BorderLayout.SOUTH);
      setSize(400, 300);
   }

   @Override
   public void actionPerformed(ActionEvent arg0) {
      int lineCount = this.text.toString().split("[\r\n]").length;
      this.text.append(lineCount + "The quick brown fox jumped over the lazy dog.\n");
      Point orig = this.scrollPane.getViewport().getViewPosition();
      // System.out.println("Orig: " + orig);
      this.textArea.setText(text.toString());
      SwingUtilities.invokeLater(new LaterUpdater(orig));
   }

   class LaterUpdater implements Runnable {
      private Point o;
      public LaterUpdater(Point o) {
         this.o = o;
      }
      public void run() {
         // System.out.println("Set to: " + o);
         Sami.this.scrollPane.getViewport().setViewPosition(o);
      }
   } 

}

回答by bdumitriu

I encountered the same problem and found that this answerincludes a nice solution that works in this case:

我遇到了同样的问题,发现这个答案包含一个很好的解决方案,适用于这种情况:

DefaultCaret caret = (DefaultCaret) jTextArea.getCaret();
caret.setUpdatePolicy(DefaultCaret.NEVER_UPDATE);