Warning: file_get_contents(/data/phpspider/zhask/data//catemap/7/user-interface/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Java Swing:滚动到JScrollPane的底部,以当前视口位置为条件_Java_User Interface_Swing_Jscrollpane - Fatal编程技术网

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

Java Swing:滚动到JScrollPane的底部,以当前视口位置为条件,java,user-interface,swing,jscrollpane,Java,User Interface,Swing,Jscrollpane,我试图模仿Adium和我见过的大多数其他聊天客户端的功能,当新消息进来时,滚动条会移到底部,但只有当你已经在那里的时候。换句话说,如果你已经滚动了几行并且正在阅读,当一条新消息进来时,它不会将你的位置跳到屏幕底部;那会很烦人。但是如果滚动到底部,程序会正确地假设您希望随时查看最新的消息,因此自动滚动 我花了一段时间试图模仿这一点;该平台似乎不惜一切代价打击这种行为。我能做的最好的事情如下: 在构造函数中: JTextArea chatArea = new JTextArea(); JScroll

我试图模仿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());
}
现在,这是可行的,但它很简陋,不理想,原因有二

  • 通过设置插入符号位置,用户在聊天区中的任何选择都会被删除。我可以想象,如果他试图复制/粘贴,这会非常恼人
  • 由于滚动窗格的前进发生在插入文本之后,因此在一瞬间滚动条处于错误的位置,然后它在视觉上向末尾跳跃。这并不理想 在你问之前,是的,我已经在上读了这篇博文,但是默认的滚动到底的行为不是我想要的

    其他相关(但在我看来,在这方面并非完全有用)问题:

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

    编辑:

    根据Devon_C#u Miller的建议,我有一种改进的滚动到底部的方法,解决问题1

    我仍然有问题#2。

    请查看和

    这应该可以在不干扰用户选择的情况下满足您的需求

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

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

    尽管如此,我还是会更改代码以使用以下更高效的代码:

    caret.setDot(textArea.getDocument().getLength());
    

    基于前面的回答和进一步的谷歌搜索,我做了一个测试,其中一个线程在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;
        }
    }
    

    过去几天我一直在尝试不同的方法来解决这个问题。我找到的最佳解决方案是替换JScrollPane视口的布局管理器,并在那里实现所有所需的滚动行为。跳跃的滚动条旋钮不见了,它燃烧得很快——如此之快,以至于它创造了另一个我不得不解决的比赛条件。详情如下:


    您可以在这里找到完整的详细信息:

    有点晚了,但我发现了一些东西:

    本质上,您可以使用以下代码:

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

    该示例再次放弃选择。此外,滚动条仍在跳跃。我没有看到“滚动条跳跃”。如果找到所选内容,则始终可以更改代码以不重置插入符号位置。然后视口将不会自动滚动,但我认为这是您想要的行为。如果用户当前正在选择文本,滚动不应该是自动的。我不明白你对不同事件的意思。。。我将滚动作为一个单独的事件。拆分滚动和追加的目的是让GUI有机会在滚动和添加文本之间重新绘制。这允许滚动条在文本出现之前移动到底部。我正在拆分滚动条和追加,但显然追加必须在滚动之前发生,否则追加文本时我们将不在视图底部。请查看我上面发布的代码中的注释。添加一个换行符并在一个事件中滚动,然后在invokeLater中添加没有前面换行符的文本。您可以在此处查看我的答案:
    DefaultCaret caret = (DefaultCaret)textArea.getCaret();  
    caret.setUpdatePolicy(DefaultCaret.ALWAYS_UPDATE); 
    
    JScrollPane scrollPane = new JScrollPane(myOversizedComponent);
    // Injects smartscroll behaviour to your scrollpane
    new SmartScroller(scrollPane); 
    
    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;
        }
    }