Incremental graphics in Swing

前端 未结 1 373
失恋的感觉
失恋的感觉 2020-12-21 08:51

I\'m trying to get the hang of doing graphics stuff (drawing lines, etc.) in Swing. So far, all the tutorials I\'ve seen declare a class that overrides paintComponent

相关标签:
1条回答
  • 2020-12-21 09:42

    Painting in Swing is destructive. That is, whenever a new paint cycle runs, you are expected to completely rebuild the output as per the state of the object you are painting.

    Take a look at Painting in AWT and Swing

    So when you call

    p.draw (new GraphicsAction () {
        public void action (Graphics g) {
            g.setColor(Color.RED);
            g.drawLine(5, 30, 100, 50);
        }
    });
    

    Followed by

    p.draw (new GraphicsAction () {
        public void action (Graphics g) {
            g.setColor(Color.BLUE);
            g.drawLine(5, 30, 150, 40);
        }
    });
    

    You are basically throwing away the first action. Ignoring how repaints are scheduled for the moment. The first request says, "paint a red line", the second says, "paint a blue line", but before these actions are executed, the Graphics context is cleaned, preparing it for updating.

    This is very important, as the Graphics context you are provided is a shared resource. All the components painted before have used the same context, all the components painted after you will use the same context. This means, if you don't "clean" the context before painting to it, you can end up with unwanted paint artifacts.

    But how can you get around it??

    There are a few choices here.

    You could draw to a backing buffer (or BufferedImage) which has it's own Graphics context, which you can add to and would only need to "paint" in your paintComponent method.

    This would mean, each time you call p.draw(...), you would actually paint to this buffer first then call repaint.

    The problem with this, is you need to maintain the size of the buffer. Each time the component size changes, you would need to copy this buffer to a new buffer based on the new size of the component. This is a little messy, but is doable.

    The other solution would be to place each action in a List and when required, simply loop through the List and re-apply the action whenever required.

    This is probably the simplest approach, but as the number of actions grow, could reduce the effectiveness of the paint process, slowing the paint process.

    You could also use a combination of the two. Generate a buffer when it doesn't exists, loop through the List of actions and renderer them to the buffer and simply paint the buffer in the paintComponent method. Whenever the component is resized, simply null the buffer and allow the paintComponent to regenerate it...for example...

    Also, if I put a Thread.sleep(1000) between the two p.draw calls

    Swing is a single threaded framework. That means that all updates and modifications are expected to done within the context of the Event Dispatching Thread.

    Equally, anything that blocks the EDT from running will prevent it from process (amongst other things) paint requests.

    This means that when you sleep between the p.draw calls, you are stopping the EDT from running, meaning it can't process your paint requests...

    Take a look at Concurrency in Swing for more details

    Updated with example

    enter image description here

    I just want to point out that is really inefficient. Re-creating the buffer each time invalidate is called will create a large number of short lived objects and could a significant drain on performance.

    Normally, I would use a javax.swing.Timer, set to be non-repeating, that would be restarted each time invalidate was called. This would be set to a short delay (somewhere between 125-250 milliseconds). When the timer is triggered, I would simply re-construct the buffer at this time, but this is just an example ;)

    import java.awt.Color;
    import java.awt.Dimension;
    import java.awt.Graphics;
    import java.awt.Graphics2D;
    import java.awt.image.BufferedImage;
    import javax.swing.JFrame;
    import javax.swing.JPanel;
    import javax.swing.SwingUtilities;
    
    public class DrawTest {
    
        private interface GraphicsAction {
    
            public void action(Graphics g);
        }
    
        private static class TestPanel extends JPanel {
    
            private GraphicsAction paintAction;
            private BufferedImage buffer;
    
            @Override
            public void invalidate() {
                BufferedImage img = new BufferedImage(
                        Math.max(1, getWidth()),
                        Math.max(1, getHeight()), BufferedImage.TYPE_INT_ARGB);
                Graphics2D g2d = img.createGraphics();
                g2d.setColor(getBackground());
                g2d.fillRect(0, 0, getWidth(), getHeight());
                if (buffer != null) {
                    g2d.drawImage(buffer, 0, 0, this);
                }
                g2d.dispose();
                buffer = img;
                super.invalidate();
            }
    
            protected BufferedImage getBuffer() {
                if (buffer == null) {
                    buffer = new BufferedImage(
                            Math.max(1, getWidth()),
                            Math.max(1, getHeight()), BufferedImage.TYPE_INT_ARGB);
                    Graphics2D g2d = buffer.createGraphics();
                    g2d.setColor(getBackground());
                    g2d.fillRect(0, 0, getWidth(), getHeight());
                    g2d.dispose();
                }
                return buffer;
            }
    
            public void draw(GraphicsAction action) {
                BufferedImage buffer = getBuffer();
                Graphics2D g2d = buffer.createGraphics();
                action.action(g2d);
                g2d.dispose();
                repaint();
            }
    
            @Override
            public void paintComponent(Graphics g) {
                super.paintComponent(g);
                g.drawImage(getBuffer(), 0, 0, this);
            }
        }
    
        private static void createAndShowGui() {
            JFrame frame = new JFrame("DrawTest");
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.setPreferredSize(new Dimension(500, 500));
    
            TestPanel p = new TestPanel();
            frame.getContentPane().add(p);
            frame.pack();
            frame.setVisible(true);
            p.repaint();
    
            p.draw(new GraphicsAction() {
                public void action(Graphics g) {
                    g.setColor(Color.RED);
                    g.drawLine(5, 30, 100, 50);
                }
            });
    
            // in real life, we would do some other stuff and then
            // later something would want to add a blue line to the
            // diagram 
            p.draw(new GraphicsAction() {
                public void action(Graphics g) {
                    g.setColor(Color.BLUE);
                    g.drawLine(5, 30, 150, 40);
                }
            });
    
        }
    
        public static void main(String[] args) {
            SwingUtilities.invokeLater(new Runnable() {
                @Override
                public void run() {
                    createAndShowGui();
                }
            });
        }
    }
    
    0 讨论(0)
提交回复
热议问题