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!
CodePudding user response:
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