【问题标题】:How to make JTextPane autoscroll only when scroll bar is at bottom and scroll lock is off?仅当滚动条位于底部且滚动锁定关闭时,如何使 JTextPane 自动滚动?
【发布时间】:2010-10-28 17:51:22
【问题描述】:

如何让 JTextPane 仅在滚动条位于底部且滚动锁定关闭时自动滚动?这不应该与插入符号有任何关系,这似乎是我在谷歌上发现的。 :(

【问题讨论】:

    标签: java swing


    【解决方案1】:

    我认为我下面的程序完全符合您的要求,但有一个可能的警告:您不能在文本区域中输入内容。所以这对于日志查看器来说会很好,但对于交互式控制台来说却不是。代码运行时间有点长,因为我已经将它变成了该方法的可立即运行的演示。我建议按原样运行程序并检查行为。如果该行为适合您,那么请花一点时间研究代码。我在代码中包含了 cmets 以突出显示一些更重要的部分。


    2013-07-17 更新:您可能还想查看 random dude 在页面下方的单独答案中的解决方案。他的方法比我的更优雅。

    另请参阅Swing: Scroll to bottom of JScrollPane, conditional on current viewport location,了解不干扰插入符号位置的潜在解决方案。


    SCCE 源代码如下:

    import java.awt.*;
    import java.awt.event.*;
    import java.util.*;
    import java.util.Timer;
    
    import javax.swing.*;
    import javax.swing.event.*;
    import javax.swing.text.*;
    
    public class ScrollingJTextAreaExample extends JFrame {
        // Worker thread to help periodically append example messages to JTextArea
        Timer timer = new Timer();
        // Merely informative counter, will be displayed with the example messages
        int messageCounter = 0;
        // GUI components
        JScrollPane jScrollPane;
        JTextArea jTextArea;
    
        public ScrollingJTextAreaExample() {
            initComponents(); // Boiler plate GUI construction and layout
    
            // Configure JTextArea to not update the cursor position after
            // inserting or appending text to the JTextArea. This disables the
            // JTextArea's usual behavior of scrolling automatically whenever
            // inserting or appending text into the JTextArea: we want scrolling
            // to only occur at our discretion, not blindly. NOTE that this
            // breaks normal typing into the JTextArea. This approach assumes
            // that all updates to the ScrollingJTextArea are programmatic.
            DefaultCaret caret = (DefaultCaret) jTextArea.getCaret();
            caret.setUpdatePolicy(DefaultCaret.NEVER_UPDATE);
    
            // Schedule a task to periodically append example messages to jTextArea
            timer.schedule(new TextGeneratorTask(), 250, 250);
    
            // This DocumentListener takes care of re-scrolling when appropriate
            Document document = jTextArea.getDocument();
            document.addDocumentListener(new ScrollingDocumentListener());
        }
    
        // Boring, vanilla GUI construction and layout code
        private void initComponents() {
            jScrollPane = new javax.swing.JScrollPane();
            jTextArea = new javax.swing.JTextArea();
            setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
            jScrollPane.setViewportView(jTextArea);
            getContentPane().add(jScrollPane, java.awt.BorderLayout.CENTER);
            setSize(320, 240);
            setLocationRelativeTo(null);
        }
    
        // ScrollingDocumentListener takes care of re-scrolling when appropriate
        class ScrollingDocumentListener implements DocumentListener {
            public void changedUpdate(DocumentEvent e) {
                maybeScrollToBottom();
            }
    
            public void insertUpdate(DocumentEvent e) {
                maybeScrollToBottom();
            }
    
            public void removeUpdate(DocumentEvent e) {
                maybeScrollToBottom();
            }
    
            private void maybeScrollToBottom() {
                JScrollBar scrollBar = jScrollPane.getVerticalScrollBar();
                boolean scrollBarAtBottom = isScrollBarFullyExtended(scrollBar);
                boolean scrollLock = Toolkit.getDefaultToolkit()
                        .getLockingKeyState(KeyEvent.VK_SCROLL_LOCK);
                if (scrollBarAtBottom && !scrollLock) {
                    // Push the call to "scrollToBottom" back TWO PLACES on the
                    // AWT-EDT queue so that it runs *after* Swing has had an
                    // opportunity to "react" to the appending of new text:
                    // this ensures that we "scrollToBottom" only after a new
                    // bottom has been recalculated during the natural
                    // revalidation of the GUI that occurs after having
                    // appending new text to the JTextArea.
                    EventQueue.invokeLater(new Runnable() {
                        public void run() {
                            EventQueue.invokeLater(new Runnable() {
                                public void run() {
                                    scrollToBottom(jTextArea);
                                }
                            });
                        }
                    });
                }
            }
        }
    
        class TextGeneratorTask extends TimerTask {
            public void run() {
                EventQueue.invokeLater(new Runnable() {
                    public void run() {
                        String message = (++messageCounter)
                                + " Lorem ipsum dolor sit amet, consectetur"
                                + " adipisicing elit, sed do eiusmod tempor"
                                + " incididunt ut labore et dolore magna aliqua.\n";
                        jTextArea.append(message);
                    }
                });
            }
        }
    
        public static boolean isScrollBarFullyExtended(JScrollBar vScrollBar) {
            BoundedRangeModel model = vScrollBar.getModel();
            return (model.getExtent() + model.getValue()) == model.getMaximum();
        }
    
        public static void scrollToBottom(JComponent component) {
            Rectangle visibleRect = component.getVisibleRect();
            visibleRect.y = component.getHeight() - visibleRect.height;
            component.scrollRectToVisible(visibleRect);
        }
    
        public static void main(String args[]) {
            java.awt.EventQueue.invokeLater(new Runnable() {
                public void run() {
                    new ScrollingJTextAreaExample().setVisible(true);
                }
            });
        }
    }
    

    【讨论】:

    • 是的,我就是这样做的。
    • 非常非常酷!!谢谢!
    • 如果你追加消息到fast就不行(比如timer.schedule(new TextGeneratorTask(), 250, 5);),你知道为什么吗?
    • 它几乎可以工作,但如果您使用鼠标滚轮,滚动条会停留在底部。如果在那里写了一个也适用于鼠标滚轮的函数:stackoverflow.com/questions/4045722/…
    【解决方案2】:

    这个问题有点晚了,但我想出了这个解决方案。

      conversationPane = new JTextPane();
      final JScrollPane conversationScrollPane = new JScrollPane(conversationPane);
      conversationScrollPane.getVerticalScrollBar().addAdjustmentListener(new AdjustmentListener() {
    
         BoundedRangeModel brm = conversationScrollPane.getVerticalScrollBar().getModel();
         boolean wasAtBottom = true;
    
         public void adjustmentValueChanged(AdjustmentEvent e) {
            if (!brm.getValueIsAdjusting()) {
               if (wasAtBottom)
                  brm.setValue(brm.getMaximum());
            } else
               wasAtBottom = ((brm.getValue() + brm.getExtent()) == brm.getMaximum());
    
         }
      });   
    

    似乎非常适合我的需求。一点解释:本质上,如果滚动条没有被人移动并且滚动条最后处于最大值/底部,则将其重置为最大值。如果是手动调整,则检查是否调整到底部。

    【讨论】:

    • 出于某种原因,brm.setValue(brm.getMaximum()) 挂起我的方法和我的第二个线程(仅在 Windows 上。linux mac solaris 效果很好)。在文本窗格上使用 setcaret postiion 执行此操作,但在 Windows 上结果相同。有什么想法吗?
    • @Meltea - 所有与 Swing/AWT 对象的交互都需要在 AWT 事件调度线程上完成。如果你有一个“第二个线程”,它需要通过 SwingUtilities.invokeLater 将 Runnables 加入队列来与 GUI 交互(“调用方法”)。基本上,如果您在 Swing 或 AWT 对象(例如 brm.getMaximum())上调用方法,请在调用该方法之前在代码行中放置一条记录 SwingUtilities.isEventDispatchThread() 值的日志语句。如果打印“false”,您违反了 Swing/AWT 的线程模型,需要使用 SwingUtilities.invokeLater(或类似的)。
    • 你拯救了我的一天。
    【解决方案3】:

    Text Area Scrolling 可能感兴趣。

    我不知道滚动锁定键如何影响它。我从Scroll Lock 上的维基百科页面找到以下内容:

    因此,几乎所有现代程序和操作系统都可以将 Scroll Lock 视为已失效的功能。

    所以我不会担心。

    【讨论】:

      【解决方案4】:

      我需要为日志记录文本区域做同样的事情。我在网上找到的解决方案对我不起作用(它们要么在快速记录大量消息时停止自动滚动,要么即使您使用鼠标滚轮向上滚动也会阻止底部的滚动条)。

      我是这样做的:

      public static void makeTextAreaAutoScroll(JTextArea textArea) {
      
          // Get the text area's scroll pane :
          final JScrollPane scrollPane = (JScrollPane) (textArea.getParent().getParent());
      
          // Disable the auto scroll :
          ((DefaultCaret)textArea.getCaret()).setUpdatePolicy(DefaultCaret.NEVER_UPDATE);
      
          // Add a listener to the vertical scroll bar :
          scrollPane.getVerticalScrollBar().addAdjustmentListener(new AdjustmentListener() {
      
              private int _val = 0;
              private int _ext = 0;
              private int _max = 0;
      
              private final BoundedRangeModel _model = scrollPane.getVerticalScrollBar().getModel();
      
              @Override
              public void adjustmentValueChanged(AdjustmentEvent e) {
      
                  // Get the new max :
                  int newMax = _model.getMaximum();
      
                  // If the new max has changed and if we were scrolled to bottom :
                  if (newMax != _max && (_val + _ext == _max) ) {
      
                      // Scroll to bottom :
                      _model.setValue(_model.getMaximum() - _model.getExtent());
                  }
      
                  // Save the new values :
                  _val = _model.getValue();
                  _ext = _model.getExtent();
                  _max = _model.getMaximum();
              }
          });
      }
      

      就这样用吧:

      makeTextAreaAutoScroll(yourTextArea);
      

      你可以用这段代码进行测试:

      new Timer().schedule(new TimerTask() {
      
          @Override
          public void run() {
      
              javax.swing.SwingUtilities.invokeLater(new Runnable() {
      
                  @Override
                  public void run() {
      
                      String line = "test " + Math.random();
                      yourTextArea.append(yourTextArea.getText().isEmpty() ? line : "\n" + line);
                  }
              });
          }
      }, 0, 5);
      

      现在,如果滚动条位于底部,您的文本区域应该会自动滚动,如果您移动滚动条(通过拖动条或使用滚轮),则停止自动滚动,如果您将滚动条放在又见底了。

      【讨论】:

      • 这个解决方案对我有用,而 random dude 的没有 -- 具体来说,random dude 的解决方案不允许使用触控板或鼠标滚轮从底部向上滚动。
      【解决方案5】:

      试试这个:

      JTextArea txt = new JTextArea();
      JScrollPane jsp = new JScrollPane(history, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
      txt.setCaretPosition(txt.getDocument().getLength()); // do this afeter any event
      

      希望对你有帮助

      【讨论】:

        【解决方案6】:

        读完 Mike Clark 和 random dude 的解决方案后,我得到了下面的 sn-p 代码。

        private boolean doAutoScroll = true;
        private JTextPane textPane;
        private JScrollPane scrollPane;
        
        public void setup() {
            /* Left Panel */
            textPane = new JTextPane();
            textPane.setPreferredSize(new Dimension(600, 400)); // width, height
        
            /*
             * Not update the cursor position after inserting or appending text to the JTextPane.
             * [NOTE]
             * This breaks normal typing into the JTextPane.
             * This approach assumes that all updates to the JTextPane are programmatic.
             */
            DefaultCaret caret = (DefaultCaret) textPane.getCaret();
            caret.setUpdatePolicy(DefaultCaret.NEVER_UPDATE);
        
            scrollPane = new JScrollPane(textPane);
            scrollPane.getVerticalScrollBar().addAdjustmentListener(new AdjustmentListener() {
                BoundedRangeModel brm = scrollPane.getVerticalScrollBar().getModel();
                @Override
                public void adjustmentValueChanged(AdjustmentEvent e) {
                    // Invoked when user select and move the cursor of scroll by mouse explicitly.
                    if (!brm.getValueIsAdjusting()) {
                        if (doAutoScroll) brm.setValue(brm. getMaximum());
                    } else {
                        // doAutoScroll will be set to true when user reaches at the bottom of document.
                        doAutoScroll = ((brm.getValue() + brm.getExtent()) == brm.getMaximum());
                    }
                }
            });
        
            scrollPane.addMouseWheelListener(new MouseWheelListener() {
                BoundedRangeModel brm = scrollPane.getVerticalScrollBar().getModel();
                @Override
                public void mouseWheelMoved(MouseWheelEvent e) {
                    // Invoked when user use mouse wheel to scroll
                    if (e.getWheelRotation() < 0) {
                        // If user trying to scroll up, doAutoScroll should be false.
                        doAutoScroll = false;
                    } else {
                        // doAutoScroll will be set to true when user reaches at the bottom of document.
                        doAutoScroll = ((brm.getValue() + brm.getExtent()) == brm.getMaximum());
                    }
                }
            });
        }
        

        不同的是,即使用户使用鼠标滚轮上下滚动,它也额外使用MouseWheelListener更新doAutoScroll标志。

        【讨论】:

          猜你喜欢
          • 2014-11-03
          • 2013-01-11
          • 2014-12-12
          • 2023-03-06
          • 2021-05-29
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多