Search code examples
javaswingjscrollpane

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

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