如何使JScrollPane(在BorderLayout中,包含JPanel)平滑自动滚动

Posted

技术标签:

【中文标题】如何使JScrollPane(在BorderLayout中,包含JPanel)平滑自动滚动【英文标题】:How to make JScrollPane (In BorderLayout, containing JPanel) smoothly autoscroll 【发布时间】:2016-02-27 16:39:24 【问题描述】:

我正在尝试在 JScrollPanel 中放置一个不同大小的 JPanel(可能比标准屏幕宽得多)。目前效果很好,我已经将滚动条配置为手动正常工作,但是我希望 JPanel 不断向左“滚动”,以便随着时间的推移显示整个内容。我找到的所有答案都特定于 JTextArea 并使用 Carets,或使用 rectToVisible。这些都不起作用,因为我试图在内部滚动到单个 JPanel。

我在下面包含了我认为的所有相关代码。

center 是 JPanel(其中 Grid 是一个子类,用于专门绘制带有某些特定单元格颜色的网格),它带有我想自动滚动的 BorderLayout。

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

整个项目是生成一个结合 MIDI 和 GUI 来播放音乐的视图,但是现在一旦 MIDI 播放了足够多的歌曲,相关的音符就会离开屏幕。我想以一定的速度滚动以跟上 MIDI 的步伐。

【问题讨论】:

一个 JScrollPane 在内部使用一个 JViewport。我认为直接使用 JViewport 而不是 JScrollPane 会更好地满足您的需求。滚动本身可以使用 javax.swing.Timer 来完成。 【参考方案1】:

您可以设置水平滚动条的值来控制当前可见的内容:

JScrollBar horizontal = scroll.getHorizontalScrollBar();
horizontal.setValue( horizontal.getValue() + ??? );

您需要使用Swing Timer 以适当的时间间隔安排滚动。

使用Timer 滚动文本的简单示例:

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

【讨论】:

我最终选择了这个。我有一个实际上基于 MIDI 的 getCurrentTick 的内置计时器,我根据它调整滚动条的值。现在我只需要四处寻找正确的值。【参考方案2】:

一种可能的解决方案是利用 JComponent#scrollRectToVisible 和 Swing Timer

例如...

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;
        

    


【讨论】:

以上是关于如何使JScrollPane(在BorderLayout中,包含JPanel)平滑自动滚动的主要内容,如果未能解决你的问题,请参考以下文章

使 JScrollPane 控制多个组件

将JScrollPane添加到GridBagLayout

如何在 JScrollPane 中居中垂直和水平滚动条?

如何强制刷新/重绘 JScrollPane?

如何在漫长的过程中让 JScrollPane 更新

如何触发jScrollPane滚动到底部事件