JScrollPane添加组件后如何动态滚动到末尾?

How to make JScrollPane dynamically scroll to the end after adding components?

这是我的 ScrollPane 代码

    public class CustomScrollPane extends JScrollPane {

    private static CustomScrollPane instance = null;

    public CustomScrollPane () {
        super(panel.getInstance()); // a panel that the scrollpane wraps around
        this.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
        // hide the vertical scroll bar
        this.getVerticalScrollBar().setPreferredSize(new Dimension(0, 0));
    }

    public static CustomScrollPane getInstance() {
        if (instance == null)
            instance = new CustomScrollPane ();
        return instance;
    }

我尝试执行 getVerticalScrollbar.setValue(getVerticalScrollbar().getMaximum()) 但是当我向其中添加 JLabel 时它没有滚动到末尾。我试图让它在新的 JLabel 添加到面板后始终滚动到屏幕底部。我没有使用 JTextArea,因为我希望每一行都有不同的前景色,所以我使用了 JLabels。

我也试过添加这个方法

public void scrollToBottom() { getVerticalScrollbar().getMaximum(); }

但它只是冻结了 ScrollPane,我无法滚动。

如有任何帮助,我们将不胜感激!

我不确定您希望通过扩展 JScrollPane 实现什么,不确定它是否真的是它的核心职责,毕竟,例如,您可以让它的“视口视图”自己完成。 ..

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.GridLayout;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;

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

    public Main() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                JFrame frame = new JFrame();
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class TestPane extends JPanel {

        public TestPane() {
            setLayout(new BorderLayout());
            JPanel contentPane = new JPanel(new GridLayout(-1, 8));
            for (int index = 0; index < 1000; index++) {
                contentPane.add(new SqaurePane());
            }
            add(new JScrollPane(contentPane));

            JButton top = new JButton("Top");
            JButton bottom = new JButton("Bottom");

            JPanel actionPane = new JPanel();
            actionPane.add(top);
            actionPane.add(bottom);

            add(actionPane, BorderLayout.SOUTH);

            top.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    contentPane.scrollRectToVisible(new Rectangle(0, 0, 1, 1));
                }
            });
            bottom.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    contentPane.scrollRectToVisible(new Rectangle(0, contentPane.getHeight(), 0, 0));
                }
            });
        }

        @Override
        public Dimension getPreferredSize() {
            return new Dimension(400, 400);
        }
    }

    static List<Color> COLORS = new ArrayList<>(Arrays.asList(new Color[] {
        Color.BLACK,
        Color.BLUE,
        Color.CYAN,
        Color.DARK_GRAY,
        Color.GRAY,
        Color.GREEN,
        Color.LIGHT_GRAY,
        Color.MAGENTA,
        Color.ORANGE,
        Color.PINK,
        Color.RED,
        Color.WHITE,
        Color.YELLOW,
    }));

    public class SqaurePane extends JPanel {

        public SqaurePane() {
            Collections.shuffle(COLORS);
            setBackground(COLORS.get(0));
        }

        @Override
        public Dimension getPreferredSize() {
            return new Dimension(50, 50);
        }        
    }
}

魔法就在这里...

top.addActionListener(new ActionListener() {
    @Override
    public void actionPerformed(ActionEvent e) {
        contentPane.scrollRectToVisible(new Rectangle(0, 0, 1, 1));
    }
});
bottom.addActionListener(new ActionListener() {
    @Override
    public void actionPerformed(ActionEvent e) {
        contentPane.scrollRectToVisible(new Rectangle(0, contentPane.getHeight(), 0, 0));
    }
});

在这里,我要求 contentPane(包含所有方块的容器)根据我的需要“滚动到一个可见的矩形”

现在,在您的情况下,当您将新组件添加到“容器”时,您需要指示组件滚动到将显示新组件的位置。

这会有点复杂,因为您需要先触发布局通道,这样您才能获得容器的新尺寸

容器中的动态添加...

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.GridLayout;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.Timer;

public class Main {

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

    public Main() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                JFrame frame = new JFrame();
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class TestPane extends JPanel {

        public TestPane() {
            setLayout(new BorderLayout());
            JPanel contentPane = new JPanel(new GridLayout(-1, 8));
            add(new JScrollPane(contentPane));

            for (int index = 0; index < 8 * 8; index++) {
                contentPane.add(new SqaurePane());
            }

            Timer timer = new Timer(500, new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    SqaurePane sqaurePane = new SqaurePane();
                    contentPane.add(sqaurePane);
                    contentPane.revalidate();
                    // There is an issue with how the layout pass runs, this 
                    // "seems" to be getting pushed onto the EDT later, which
                    // is messing up the scroll logic.
                    // So, instead, we push this on to the EDT to be executed
                    // "later" after the layout pass has run.  Yes, I tried
                    // calling doLayout directly, but, for the first element
                    // of each row, it wouldn't work correctly
                    EventQueue.invokeLater(new Runnable() {
                        @Override
                        public void run() {
                            contentPane.scrollRectToVisible(new Rectangle(0, sqaurePane.getY(), 1, sqaurePane.getHeight()));
                        }
                    });
                }
            });
            timer.start();
        }

        @Override
        public Dimension getPreferredSize() {
            return new Dimension(400, 400);
        }
    }

    static List<Color> COLORS = new ArrayList<>(Arrays.asList(new Color[]{
        Color.BLACK,
        Color.BLUE,
        Color.CYAN,
        Color.DARK_GRAY,
        Color.GRAY,
        Color.GREEN,
        Color.LIGHT_GRAY,
        Color.MAGENTA,
        Color.ORANGE,
        Color.PINK,
        Color.RED,
        Color.WHITE,
        Color.YELLOW,}));

    public class SqaurePane extends JPanel {

        public SqaurePane() {
            Collections.shuffle(COLORS);
            setBackground(COLORS.get(0));
        }

        @Override
        public Dimension getPreferredSize() {
            return new Dimension(50, 50);
        }
    }
}

现在,如果您真的想解耦这个概念,您可以使用 ContainerListener 并对通过它添加的新组件做出反应