【问题标题】:Can a specific RepaintManager be used for a specific JPanel?特定的 RepaintManager 可以用于特定的 JPanel 吗?
【发布时间】:2020-08-21 20:27:58
【问题描述】:

我知道 Java RepaintManager 将合并对 repaint() 的调用,这对于 99% 的渲染来说都很好。我有一个 JPanel,我想在计时器(100 毫秒)上更新图像,以提供像视频一样的平滑渲染。在实践中,除非鼠标被移动,否则 RepaintManager 似乎会窃取/忽略所有其他 repaint() 。我想知道我必须有哪些选项来解决这个问题。我还查看了paintImmediately(),但它导致与repaint() 相同的行为,因此不是很有用。提前感谢您提供有用的想法!

  1. 是否可以为特定的 JPanel 创建和使用自定义 RepaintManager,而对其他所有内容使用默认值?
  2. 有没有办法让默认的 RepaintManger 确定某个面板是“脏的”,以便重新绘制而不是忽略?

下面是一些用于说明实现的代码,您会注意到(至少在我的 Linux 测试中),这些数字几乎每隔一个序列就会跳过。

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

【问题讨论】:

  • 请阅读How do I ask a good question? 请注意上面写着的部分:帮助他人重现问题 如果您的问题与您的代码有关'已经写了,你应该包括一些。
  • 我很高兴提供一些代码,但我认为这更像是 Java 的基础。让我看看是否可以在不提供图像的情况下创建一些显示问题的最小内容。
  • 你在linux上吗?
  • 我想我遇到了同样的问题,stackoverflow.com/questions/61188644/… 唯一能让它在没有任何跳跃的情况下持续重绘的方法是以更高的速率调用重绘。在 osx 上,我的示例非常顺利。在linux上它会开始跳跃。我不认为这是重绘的结合。
  • 是的!我在Linux上!我刚开始怀疑它是否可能与硬件/操作系统有关!

标签: java swing repaintmanager


【解决方案1】:

我仍然不确定绘画问题是什么。

我对您的代码做了一些更改。

  1. 我使用SwingUtilitiesinvokeLater方法将Swing组件的创建和执行放在Event Dispatch Thread(EDT)上。

  2. 我从paintComponent 方法中删除了除绘画代码之外的所有内容。在paintComponent 方法中除了绘画之外什么都不应该发生。

这是我运行的代码。

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.util.Timer;
import java.util.TimerTask;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;

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;
    private int _TimerCnt;
    private int _PaintCnt;

    public PanelRepaintIssue() {
        _Index = 0;
        _TimerCnt = 0;
        _PaintCnt = 0;

        _ImagePanel = new JPanel() {

            private static final long serialVersionUID = 1L;

            @Override
            protected void paintComponent(Graphics g) {
                super.paintComponent(g);

//              if (_Index < kNUM_IMAGES) {
                    g.drawImage(_Images[_Index], 0, 0, null);
                    ++_PaintCnt;
//                  System.out.println("Timer: " + _TimerCnt + 
//                          " Paint: " + _PaintCnt);
//              }
            }
        };
        _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);
            Color color = new Color(128, 128, 20 * i);
            Graphics g = _Images[i].getGraphics();
            g.setColor(color);
            g.fillRect(0, 0, kWIDTH, kHEIGHT);
            g.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() {
                ++_TimerCnt;
                ++_Index;

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

                _ImagePanel.repaint();

                System.out.println("Timer: " + _TimerCnt + 
                        " Paint: " + _PaintCnt);
                // Also tried _ImagePanel.paintImmediately
                //    (0, 0, kWIDTH, kHEIGHT);
            }
        };

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

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                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);
            }   
        });
    }
}

编辑:根据评论中提供的新信息而不是原始问题,我重新编写了代码。

我没有发现问题。这是Timer 方法中println 的最后4 行。

Timer: 200 Paint: 201
Timer: 201 Paint: 202
Timer: 202 Paint: 203
Timer: 203 Paint: 205

如您所见,数字是同步的,除了最后一行。差异可能是我通过 Eclipse 取消程序造成的。

这是修改后的代码。我仍然没有看到问题。

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.util.Timer;
import java.util.TimerTask;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;

public class PanelRepaintIssue {
    private static final int kWIDTH = 300;
    private static final int kHEIGHT = 200;
    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;
    private int _TimerCnt;
    private int _PaintCnt;

    public PanelRepaintIssue() {
        _Index = 0;
        _TimerCnt = 0;
        _PaintCnt = 0;

        _ImagePanel = new JPanel() {

            private static final long serialVersionUID = 1L;

            @Override
            protected void paintComponent(Graphics g) {
                super.paintComponent(g);
                g.drawImage(_Images[_Index], 0, 0, null);
                ++_PaintCnt;
            }
        };
        _ImagePanel.setSize(new Dimension(kWIDTH, kHEIGHT));
        _ImagePanel.setPreferredSize(
                new Dimension(kWIDTH, kHEIGHT));

        _Images = new BufferedImage[kNUM_IMAGES];

        int x = kWIDTH / 2;
        int y = kHEIGHT / 2;

        for (int i = 0; i < _Images.length; ++i) {
            _Images[i] = new BufferedImage(kWIDTH, kHEIGHT, 
                    BufferedImage.TYPE_INT_ARGB);
            String text = Integer.toString(i + 1);
            Graphics g = _Images[i].getGraphics();
            g.setFont(g.getFont().deriveFont(80f));
            g.setColor(Color.BLACK);
            g.drawString(text, x, y);
            g.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() {
                ++_TimerCnt;
                ++_Index;

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

                updateImagePanel();

                System.out.println("Timer: " + _TimerCnt + 
                        " Paint: " + _PaintCnt);
            }
        };

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

    private void updateImagePanel() {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                _ImagePanel.repaint();
            }
        });
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                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);
            }   
        });
    }
}

再次编辑:我在准备午餐时运行了您的代码。这是println 输出的最后几行。

Timer: 4037 Paint: 4038
Timer: 4038 Paint: 4040
Timer: 4039 Paint: 4040
Timer: 4040 Paint: 4041
Timer: 4041 Paint: 4042
Timer: 4042 Paint: 4043

如您所见,动画没有丢失一帧。在每帧 250 毫秒的情况下,Swing 重绘管理器可以毫无问题地绘制图像。

只是为了好玩,我将kREPAINT_DELAY 减少到每帧 10 毫秒。那是 100 帧/秒。

这是该测试中的 println 行。

Timer: 1104 Paint: 1105
Timer: 1105 Paint: 1106
Timer: 1106 Paint: 1107
Timer: 1107 Paint: 1108
Timer: 1108 Paint: 1109
Timer: 1109 Paint: 1110

再一次,没有丢帧。

问题可能出在您的代码中,而不是 Swing 重绘管理器。

【讨论】:

  • 谢谢。我认为您在更新中忘记了 invokeLater() 。代码示例来自“提出更好的问题”的请求,但它本身并没有显示我遇到的问题。基本上,如果您要让每个图像显示一个数字(_Images[0] 显示一个大零等),我看到的是其他所有图像都没有显示。期望看到 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 - 相反我看到 1, 3, 5, 7, 9,重复,除非鼠标移动然后我看到预期。
【解决方案2】:

问题最终是特定于 Linux 的平台,可能还有其他问题,但仅测试了 Linux。使用以下方法更正或避免了该问题:

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

Toolkit.getDefaultToolkit().sync() 的添加确保 repaint() 实际更新了屏幕上的图像。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-10-01
    • 1970-01-01
    • 2014-02-05
    • 1970-01-01
    相关资源
    最近更新 更多