简体   繁体   English

Java Swing绘画和鼠标事件闪烁

[英]Java Swing painting & Mouse event flicker

I have a class that extends JPanel (below), this panel sits inside a JScrollPane. 我有一个扩展JPanel的类(如下),该面板位于JScrollPane中。 It listens (to itself) for mouse events and tries to reposition itself (on drag) and rescale itself (on wheel) to simulate mouse movement and zooming. 它侦听(自己)鼠标事件,并尝试重新定位自身(在拖动时)和重新缩放自身(在滚轮上)以模拟鼠标的移动和缩放。 The panel is also responsible for the main visual output of my application. 该面板还负责我的应用程序的主要视觉输出。 It stores a BufferedImage that is rendered in the viewable area of the JScrollPane (but on the panel's graphics). 它存储一个呈现在JScrollPane的可视区域(但在面板的图形上)的BufferedImage。 The size and shape of the image is maintained to match the viewable area. 图像的大小和形状将保持与可视区域匹配。

My problems are as such; 我的问题就是这样。

1) On mouse events I get a massive amount of flicker and performance drops 2) If I override the paint or paintComponent methods with my own painting method, which is desirable to get rid of flicker and other painting issues, I still get the same flicker effect and graphics drawn from loaded images that have a transparent area then colour that area black. 1)在发生鼠标事件时,会出现大量闪烁,并且性能会下降2)如果我用自己的绘画方法覆盖了paint或paintComponent方法,这是摆脱闪烁和其他绘画问题的理想方法,那么我仍然会得到相同的闪烁从具有透明区域的已加载图像绘制的效果和图形,然后将该区域涂成黑色。 When I call my paint method manually without overriding the paint and paintComponent methods, I still get flicker but the transparent areas display properly. 当我手动调用我的paint方法而不覆盖paint和paintComponent方法时,仍然出现闪烁,但透明区域显示正确。

I'm new to Swing painting and obviously doing something wrong, could anyone point me in the right direction to fix this? 我是Swing绘画的新手,显然做错了什么,有人可以指出正确的方向来解决此问题吗?

Thanks 谢谢

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

In short, look up double buffering. 简而言之,查找双重缓冲。

The longer answer... 更长的答案...

Override paintComponent. 覆盖paintComponent。 Create an offscreen graphics object. 创建一个屏幕外的图形对象。 Do your painting on that object. 在该物体上绘画。 Copy that over to the graphics objects passed into the paint method. 将其复制到传递到paint方法的图形对象中。

Oh, and get rid of all the synchronization. 哦,摆脱所有同步。 You don't need it. 不用了

If you add a setOpaque(true) on the viewport you inform Swing that you'll do all the painting (especially the background) yourself. 如果在视口上添加setOpaque(true),则通知Swing您将自己完成所有绘画(尤其是背景)。 That may already help a bit. 这可能已经有所帮助。

EDIT 编辑

I've looked around a bit more, and think you should override paintComponent. 我环顾了四周,并认为您应该重写paintComponent。

You could have 2 images, and four references: 您可能有2张图片和4个参考:

  • imageToPaint = null imageToPaint = null
  • imageToWriteTo = null imageToWriteTo = null
  • bufferImageOne initialized to appropriately sized BufferedImage 将bufferImageOne初始化为适当大小的BufferedImage
  • bufferImageTwo initialized to appropriately sized BufferedImage 将bufferImageTwo初始化为适当大小的BufferedImage

You'd override paintComponent to draw the background and then drawImage(imageToPaint) (if it's not null, which it shouldn't be) 您可以重写paintComponent来绘制背景,然后绘制drawImage(imageToPaint)(如果它不为null,则不应为null)

You'll have a thread which does the custom painting of imageToWriteTo. 您将拥有一个执行imageToWriteTo的自定义绘制的线程。 At the end it swaps imageToPaint and imageToWriteTo. 最后,它交换imageToPaint和imageToWriteTo。

Then you call repaint(). 然后,您调用repaint()。 This requests a repaint, with the added advantage that all repaint requests on the Swing queue get taken together and result in a single paint. 这要求重新绘制,其附加优点是将Swing队列上的所有重新绘制请求放在一起,并产生单个绘制。 No revalidate or sync please. 请不要重新验证或同步。 This repainting is automatically done on your second thread, the Event Dispatch Thread. 重新绘制是在您的第二个线程“事件调度线程”上自动完成的。

That way updating the simulation image is decoupled from actual painting, and paint updates need not wait for the simulation to finish drawing. 这样,更新仿真图像就与实际绘画分离了,并且绘画更新无需等待模拟完成绘制。 It may result from slightly outdated images (a bit implicit in using buffering) but it should yield better results. 这可能是由于图像有些过时(在使用缓冲时有些隐含),但应产生更好的结果。

In short, expensive writing is done to imageToWriteTo. 简而言之,对imageToWriteTo进行了昂贵的编写。 Painting is done using imageToPaint. 使用imageToPaint完成绘制。 Expensive writing ends with swapping imageToWriteTo and imageToPaint. 昂贵的写入以交换imageToWriteTo和imageToPaint结尾。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM