【问题标题】:JScrollPane and autoscroll: Disturbing scrolling behaviorJScrollPane 和自动滚动:令人不安的滚动行为
【发布时间】:2015-03-16 21:35:29
【问题描述】:

我正在编写一个使用 JScrollPane 的应用程序。在这个 JScrollPane 中,我想自动显示搜索结果,这意味着我必须在 JScrollPane 中动态添加和删除结果。结果实现为 JTextArea,嵌入在 GridBagLayout 中。

当有大量搜索结果时,JScrollPane 会自动滚动到底部(它应该在顶部)。我已经用我在这里找到的解决方案解决了这个问题。这里的问题是,你可以看到,它是如何滚动回顶部的。是否可以消除这种行为?

我发现了以下几点:

  • 我必须删除以前的搜索结果才能显示新的搜索结果。如果我不删除以前的,它会正确显示。
  • 它既不能解决 wgeb 的问题,我在添加 arow 后每次调整时更新 JScrollPane,也不能仅在添加所有行后更新。

最好只禁用自动滚动。我创建了一个可执行示例来演示此行为。单击“添加行”按钮时,将添加 500 行。点几下就很清楚了。

非常感谢您的帮助!

import java.awt.GridBagConstraints;
import javax.swing.JTextArea;

public class ScrollPaneTest extends javax.swing.JFrame {
    private javax.swing.JButton jButton1;
    private javax.swing.JPanel jPanel1;
    private javax.swing.JPanel jPanel2;
    private javax.swing.JScrollPane jScrollPane1;


    /**
     * Creates new form ScrollPaneTest
     */
    public ScrollPaneTest() {
        initComponents();
    }

    /**
     * Adds a new row.
     * @param index The index of the new row.
     */
    private void addRow(int index) {
        JTextArea row = new JTextArea("Area " + index);
        row.setEditable(false);
        row.setBorder(null);

        GridBagConstraints gridBagConstraints = new GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = index;
        gridBagConstraints.fill = GridBagConstraints.BOTH;
        gridBagConstraints.weightx = 1.0;
        gridBagConstraints.insets = new java.awt.Insets(2, 0, 2, 0);
        jPanel2.add(row, gridBagConstraints);
    }


    /**
     * Initializes the components.
     */
    private void initComponents() {

        jScrollPane1 = new javax.swing.JScrollPane();
        jPanel1 = new javax.swing.JPanel();
        jPanel2 = new javax.swing.JPanel();
        jButton1 = new javax.swing.JButton();

        setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);

        jScrollPane1.setHorizontalScrollBarPolicy(javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
        jScrollPane1.setVerticalScrollBarPolicy(javax.swing.ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
        jScrollPane1.setPreferredSize(new java.awt.Dimension(400, 400));

        jScrollPane1.setViewportView(jPanel1);

        jPanel1.setLayout(new java.awt.BorderLayout());

        jPanel2.setLayout(new java.awt.GridBagLayout());
        jPanel1.add(jPanel2, java.awt.BorderLayout.NORTH);

        jScrollPane1.setViewportView(jPanel1);

        jButton1.setText("Create Rows");
        jButton1.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jButton1ActionPerformed(evt);
            }
        });

        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
        getContentPane().setLayout(layout);
        layout.setHorizontalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
            .addGroup(layout.createSequentialGroup()
                .addGap(148, 148, 148)
                .addComponent(jButton1)
                .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
        );
        layout.setVerticalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, 245, javax.swing.GroupLayout.PREFERRED_SIZE)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
                .addComponent(jButton1)
                .addGap(0, 21, Short.MAX_VALUE))
        );

        pack();
    }                   

    /**
     * Creates 500 new rows.
     * @param evt
     */
    private void jButton1ActionPerformed(java.awt.event.ActionEvent evt) { 
        jPanel2.removeAll();

        for(int i = 0; i < 1000; i++) {
            addRow(i);
        }

         javax.swing.SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                jScrollPane1.getVerticalScrollBar().setValue(0);
            }

         });
         jPanel2.validate();
         jPanel2.repaint();
    }                                        

    public static void main(String args[]) {
        java.awt.EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() { 
                new ScrollPaneTest().setVisible(true);
            }
        });
    }             
}

更新 1

我删除了 lambda 表达式。希望它现在也可以用

更新 2

通过替换解决了令人不安的滚动行为问题

jPanel2.validate();
jPanel2.repaint();

jScrollPane1.validate();
jScrollPane1.repaint();

尽管如此,这个问题的两个答案在其他一些情况下都非常有帮助,应该引起注意。

【问题讨论】:

  • you can see, how it scrolls back to the top. - 我不能,因为我不使用 Java 8 并且您的代码无法编译。
  • 亲爱的camickr,我更新了代码。感谢您的关注和帮助!
  • 你不能将插入符号的位置设置为视图的开头吗?

标签: java swing jscrollpane autoscroll


【解决方案1】:

实现此目的的一种方法是为您的滚动窗格自定义JViewPort。此自定义视口覆盖 setViewPosition 并使用标志来防止滚动。

这是一个这样的代码示例,在更改文本区域的内容之前,我们“锁定”视口以防止滚动,稍后我们将其解锁:

import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JViewport;
import javax.swing.SwingUtilities;
import javax.swing.Timer;

public class TestNoScrolling {

    private int lineCount = 0;
    private LockableViewPort viewport;
    private JTextArea ta;

    private final class LockableViewPort extends JViewport {

        private boolean locked = false;

        @Override
        public void setViewPosition(Point p) {
            if (locked) {
                return;
            }
            super.setViewPosition(p);
        }

        public boolean isLocked() {
            return locked;
        }

        public void setLocked(boolean locked) {
            this.locked = locked;
        }
    }

    protected void initUI() {
        JFrame frame = new JFrame("test");
        ta = new JTextArea(5, 30);
        JScrollPane scrollpane = new JScrollPane();
        viewport = new LockableViewPort();
        viewport.setView(ta);
        scrollpane.setViewport(viewport);
        frame.add(scrollpane);
        frame.pack();
        frame.setVisible(true);
        Timer t = new Timer(1000, new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                viewport.setLocked(true);
                ta.append("Some new line " + lineCount++ + "\n");
                SwingUtilities.invokeLater(new Runnable() {
                    @Override
                    public void run() {
                        viewport.setLocked(false);
                    }
                });
            }
        });
        t.setRepeats(true);
        t.start();
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {

            @Override
            public void run() {
                new TestNoScrolling().initUI();
            }
        });
    }
}

【讨论】:

  • 亲爱的@Guillaume Polet,这个答案真的很有帮助!我将它构建到我的示例代码中并尝试了一下。如果我设置 viewport.setView(jPanel1) 它不起作用。但是,如果我设置 viewport.setView(jPanel2) 它确实有效!但是像这样,还有一个问题:比如只有两个searchResults(只有两次addRow(i))时,结果显示在字段的中间而不是顶部。 jPanel1 的目的是将其放置在 NORTH 位置。我现在尝试在 GridBagConstraint 和其他人中设置锚点,但它也无济于事。非常感谢!
  • @Rolch2015 尝试设置weighty = 1.0; anchor = NORTH(简化代码)。
  • 亲爱的@Guillaume Polet,我也尝试过 weighty = 1.0 和 anchor。但是像这样,如果即只有两行,那么它会垂直拉伸这些行,以便它们填满整个字段。在这种情况下,设置行的首选/最大等大小也不起作用。
  • @Rolch2015 您需要将填充属性设置为 NONE 或 HORIZONTAL:gridBagConstraints.fill = GridBagConstraints.NONE;gridBagConstraints.fill = GridBagConstraints.HORIZONTAL;
  • 亲爱的@Guillaume Polet,这也不行,因为像这样,第一排和第二排之间有很大的差距。
【解决方案2】:

您可以简单地将Caret 位置设置为起始位置 (0),例如...

import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

public class ScrollNoMore {

    public static void main(String[] args) {
        new ScrollNoMore ();
    }

    public ScrollNoMore () {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                    ex.printStackTrace();
                }

                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class TestPane extends JPanel {

        private JTextArea ta;
        private JScrollPane sp;
        private Random rnd = new Random();

        private boolean initalised = false;

        public TestPane() {
            setLayout(new BorderLayout());
            ta = new JTextArea(20, 40);
            sp = new JScrollPane(ta);
            add(sp);
            Timer timer = new Timer(250, new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    long value = rnd.nextLong();
                    ta.append(String.valueOf(value) + "\n");
                    if (!initalised) {
                        ta.setCaretPosition(0);
                        initalised = true;
                    }
                }
            });
            timer.start();
        }

    }

}

这只会在Timer 第一次运行时设置,这意味着如果您移动Caret 或出于某种原因滚动位置,它不会“弹回”。您可以设置一系列状态,如果用户移动当前视图,它不会影响滚动,但可以根据需要重置。

【讨论】:

  • 亲爱的@MadProgrammer,我也尝试过 setCaretPosition(0) 但它不起作用,因为我将 JTextAreas 动态添加到我的面板中,并且只有一个。
  • 亲爱的@MadProgrammer,我找到了解决方案。请看上面的cmets第一个答案。
  • 好的,但基本原理还是一样的。您知道要在哪里添加文本,因此您必须已经拥有对要更新的JTextArea 的引用。只是说
猜你喜欢
  • 2011-01-09
  • 1970-01-01
  • 1970-01-01
  • 2011-01-29
  • 2012-01-18
  • 2018-09-12
  • 1970-01-01
  • 1970-01-01
  • 2015-06-19
相关资源
最近更新 更多