摆动:滚动到JScrollPane的底部,以当前视口位置为条件

2022-09-04 21:02:10

我试图模仿Adium和我见过的大多数其他聊天客户端的功能,其中滚动条在新消息传入时会前进到底部,但前提是您已经在那里。换句话说,如果您向上滚动了几行并正在阅读,则当新消息出现时,它不会将您的位置跳到屏幕底部;那会很烦人。但是,如果您滚动到底部,程序会正确地假设您希望始终查看最新的消息,因此会相应地自动滚动。

我有一段时间试图模仿这一点;该平台似乎不惜一切代价与这种行为作斗争。我能做的最好的事情如下:

在构造函数中:

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

现在,这有效,但由于两个原因,它很不稳定且不理想。

  1. 通过设置插入记号位置,用户在聊天区域中可能具有的任何选择都将被删除。我可以想象,如果他试图复制/粘贴,这将是非常烦人的。
  2. 由于滚动窗格的前进发生在插入文本之后,因此滚动条在错误位置的瞬间,然后它会在视觉上跳到末尾。这并不理想。

在你问之前,是的,我已经阅读了这篇关于文本区域滚动的博客文章,但默认的滚动到底部行为不是我想要的。

其他相关的(但在我看来,在这方面并不完全有帮助)问题:在jscrollpane上设置滚动条制作JScrollPane会自动向下滚动。

在这方面的任何帮助将不胜感激。

编辑:

根据Devon_C_Miller的建议,我有一种改进的滚动到底部的方式,解决了问题#1。

private void scrollToBottom() {
    javax.swing.SwingUtilities.invokeLater(new Runnable() {
       public void run() {
           try {
               int endPosition = chatArea.getDocument().getLength();
               Rectangle bottom = chatArea.modelToView(endPosition);
               chatArea.scrollRectToVisible(bottom);
           }
           catch (BadLocationException e) {
               System.err.println("Could not scroll to " + e);
           }
       }
    });
}

我仍然有问题#2。


答案 1

看看 scrollRectToVisible(Rectangle r)modelToView(int pos)

这应该可以在不干扰用户选择的情况下为您提供所需的内容。

至于滚动条,请尝试强制滚动和追加发生在不同的事件上。例如:

if (atBottom) {
    // append new line & do scroll
    SwingUtilities.invokerLater(new Runnable(){
        public void run() {
            // append text
        }});
} else {
    // append newline
    // append text
}

答案 2

基于之前的答案和进一步的Google'ing,我做了一个测试,其中Thread将Strings连续附加到JScrollPane中的JTextArea。我认为在这里使用 invokeLater 很重要,因为在我们滚动到该值之前,JScrollBar 需要更新其新的最大值。使用 invokeLater,AWT Thread 将负责处理这个问题。

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

推荐