javaswingrepaintmanager

Can a specific RepaintManager be used for a specific JPanel?


I understand that the Java RepaintManager will coalesce calls to repaint(), which is fine for 99% of rendering. I have one JPanel that I'd like to update on a timer (100 ms) with images to provide smooth'ish rendering like video. In practice the RepaintManager seems to steal/ignore about every other repaint() unless the mouse is being moved. I'm wondering what options I have to workaround this issue. I also looked at paintImmediately(), but it results in the same behavior as repaint(), thus not very useful. Thanks in advance for the helpful ideas!

  1. Is it possible to create and use a custom RepaintManager for a specific JPanel, and use the default for everything else?
  2. Is there a way to make the default RepaintManger determine a certain panel is "dirty" such that it would be repainted vs. ignored?

Below is some code to illustrate the implementation, you will notice (at least on my Linux testing), that the numbers will skip almost every other sequence.

    public class PanelRepaintIssue
    {
        private static final int kWIDTH = 200;
        private static final int kHEIGHT = 100;
        private static final int kNUM_IMAGES = 10;
        private static final int kREPAINT_DELAY = 250;

        private final JPanel _ImagePanel;
        private final BufferedImage[] _Images;
        private final Timer _Timer;

        private TimerTask _TimerTask;
        private int _Index;

        public PanelRepaintIssue()
        {
            _Index = 0;

            _ImagePanel = new JPanel()
            {
                @Override
                protected void paintComponent(Graphics g)
                {
                    super.paintComponent(g);

                    if (_Index < kNUM_IMAGES)
                    {
                        g.drawImage(_Images[_Index], 0, 0, null);
                    }
                }
            };
            _ImagePanel.setSize(new Dimension(kWIDTH, kHEIGHT));
            _ImagePanel.setPreferredSize(new Dimension(kWIDTH, kHEIGHT));

            _Images = new BufferedImage[kNUM_IMAGES];

            for (int i = 0; i < _Images.length; ++i)
            {
                _Images[i] = new BufferedImage(kWIDTH, kHEIGHT, BufferedImage.TYPE_INT_ARGB);
                Graphics2D t2d = _Images[i].createGraphics();
                t2d.setColor(Color.BLACK);
                t2d.fillRect(0, 0, kWIDTH, kHEIGHT);
                t2d.setColor(Color.RED);
                t2d.drawString(Integer.toString(i), kWIDTH/2, kHEIGHT/2);
                t2d.dispose();
            }

            _Timer = new Timer(this.getClass().getName());
        }

        public JPanel getPanel()
        {
            return _ImagePanel;
        }

        public void start()
        {
            if (null != _TimerTask)
            {
                _TimerTask.cancel();
                _TimerTask = null;
            }

            _TimerTask = new TimerTask()
            {
                @Override
                public void run()
                {
                    ++_Index;

                    if (_Index >= kNUM_IMAGES)
                    {
                        _Index = 0;
                    }

                    _ImagePanel.repaint();
                    // Also tried _ImagePanel.paintImmediately(0, 0, kWIDTH, kHEIGHT);
                }
            };

            _Timer.scheduleAtFixedRate(_TimerTask, 1000, kREPAINT_DELAY);
        }

        public static void main(String[] args)
        {
            PanelRepaintIssue tPanel = new PanelRepaintIssue();
            tPanel.start();
            JFrame tFrame = new JFrame("PanelRepaintIssue");
            tFrame.add(tPanel.getPanel());
            tFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            tFrame.setResizable(false);
            tFrame.pack();
            tFrame.setVisible(true);
        }
    }

Solution

  • The issue ended up being platform specific with Linux, possibly other, but only Linux was tested. The issue was corrected or avoided by using the following:

    _ImagePanel.repaint();
    Toolkit.getDefaultToolkit().sync();
    

    The addition of Toolkit.getDefaultToolkit().sync() ensured that repaint() actually updated the image on the screen.