在插入组件后,将JScrollPane滚动到底部的正确方法是什么?

时间:2014-03-20 10:17:40

标签: java swing jscrollpane swingworker

我已经看过几次这个问题了,但我发现的答案在我看来有点“糟糕”。

所以,基本上我有一个我插入组件的JScrollPane。每次插入组件时,我都希望JScrollPane滚动到底部。很简单。

现在,合乎逻辑的做法是将一个监听器(componentAdded)添加到我要插入的容器中。

然后,那个听众就会滚动到底部。但是,这不起作用,因为此时组件高度尚未完成计算,因此滚动失败。

我看到的答案通常涉及将滚动行放在一个(甚至几个链接的)“invokeLater”线程中。

在我看来,这似乎是一个“丑陋的黑客”。当所有高度计算完成后,确实应该有更好的方法来实际移动滚动,而不是仅仅将“滚动”延迟一段未知的时间?

我还阅读了一些你应该使用SwingWorker的答案,我从来没有真正理解过。请赐教:))

以下是一些代码供您修改(阅读“make work”):

JScrollPane scrollPane = new JScrollPane();
add(scrollPane);

JPanel container = new JPanel();
scrollPane.setViewportView(container);

container.addContainerListener(new ContainerAdapter() {
    public void componentAdded(ContainerEvent e) {
        JScrollPane scrollPane = (JScrollPane) value.getParent().getParent();
        JScrollBar scrollBar = scrollPane.getVerticalScrollBar();
        scrollBar.setValue(scrollBar.getMaximum());
    }
});

JPanel hugePanel = new JPanel();
hugePanel.setPreferredSize(new Dimension(10000, 10000);
container.add(hugePanel);

更新: 添加了一些代码来测试理论。但是,它似乎工作正常,所以我想我的程序中的其他地方有问题:)

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.event.ContainerAdapter;
import java.awt.event.ContainerEvent;

import javax.swing.BoxLayout;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollBar;
import javax.swing.JScrollPane;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.border.LineBorder;


public class ScrollTest extends JFrame {

private static final long serialVersionUID = -8538440132657016395L;

public static void main(String[] args) {

    SwingUtilities.invokeLater(new Runnable() {
        public void run() {
            new ScrollTest().setVisible(true);
        }
    });

}

public ScrollTest() {

    UIManager.put("swing.boldMetal", Boolean.FALSE);

    setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    setTitle("Scroll Test");
    setSize(1000, 720);
    setLocationRelativeTo(null);

    JPanel container = new JPanel();
    container.setLayout(new BoxLayout(container, BoxLayout.X_AXIS));
    add(container);

    //Create 3 scollpanels
    final JScrollPane scrollPane1 = new JScrollPane();
    scrollPane1.setBorder(new LineBorder(Color.RED, 2));
    container.add(scrollPane1);

    final JScrollPane scrollPane2 = new JScrollPane();
    scrollPane2.setBorder(new LineBorder(Color.GREEN, 2));
    container.add(scrollPane2);

    final JScrollPane scrollPane3 = new JScrollPane();
    scrollPane3.setBorder(new LineBorder(Color.BLUE, 2));
    container.add(scrollPane3);

    //Create a jpanel inside each scrollpanel
    JPanel wrapper1 = new JPanel();
    wrapper1.setLayout(new BoxLayout(wrapper1, BoxLayout.Y_AXIS));
    scrollPane1.setViewportView(wrapper1);
    wrapper1.addContainerListener(new ContainerAdapter() {
        public void componentAdded(ContainerEvent e) {

            SwingUtilities.invokeLater(new Runnable() {
                public void run() {
                    scrollPane1.getVerticalScrollBar().setValue(scrollPane1.getVerticalScrollBar().getMaximum());
                }
            });

        }
    });        

    JPanel wrapper2 = new JPanel();
    wrapper2.setLayout(new BoxLayout(wrapper2, BoxLayout.Y_AXIS));
    scrollPane2.setViewportView(wrapper2);
    wrapper2.addContainerListener(new ContainerAdapter() {
        public void componentAdded(ContainerEvent e) {

            SwingUtilities.invokeLater(new Runnable() {
                public void run() {
                    scrollPane2.getVerticalScrollBar().setValue(scrollPane2.getVerticalScrollBar().getMaximum());
                }
            });

        }
    });        

    JPanel wrapper3 = new JPanel();
    wrapper3.setLayout(new BoxLayout(wrapper3, BoxLayout.Y_AXIS));
    scrollPane3.setViewportView(wrapper3);
    wrapper3.addContainerListener(new ContainerAdapter() {
        public void componentAdded(ContainerEvent e) {

            SwingUtilities.invokeLater(new Runnable() {
                public void run() {
                    scrollPane3.getVerticalScrollBar().setValue(scrollPane3.getVerticalScrollBar().getMaximum());
                }
            });

        }
    });        

    //Add come stuff into each wrapper
    JPanel junk;
    for(int x = 1; x <= 1000; x++) {
        junk = new JPanel();
        junk.setBorder(new LineBorder(Color.BLACK, 2));
        junk.setPreferredSize(new Dimension(100, 40));
        junk.setMaximumSize(junk.getPreferredSize());
        wrapper1.add(junk);
    }

    for(int x = 1; x <= 1000; x++) {
        junk = new JPanel();
        junk.setBorder(new LineBorder(Color.BLACK, 2));
        junk.setPreferredSize(new Dimension(100, 40));
        junk.setMaximumSize(junk.getPreferredSize());
        wrapper2.add(junk);
    }

    for(int x = 1; x <= 1000; x++) {
        junk = new JPanel();
        junk.setBorder(new LineBorder(Color.BLACK, 2));
        junk.setPreferredSize(new Dimension(100, 40));
        junk.setMaximumSize(junk.getPreferredSize());
        wrapper3.add(junk);
    }



}

}

1 个答案:

答案 0 :(得分:5)

正确 - 没有浪费时间重新发明轮子 - 在任何你想要的地方滚动组件的方式如下:

wrapper1.addContainerListener(new ContainerAdapter() {
    @Override
    public void componentAdded(final ContainerEvent e) {

        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                JComponent comp = (JComponent) e.getChild();
                Rectangle bounds = new Rectangle(comp.getBounds());
                comp.scrollRectToVisible(bounds);
            }
        });

    }
});

你要避免的气味:

  • 不参考任何封闭的父级(如scrollPane)
  • 没有深入到任何父级的内部(如scrollBar)
  • 无需自定义模型

至于invokeLater,引用它的api doc(由我添加的粗体):

  

导致doRun.run()在AWT事件上异步执行   调度线程。在所有挂起的AWT事件发生后,这将发生   已处理

因此,您可以相当确定在代码执行之前处理内部。