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

Java 插入组件后,让JScrollPane滚动到底的正确方法是什么?,java,swing,jscrollpane,swingworker,Java,Swing,Jscrollpane,Swingworker,这个问题我已经看过好几次了,但我发现答案在我看来有点“糟糕” 所以,基本上我有一个JScrollPane,我可以在其中插入组件。每次插入组件时,我都希望JScrollPane滚动到底部。很简单 现在,合乎逻辑的做法是将侦听器(componentAdded)添加到我要插入的容器中 然后,该侦听器将简单地滚动到底部。但是,这将不起作用,因为此时组件高度尚未完成计算,因此滚动失败 我看到的答案通常包括将滚动行放在一个(甚至几个链接的)“invokeLater”线程中 在我看来,这就像一个“丑陋的黑客”

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

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

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

然后,该侦听器将简单地滚动到底部。但是,这将不起作用,因为此时组件高度尚未完成计算,因此滚动失败

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

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

我还读了一些关于你应该和SwingWorker一起工作的答案,我从来没有真正理解过。请告诉我:)

下面是一些代码供您修改(阅读“makework”):

更新: 添加了一些代码来测试理论。然而,它似乎工作得很好,所以我想我在程序的其他地方遇到了一个问题:)

导入java.awt.BorderLayout;
导入java.awt.Color;
导入java.awt.Dimension;
导入java.awt.event.ContainerAdapter;
导入java.awt.event.ContainerEvent;
导入javax.swing.BoxLayout;
导入javax.swing.JFrame;
导入javax.swing.JPanel;
导入javax.swing.JScrollBar;
导入javax.swing.JScrollPane;
导入javax.swing.SwingUtilities;
导入javax.swing.UIManager;
导入javax.swing.border.LineBorder;
公共类ScrollTest扩展了JFrame{
私有静态最终长serialVersionUID=-8538440132657016395L;
公共静态void main(字符串[]args){
SwingUtilities.invokeLater(新的Runnable(){
公开募捐{
新建ScrollTest().setVisible(true);
}
});
}
公共滚动测试(){
UIManager.put(“swing.boldMetal”,Boolean.FALSE);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setTitle(“滚动测试”);
设置大小(1000720);
setLocationRelativeTo(空);
JPanel容器=新的JPanel();
container.setLayout(新的BoxLayout(container,BoxLayout.X_轴));
添加(容器);
//创建3个ScollPanel
final JScrollPane scrollPane1=新JScrollPane();
滚动窗格1.设置顺序(新行边框(颜色为红色,2));
container.add(滚动窗格1);
final JScrollPane scrollPane2=新JScrollPane();
滚动窗格2.setboorder(新的线边框(Color.GREEN,2));
container.add(滚动窗格2);
final JScrollPane scrollPane3=新JScrollPane();
滚动窗格3.setboorder(新的线边框(Color.BLUE,2));
container.add(滚动窗格3);
//在每个scrollpanel内创建一个jpanel
JPanel wrapper1=新的JPanel();
wrapper1.setLayout(新的BoxLayout(wrapper1,BoxLayout.Y_轴));
scrollPane1.setViewportView(包装器1);
wrapper1.addContainerListener(新的ContainerAdapter(){
添加了公共无效组件(ContainerEvent e){
SwingUtilities.invokeLater(新的Runnable(){
公开募捐{
scrollPane1.getVerticalScrollBar().setValue(scrollPane1.getVerticalScrollBar().getMaximum());
}
});
}
});        
JPanel wrapper2=新的JPanel();
wrapper2.setLayout(新的BoxLayout(wrapper2,BoxLayout.Y_轴));
scrollPane2.setViewportView(包装器2);
wrapper2.addContainerListener(新的ContainerAdapter(){
添加了公共无效组件(ContainerEvent e){
SwingUtilities.invokeLater(新的Runnable(){
公开募捐{
scrollPane2.getVerticalScrollBar().setValue(scrollPane2.getVerticalScrollBar().getMaximum());
}
});
}
});        
JPanel wrapper3=新的JPanel();
wrapper3.setLayout(新的BoxLayout(wrapper3,BoxLayout.Y_轴));
滚动窗格3.setViewportView(包装器3);
wrapper3.addContainerListener(新的ContainerAdapter(){
添加了公共无效组件(ContainerEvent e){
SwingUtilities.invokeLater(新的Runnable(){
公开募捐{
scrollPane3.getVerticalScrollBar().setValue(scrollPane3.getVerticalScrollBar().getMaximum());
}
});
}
});        
//在每一个包装纸上都加些东西
JPanel垃圾;

对于(int x=1;x来说,在不浪费时间重新发明轮子的情况下,将组件滚动到任意位置的正确方法如下:

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

    }
});
您正在避免的气味:

  • 不引用任何封闭的父级(如滚动窗格)
  • 不深入任何父级的内部(如滚动条)
  • 不需要自定义模型
对于invokeLater,引用其api文档(我添加了粗体):

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


因此,您可以相当肯定的是,在执行代码之前已经处理了内部构件。

“我已经多次看到这个问题,但我发现的答案在我看来有点“不好”。“具体在哪里?链接到它们。要更快地获得更好的帮助,请发布一个(最简单的完整测试和可读示例)。很难相信您从未遇到过面板。scrollRectToVisible在您的搜索中;-)您的意思是scrollRectToVisible可以工作,即使我在GUI有时间更新高度之前设置了它?提示:添加@kleopatra(或者其他人,
@
很重要)通知某人有新的评论。@安德鲁·汤普森:啊,谢谢。我会记住这一点。我假设如果添加了其他评论(如Facebo),已经发表评论的人会自动得到通知
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);
    }



}

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

    }
});