维护JTextArea滚动位置

时间:2010-01-11 01:07:28

标签: java user-interface swing

我有一个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);

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

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

2 个答案:

答案 0 :(得分:13)

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

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

答案 1 :(得分:6)

这是从API文档拼凑而未经测试的:

  • getViewport()上使用JScrollPane来抓住视口。
  • 使用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);
      }
   } 

}