【问题标题】:Maintaing JTextArea scroll position保持 JTextArea 滚动位置
【发布时间】:2010-01-11 01:07:28
【问题描述】:

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

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

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

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

    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);

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

我将如何使用(垂直)滚动位置来代替?

【问题讨论】:

  • 我刚刚意识到:JTextArea 处理自己的滚动!这可能破坏了我们控制嵌入 JScrollPane 的努力。
  • 好的,我搞定了。查看我的更新 2。

标签: java user-interface swing


【解决方案1】:

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

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

【讨论】:

  • 这个解决方案比公认的要简单得多。
  • 这是更好的答案,它很简单,效果很好
【解决方案2】:

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

  • 在您的JScrollPane 上使用getViewport() 来控制视口。
  • 使用Viewport.getViewPosition() 获取左上角坐标。这些是绝对的,而不是滚动文本的百分比。
  • 使用Viewport.addChangeListener() 在左上角位置发生变化时得到通知(除其他外)。当然,您可能希望创建一种机制来区分用户更改和程序所做的更改。
  • 使用Viewport.setViewPosition() 将左上角设置为干扰前的位置。

更新:

  • 要阻止 JTextArea 滚动,您可能需要覆盖其 getScrollableTracksViewport{Height|Width}() 方法以返回 false

更新 2:

以下代码可以满足您的需求。令人惊讶的是,我费了多少力气才能让它工作:

  • 显然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);
      }
   } 

}

【讨论】:

  • 我试过了:Point orig = scrollPane.getViewport().getViewPosition(); textArea.setText(文本); scrollPane.getViewport().setViewPosition(orig);它似乎对我没有任何作用。使用该代码,文本区域每次都会一直滚动到底部。 (并在 swing 事件调度线程中运行它)。
  • [叹气] 我知道我应该测试一下。我现在就去做,然后回复你。
  • 谢谢。我玩了一下这个,似乎实际上不需要覆盖。我认为 getViewPosition() 返回的 Point 对象是共享的,而不是副本,所以我们需要克隆它,以获得相同的效果: final Point orig = (Point) scrollPane.getViewport().getViewPosition().clone( ); textArea.setText(文本); SwingUtilities.invokeLater() { new Runnable() { scrollPane.getViewport().setViewPosition(orig); } }); .. 也就是说,这个解决方案在 99% 的情况下都有效,但有时,在进行快速更新时,它只会一直向下滚动。
  • 有效的 99% 时间评论既适用于使用克隆的代码,也适用于您答案中的代码。无论如何,对于我的实际用途来说已经足够了,所以我稍后会接受答案。无论如何,仍然对如何使其 100% 的工作充满好奇。
  • 我也有类似的问题。出于某种原因,仅当从调度线程调用setText 时,文本区域才会滚动到底部。请注意,setText 是线程安全的。当不使用调度线程时,一切正常。我真的很想知道为什么。
猜你喜欢
  • 2011-03-22
  • 1970-01-01
  • 2011-07-15
  • 1970-01-01
  • 1970-01-01
  • 2011-11-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多