【问题标题】:Java Swing painting & Mouse event flickerJava Swing 绘画和鼠标事件闪烁
【发布时间】:2011-08-26 09:37:09
【问题描述】:

我有一个扩展 JPanel 的类(如下),这个面板位于 JScrollPane 内。它侦听(自己)鼠标事件并尝试重新定位自己(在拖动时)并重新调整自己(在滚轮上)以模拟鼠标移动和缩放。该面板还负责我的应用程序的主要视觉输出。它存储在 JScrollPane 的可视区域(但在面板的图形上)呈现的 BufferedImage。图像的大小和形状保持与可视区域相匹配。

我的问题就是这样的;

1) 在鼠标事件中,我得到大量的闪烁和性能下降 2)如果我用自己的绘画方法覆盖paint或paintComponent方法,这对于摆脱闪烁和其他绘画问题是可取的,我仍然会得到相同的闪烁效果和从加载的图像中绘制的图形,这些图像具有透明区域然后着色黑色区域。当我手动调用我的paint方法而不覆盖paint和paintComponent方法时,我仍然会闪烁,但透明区域可以正常显示。

我是 Swing 绘画的新手,显然做错了什么,谁能指出我正确的方向来解决这个问题?

谢谢

    import jSim.simulation.Simulation;
    import java.awt.Color;
    import java.awt.Cursor;
    import java.awt.Dimension;
    import java.awt.Graphics;
    import java.awt.Graphics2D;
    import java.awt.Image;
    import java.awt.Point;
    import java.awt.Rectangle;
    import java.awt.Toolkit;
    import java.awt.event.MouseEvent;
    import java.awt.event.MouseWheelEvent;
    import java.awt.event.MouseWheelListener;
    import java.awt.image.BufferStrategy;
    import java.awt.image.BufferedImage;
    import javax.swing.JPanel;
    import javax.swing.JViewport;
    import javax.swing.event.MouseInputListener;

    public class SimPanel extends JPanel implements MouseWheelListener, MouseInputListener {
        //Simulation

        Simulation sim;
        //Viewer
        JViewport viewport;
        Dimension viewSize;
        BufferStrategy strat;
        //Drawing
        Image renderImage;
        Graphics2D g2d;
        boolean draw = true;
        double scale = 1.0;
        Object drawLock = new Object();
        //Mouse events
        int m_XDifference, m_YDifference;

        public SimPanel(JViewport viewport) {
            this.viewport = viewport;
            this.addMouseListener(this);
            this.addMouseMotionListener(this);
            this.addMouseWheelListener(this);

            //this.setup();
        }

        public SimPanel(Simulation sim, JViewport viewport) {
            this.sim = sim;
            this.viewport = viewport;
            this.addMouseListener(this);
            this.addMouseMotionListener(this);
            this.addMouseWheelListener(this);
            //this.setup();
        }

        //Used to initialise the buffered image once drawing begins
        private void setup() {
            synchronized (drawLock) {
                viewSize = viewport.getExtentSize();
                renderImage = new BufferedImage(viewSize.width, viewSize.height, BufferedImage.TYPE_INT_RGB);
                g2d = (Graphics2D) renderImage.getGraphics();
            }
        }

    //    @Override
    //    public void paint(Graphics g)
    //    {
    //        synchronized(drawLock) {
    //        //super.paintComponent(g);
    //        paintSimulation();
    //        }
    //    }
        //Paint the screen for a specific simulation
        public void paintSimulation(Simulation sim) {
            synchronized (drawLock) {
                setSimulation(sim);
                paintSimulation();
            }
        }

        //Paint the screen with the panels simulation
        public void paintSimulation() {
            synchronized (drawLock) {
                //if no image, then init
                if (renderImage == null) {
                    setup();
                }
                //clear the screen
                resetScreen();
                //draw the simulation if not null, to the image
                if (sim != null) {
                    sim.draw(this);
                }
                //paint the screen with the image
                paintScreen();
            }
        }

        private void resetScreen() {
            Dimension newSize = viewport.getExtentSize();
            if (viewSize.height != newSize.height || viewSize.width != newSize.width || renderImage == null) {
                //System.out.println("Screen Size Changed: " + viewSize + "   " + newSize);
                viewSize = newSize;
                renderImage = new BufferedImage(viewSize.width, viewSize.height, BufferedImage.TYPE_INT_RGB);
                g2d = (Graphics2D) renderImage.getGraphics();
            } else {
                g2d.setBackground(Color.DARK_GRAY);
                g2d.clearRect(0, 0, (int) (viewSize.width), (int) (viewSize.height));
            }
        }

        private void paintScreen() {
            Graphics g;
            Graphics2D g2;
            try {
                //g = viewport.getGraphics();
                g = this.getGraphics();
                g2 = (Graphics2D) g;
                if ((g != null) && (renderImage != null)) {
                    g2.drawImage(renderImage, (int) viewport.getViewPosition().getX(), (int) viewport.getViewPosition().getY(), null);
                }
                Toolkit.getDefaultToolkit().sync();  // sync the display on some systems
                g.dispose();
                g2.dispose();
                this.revalidate();
            } catch (Exception e) {
                System.out.println("Graphics context error: " + e);
            }
        }

        //Simulation makes calls to this method to draw items on the image
        public void draw(BufferedImage image, int x, int y, Color colour) {
            synchronized (drawLock) {
                Rectangle r = viewport.getViewRect();
                if (g2d != null && draw) {
                    Point p = new Point((int) (x * scale), (int) (y * scale));
                    if (r.contains(p)) {
                        if (scale < 1) {
                            Graphics2D g2 = (Graphics2D) image.getGraphics();
                            Image test = image.getScaledInstance((int) (image.getWidth(null) * scale), (int) (image.getHeight(null) * scale), Image.SCALE_FAST);
                            g2d.drawImage(test, (int) ((x * scale - r.x)), (int) ((y * scale - r.y)), null);
                        } else {
                            g2d.drawImage(image, x - r.x, y - r.y, null);
                        }
                    }
                }
            }
        }

        public void setDraw(boolean draw) {
            this.draw = draw;
        }

        public void setSimulation(Simulation sim) {
            synchronized (drawLock) {
                if (!(this.sim == sim)) {
                    this.sim = sim;
                }
            }
        }

        public void mouseWheelMoved(MouseWheelEvent e) {
            synchronized (drawLock) {
                updatePreferredSize(e.getWheelRotation(), e.getPoint());
            }
        }

        private void updatePreferredSize(int wheelRotation, Point stablePoint) {
            double scaleFactor = findScaleFactor(wheelRotation);
            if (scale * scaleFactor < 1 && scale * scaleFactor > 0.05) {
                scaleBy(scaleFactor);
                Point offset = findOffset(stablePoint, scaleFactor);
                offsetBy(offset);
                this.getParent().doLayout();
            }
        }

        private double findScaleFactor(int wheelRotation) {
            double d = wheelRotation * 1.08;
            return (d > 0) ? 1 / d : -d;
        }

        private void scaleBy(double scaleFactor) {
            int w = (int) (this.getWidth() * scaleFactor);
            int h = (int) (this.getHeight() * scaleFactor);
            this.setPreferredSize(new Dimension(w, h));
            this.scale = this.scale * scaleFactor;
        }

        private Point findOffset(Point stablePoint, double scaleFactor) {
            int x = (int) (stablePoint.x * scaleFactor) - stablePoint.x;
            int y = (int) (stablePoint.y * scaleFactor) - stablePoint.y;
            return new Point(x, y);
        }

        private void offsetBy(Point offset) {
            Point location = viewport.getViewPosition();
            //this.setLocation(location.x - offset.x, location.y - offset.y);
            viewport.setViewPosition(new Point(location.x + offset.x, location.y + offset.y));
        }

        public void mouseDragged(MouseEvent e) {
            synchronized (drawLock) {
                //Point p = this.getLocation();
                Point p = viewport.getViewPosition();
                int newX = p.x - (e.getX() - m_XDifference);
                int newY = p.y - (e.getY() - m_YDifference);
                //this.setLocation(newX, newY);
                viewport.setViewPosition(new Point(newX, newY));
                //this.getParent().doLayout();
            }
        }

        public void mousePressed(MouseEvent e) {
            synchronized (drawLock) {
                setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
                m_XDifference = e.getX();
                m_YDifference = e.getY();
            }
        }

        public void mouseReleased(MouseEvent e) {
            synchronized (drawLock) {
                setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
            }
        }

        public void mouseClicked(MouseEvent e) {
            //throw new UnsupportedOperationException("Not supported yet.");
        }

        public void mouseEntered(MouseEvent e) {
            //throw new UnsupportedOperationException("Not supported yet.");
        }

        public void mouseExited(MouseEvent e) {
            //throw new UnsupportedOperationException("Not supported yet.");
        }

        public void mouseMoved(MouseEvent e) {
            //throw new UnsupportedOperationException("Not supported yet.");
        }
    }

【问题讨论】:

  • 为什么要同步所有方法?我已经使用 Swing 多年,从来不需要这样做。
  • 对不起,我添加了这些只是为了看看在尝试了很多其他的东西之后是否有任何不同而忘了把它们拿出来。我只是想检查一下没有什么愚蠢的事情发生,比如调整面板中画或类似的大小。
  • Swing 绘制发生在 EDT - 事件调度线程上。在绘制完成之前不会处理“调整面板大小”事件,因为它们都在同一个线程上处理。这就是为什么如果一个事件导致一个较长的进程,你应该在另一个线程中运行那个进程,这样你的 Swing UI 仍然可以响应并重新绘制自己。
  • 您认为在这种情况下将鼠标侦听器移到此类之外会有所帮助吗?我不认为这将是一个漫长的过程。我认为运行时间最长的过程是调用 sim.draw() 因为它负责将所有图形实际绘制到缓冲区图像。但是,当没有注册鼠标事件(即面板没有调整大小或移动)时,显示运行平稳,也许这是有道理的。

标签: java mouse paint flicker


【解决方案1】:

简而言之,查找双缓冲。

更长的答案...

覆盖paintComponent。创建一个离屏图形对象。在那个物体上画你的画。将其复制到传递给paint方法的图形对象中。

哦,摆脱所有的同步。你不需要它。

【讨论】:

  • 这就是我正在做的不是吗?我正在绘制一个图像,然后我的所有绘制方法就是将该图像的内容绘制到 JPanels 图形对象。
  • 我在上面说错了...覆盖paintComponent。也像这样覆盖更新:public void update(Graphics g) { paint(g); } update 默认情况下会擦除面板 - 调用paint会阻止这种情况。
  • 再次感谢您的评论,但我已经尝试上述方法无济于事。还有什么想法吗?这让我发疯了。我需要了解更多关于 Swing 和绘画的知识。
  • 请使用您尝试上述建议的代码更新您的问题。谢谢。
  • 啊,太好了,这就是解决方案。我只是忘记了我的代码中还有一点是直接调用我的绘制方法而不是重绘。我第一次尝试时觉得有点奇怪,因为重写 paintComponent 方法应该意味着所有的绘制调用都在那里进行,除非性能很差,否则事情应该看起来很顺利。无论如何,谢谢你的帮助。
【解决方案2】:

如果您在视口上添加 setOpaque(true),您会通知 Swing 您将自己完成所有绘制(尤其是背景)。这可能已经有点帮助了。

编辑

我环顾四周,认为您应该重写paintComponent。

您可以有 2 张图片和 4 个参考资料:

  • imageToPaint = null
  • imageToWriteTo = null
  • bufferImageOne 已初始化为适当大小的 BufferedImage
  • bufferImageTwo 已初始化为适当大小的 BufferedImage

你会覆盖paintComponent来绘制背景,然后drawImage(imageToPaint)(如果它不为null,它不应该是)

您将有一个线程来执行 imageToWriteTo 的自定义绘制。 最后它交换 imageToPaint 和 imageToWriteTo。

然后你调用 repaint()。这会请求重绘,还有一个额外的好处是 Swing 队列上的所有重绘请求都聚集在一起并导致一次绘制。请不要重新验证或同步。此重绘会在您的第二个线程(事件调度线程)上自动完成。

这样更新模拟图像与实际绘制分离,并且绘制更新不需要等待模拟完成绘制。 这可能是由稍微过时的图像造成的(使用缓冲有点隐含),但它应该会产生更好的结果。

简而言之,对 imageToWriteTo 进行了昂贵的写入。绘画是使用 imageToPaint 完成的。昂贵的写作以交换 imageToWriteTo 和 imageToPaint 结束。

【讨论】:

  • 感谢您的回答,我已经尝试过了,但没有任何区别。
  • @James 稍微更新了我的答案。
猜你喜欢
  • 2011-11-25
  • 1970-01-01
  • 1970-01-01
  • 2011-09-17
  • 2019-10-01
  • 1970-01-01
  • 2014-09-07
  • 2014-10-21
  • 1970-01-01
相关资源
最近更新 更多