代码之家  ›  专栏  ›  技术社区  ›  I82Much

swing:滚动到jscrollpane的底部,条件是当前的视区位置

  •  10
  • I82Much  · 技术社区  · 16 年前

    我试图模仿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());
    }
    

    现在,这是可行的,但它的Janky和不理想的两个原因。

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

    在你问之前,是的,我读过这篇博文 Text Area Scrolling ,但默认的滚动到底部行为不是我想要的。

    其他相关问题(但在我看来,在这方面没有完全帮助): Setting scroll bar on a jscrollpane Making a JScrollPane automatically scroll all the way down.

    在这方面的任何帮助都将非常感谢。

    编辑:

    根据Devon_u C_u 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);
               }
           }
        });
    }
    

    我还有问题。

    7 回复  |  直到 10 年前
        1
  •  6
  •   Devon_C_Miller    16 年前

    看看 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
  •  3
  •   kleopatra Aji kattacherry    14 年前

    基于前面的答案和进一步的google搜索,我做了一个测试,在这个测试中,一个线程连续地将字符串附加到jscrollpane中的jtextArea。我认为在这里使用invokelater很重要,因为在滚动到该值之前,jscrollbar需要用新的最大值进行更新。使用invokelater,awt线程将处理这个问题。

    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;
        }
    }
    
        3
  •  1
  •   camickr    16 年前

    看看这个 example .

    不过,我将更改代码以使用以下更有效的代码:

    caret.setDot(textArea.getDocument().getLength());
    
        4
  •  1
  •   kjkrum    14 年前

    过去几天我一直在尝试不同的方法来解决这个问题。我找到的最佳解决方案是替换jscrollpane的viewport的布局管理器,并在那里实现所有所需的滚动行为。跳跃的滚动条旋钮不见了,它燃烧的很快——如此之快,以至于它创造了另一个我不得不努力的比赛条件。详情如下: http://www.java-forums.org/awt-swing/56808-scroll-bar-knob-jumpy-when-component-growing.html

        5
  •  1
  •   desperateCoder    10 年前

    有点晚了,但我发现: http://www.camick.com/java/source/SmartScroller.java

    实际上,您可以使用以下代码:

    JScrollPane scrollPane = new JScrollPane(myOversizedComponent);
    // Injects smartscroll behaviour to your scrollpane
    new SmartScroller(scrollPane); 
    

    这里是smartscroller类:

    import java.awt.Component;
    import java.awt.event.*;
    import javax.swing.*;
    import javax.swing.text.*;
    
    /**
     *  The SmartScroller will attempt to keep the viewport positioned based on
     *  the users interaction with the scrollbar. The normal behaviour is to keep
     *  the viewport positioned to see new data as it is dynamically added.
     *
     *  Assuming vertical scrolling and data is added to the bottom:
     *
     *  - when the viewport is at the bottom and new data is added,
     *    then automatically scroll the viewport to the bottom
     *  - when the viewport is not at the bottom and new data is added,
     *    then do nothing with the viewport
     *
     *  Assuming vertical scrolling and data is added to the top:
     *
     *  - when the viewport is at the top and new data is added,
     *    then do nothing with the viewport
     *  - when the viewport is not at the top and new data is added, then adjust
     *    the viewport to the relative position it was at before the data was added
     *
     *  Similiar logic would apply for horizontal scrolling.
     */
    public class SmartScroller implements AdjustmentListener
    {
        public final static int HORIZONTAL = 0;
        public final static int VERTICAL = 1;
    
        public final static int START = 0;
        public final static int END = 1;
    
        private int viewportPosition;
    
        private JScrollBar scrollBar;
        private boolean adjustScrollBar = true;
    
        private int previousValue = -1;
        private int previousMaximum = -1;
    
        /**
         *  Convenience constructor.
         *  Scroll direction is VERTICAL and viewport position is at the END.
         *
         *  @param scrollPane the scroll pane to monitor
         */
        public SmartScroller(JScrollPane scrollPane)
        {
            this(scrollPane, VERTICAL, END);
        }
    
        /**
         *  Convenience constructor.
         *  Scroll direction is VERTICAL.
         *
         *  @param scrollPane the scroll pane to monitor
         *  @param viewportPosition valid values are START and END
         */
        public SmartScroller(JScrollPane scrollPane, int viewportPosition)
        {
            this(scrollPane, VERTICAL, viewportPosition);
        }
    
        /**
         *  Specify how the SmartScroller will function.
         *
         *  @param scrollPane the scroll pane to monitor
         *  @param scrollDirection indicates which JScrollBar to monitor.
         *                         Valid values are HORIZONTAL and VERTICAL.
         *  @param viewportPosition indicates where the viewport will normally be
         *                          positioned as data is added.
         *                          Valid values are START and END
         */
        public SmartScroller(JScrollPane scrollPane, int scrollDirection, int viewportPosition)
        {
            if (scrollDirection != HORIZONTAL
            &&  scrollDirection != VERTICAL)
                throw new IllegalArgumentException("invalid scroll direction specified");
    
            if (viewportPosition != START
            &&  viewportPosition != END)
                throw new IllegalArgumentException("invalid viewport position specified");
    
            this.viewportPosition = viewportPosition;
    
            if (scrollDirection == HORIZONTAL)
                scrollBar = scrollPane.getHorizontalScrollBar();
            else
                scrollBar = scrollPane.getVerticalScrollBar();
    
            scrollBar.addAdjustmentListener( this );
    
            //  Turn off automatic scrolling for text components
    
            Component view = scrollPane.getViewport().getView();
    
            if (view instanceof JTextComponent)
            {
                JTextComponent textComponent = (JTextComponent)view;
                DefaultCaret caret = (DefaultCaret)textComponent.getCaret();
                caret.setUpdatePolicy(DefaultCaret.NEVER_UPDATE);
            }
        }
    
        @Override
        public void adjustmentValueChanged(final AdjustmentEvent e)
        {
            SwingUtilities.invokeLater(new Runnable()
            {
                public void run()
                {
                    checkScrollBar(e);
                }
            });
        }
    
        /*
         *  Analyze every adjustment event to determine when the viewport
         *  needs to be repositioned.
         */
        private void checkScrollBar(AdjustmentEvent e)
        {
            //  The scroll bar listModel contains information needed to determine
            //  whether the viewport should be repositioned or not.
    
            JScrollBar scrollBar = (JScrollBar)e.getSource();
            BoundedRangeModel listModel = scrollBar.getModel();
            int value = listModel.getValue();
            int extent = listModel.getExtent();
            int maximum = listModel.getMaximum();
    
            boolean valueChanged = previousValue != value;
            boolean maximumChanged = previousMaximum != maximum;
    
            //  Check if the user has manually repositioned the scrollbar
    
            if (valueChanged && !maximumChanged)
            {
                if (viewportPosition == START)
                    adjustScrollBar = value != 0;
                else
                    adjustScrollBar = value + extent >= maximum;
            }
    
            //  Reset the "value" so we can reposition the viewport and
            //  distinguish between a user scroll and a program scroll.
            //  (ie. valueChanged will be false on a program scroll)
    
            if (adjustScrollBar && viewportPosition == END)
            {
                //  Scroll the viewport to the end.
                scrollBar.removeAdjustmentListener( this );
                value = maximum - extent;
                scrollBar.setValue( value );
                scrollBar.addAdjustmentListener( this );
            }
    
            if (adjustScrollBar && viewportPosition == START)
            {
                //  Keep the viewport at the same relative viewportPosition
                scrollBar.removeAdjustmentListener( this );
                value = value + maximum - previousMaximum;
                scrollBar.setValue( value );
                scrollBar.addAdjustmentListener( this );
            }
    
            previousValue = value;
            previousMaximum = maximum;
        }
    }
    
        6
  •  0
  •   Community Mohan Dere    9 年前
    public static void scrollToBottom(JComponent component) 
    {
        Rectangle visibleRect = component.getVisibleRect();
        visibleRect.y = component.getHeight() - visibleRect.height;
        component.scrollRectToVisible(visibleRect);
    }
    

    你可以在这里找到全部细节: How to make JTextPane autoscroll only when scroll bar is at bottom and scroll lock is off?

        7
  •  0
  •   animuson    13 年前
    DefaultCaret caret = (DefaultCaret)textArea.getCaret();  
    caret.setUpdatePolicy(DefaultCaret.ALWAYS_UPDATE);