Here is my code for the 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; }
I tried to do getVerticalScrollbar.setValue(getVerticalScrollbar().getMaximum())
but it does not scroll to the end when I add JLabels to it. I’m trying to make it so that it will always scroll to the bottom of the screen once a new JLabel gets added to the panel. I din’t use JTextArea as I want each line to have a different foreground color, so I used JLabels.
I also tried adding this method
public void scrollToBottom() { getVerticalScrollbar().getMaximum(); }
but it just freezes the ScrollPane and I am unable to scroll.
Any help would be appreciated!
Advertisement
Answer
I’m not sure what you hope to achieve by extending JScrollPane
, not sure that it’s really its core responsibility, after all, you could have its “viewport view” do it itself, for example…
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); } } }
The magic is right here…
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)); } });
Here, I’m asking the contentPane
(which is the container holding all the squares) to “scroll to a visible rectangle” based on my needs
Now, in you case, when you add a new component to the “container”, you would need to instruct the component to scroll to a position which would show the new component.
This is going to be a little more complicated as you will need to trigger a layout pass first, so you can get the new size of the container
Dynamic addition to the container…
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); } } }
Now, if you really wanted to decouple the concept, you could us a ContainerListener
and react to the new component been added via it