How to make JScrollPane (In BorderLayout, containing JPanel) smoothly autoscroll
I'm trying to have a JPanel of varying size (potentially much wider than the standard screen) inside of a JScrollPanel. Currently it works out great, and I have configured the scrollbars to work fine manually, however I would like the JPanel to "scroll" constantly to the left, so that over time the whole thing is displayed. All of the answers I found are specific to JTextArea and use Carets, or use rectToVisible. Neither of these will work because I'm trying to scroll internally to a single JPanel.
I've included what I believe to be all of the relevant code below.
center is the JPanel (of which Grid is a subclass, used to paint specifically a grid with some specific cells colored) with a BorderLayout that I would like to autoscroll.
public GuiViewFrame(Song playMe) {
String[][] songArray = playMe.to2DArray();
this.displayPanel = new ConcreteGuiViewPanel(playMe);
main = new JPanel();
main.setLayout(new BorderLayout());
displayPanel.setLayout(new BorderLayout());
center = new Grid(playMe);
labels = new Labels(playMe);
horiz = new Horiz(playMe);
center.setPreferredSize(new Dimension(10 * songArray.length, 10 * songArray[0].length));
horiz.setPreferredSize(new Dimension(10 * songArray.length, 10));
horiz.setVisible(true);
main.add(center, BorderLayout.CENTER);
main.add(horiz, BorderLayout.NORTH);
scroll = new JScrollPane(main,
JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
add(scroll, BorderLayout.CENTER);
labels.setPreferredSize(new Dimension(20, 10 * songArray[0].length));
labels.setVisible(true);
add(labels, BorderLayout.WEST);
JScrollBar horiz = scroll.getHorizontalScrollBar();
InputMap im = horiz.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
im.put(KeyStroke.getKeyStroke("RIGHT"), "positiveUnitIncrement");
im.put(KeyStroke.getKeyStroke("LEFT"), "negativeUnitIncrement");
im.put(KeyStroke.getKeyStroke("HOME"), "minScroll");
im.put(KeyStroke.getKeyStroke("END"), "maxScroll");
this.setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
this.pack();
}
The project as a whole is to generate a view for playing music that combines MIDI and a GUI, but right now once MIDI plays enough of the song, the relevant notes are off screen. I would like to scroll at a rate to keep pace with MIDI.
Solution 1:
You can set the value of the horizontal scrollbar to control what is currently visible:
JScrollBar horizontal = scroll.getHorizontalScrollBar();
horizontal.setValue( horizontal.getValue() + ??? );
You would need to use a Swing Timer to schedule the scrolling at an appropriate interval.
Simple example of using a Timer
to scroll text:
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import javax.swing.*;
public class TimerTest extends JPanel implements ActionListener
{
JLabel timeLabel;
JLabel scrollLabel;
public TimerTest()
{
setLayout( new BorderLayout() );
timeLabel = new JLabel( new Date().toString() );
add(timeLabel, BorderLayout.NORTH);
scrollLabel = new JLabel( "Some continuously scrolling text!! " );
add(scrollLabel, BorderLayout.SOUTH);
int time = 1000;
javax.swing.Timer timer = new javax.swing.Timer(time, this);
timer.setInitialDelay(1);
timer.start();
}
public void actionPerformed(ActionEvent e)
{
timeLabel.setText( new Date().toString() );
String oldText = scrollLabel.getText();
// Scroll right to left
String newText = oldText.substring(1) + oldText.substring(0, 1);
// Scroll left to right
// int length = oldText.length();
// String newText = oldText.substring(length-1, length)
// + oldText.substring(0, length-1);
scrollLabel.setText( newText );
}
private static void createAndShowGUI()
{
JFrame frame = new JFrame("SSCCE");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add( new TimerTest() );
frame.setLocationByPlatform( true );
frame.pack();
frame.setVisible( true );
}
public static void main(String[] args)
{
EventQueue.invokeLater(new Runnable()
{
public void run()
{
createAndShowGUI();
}
});
}
}
Solution 2:
One possible solution might be to take advantage of JComponent#scrollRectToVisible
and a Swing Timer
For example...
import java.awt.Container;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JViewport;
import javax.swing.Scrollable;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class ScrollTest {
public static void main(String[] args) {
new ScrollTest();
}
public ScrollTest() {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new JScrollPane(new TestPane()));
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel implements Scrollable {
public TestPane() {
Timer timer = new Timer(40, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
JViewport viewport = (JViewport) getParent();
Rectangle viewRect = viewport.getViewRect();
if (viewRect.x + viewRect.width < getWidth()) {
viewRect.x += 2;
scrollRectToVisible(viewRect);
} else {
((Timer)e.getSource()).stop();
}
}
});
timer.start();
}
@Override
public Dimension getPreferredSize() {
return new Dimension(1000, 200);
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.drawLine(0, 0, getWidth(), getHeight());
g2d.dispose();
}
@Override
public Dimension getPreferredScrollableViewportSize() {
return new Dimension(200, 200);
}
@Override
public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) {
return 64;
}
@Override
public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction) {
return 64;
}
@Override
public boolean getScrollableTracksViewportWidth() {
return getPreferredSize().width <= getParent().getSize().width;
}
@Override
public boolean getScrollableTracksViewportHeight() {
return getPreferredSize().height <= getParent().getSize().height;
}
}
}