Search code examples
javaswingpaintrepaintjlayeredpane

JLayeredPane and painting


I am writing an application which has a JLayeredPane (call it layers) containing two JPanels in different layers. I override the paintComponent method of the JPanel at the bottom (call it grid_panel) so it paints a grid, and the the paintComponent method of the one at the top (call it circuit_panel) so it paints a circuit.

Here's a summary of the structure:

layers -
       |-circuit_panel (on top)
       |-grid_panel (at bottom)

I want the grid_panel to stay static, ie, not to do any repaint (except the initial one) since it does not change.

The trouble is, whenever I call circuit_panel.repaint(), grid_panel gets repainted as well! This is a definitely not efficient.

I think this is due to the eager painting behavior of JLayeredPane. Is there a way to disable this feature in JLayeredPane?

In case you're interested to see the above effect, I've written a small demo program:

public class Test2 extends JFrame {

    public Test2() {
        JLayeredPane layers = new JLayeredPane();
        layers.setPreferredSize(new Dimension(600, 400));

        MyPanel1 myPanel1 = new MyPanel1();
        MyPanel2 myPanel2 = new MyPanel2();
        myPanel1.setSize(600, 400);
        myPanel2.setSize(600, 400);
        myPanel1.setOpaque(false);
        myPanel2.setOpaque(false);
        myPanel2.addMouseListener(new MyMouseListener(myPanel2));

        layers.add(myPanel1, new Integer(100)); // At bottom
        layers.add(myPanel2, new Integer(101)); // On top

        this.getContentPane().add(layers, BorderLayout.CENTER);
        this.setSize(600, 400);
    }

    class MyPanel1 extends JPanel {

        Color getRandomColor() {
            int r = (int) (256 * Math.random());
            int g = (int) (256 * Math.random());
            int b = (int) (256 * Math.random());
            return new Color(r, g, b);
        }

        @Override
        public void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2d = (Graphics2D) g;
            g2d.setColor(getRandomColor());
            g2d.fillRoundRect(30, 30, 60, 60, 5, 5);
        }
    }

    class MyPanel2 extends JPanel {

        Color getRandomColor() {
            int r = (int) (256 * Math.random());
            int g = (int) (256 * Math.random());
            int b = (int) (256 * Math.random());
            return new Color(r, g, b);
        }

        @Override
        public void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2d = (Graphics2D) g;
            g2d.setColor(getRandomColor());
            g2d.fillRoundRect(45, 45, 75, 75, 5, 5);
        }
    }

    class MyMouseListener extends MouseAdapter {

        JPanel panel;

        MyMouseListener(JPanel panel) {
            this.panel = panel;
        }

        @Override
        public void mouseClicked(MouseEvent e) {
            panel.repaint();
        }
    }

    /**
     * @param args
     */
    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {

            @Override
            public void run() {
                (new Test2()).setVisible(true);
            }
        });
    }
}

Solution

  • As you found, a BufferedImage is an effective way to cache complex content for efficient rendering; CellTest is an example. A flyweight renderer, shown here, is another approach. Finally, I've re-factored your instructive example in a way that may make experimentation easier.

    Layer Demo

    import java.awt.BorderLayout;
    import java.awt.Color;
    import java.awt.Dimension;
    import java.awt.Graphics;
    import java.awt.Graphics2D;
    import java.awt.RenderingHints;
    import java.awt.event.MouseAdapter;
    import java.awt.event.MouseEvent;
    import java.util.Random;
    import javax.swing.JFrame;
    import javax.swing.JLayeredPane;
    import javax.swing.JPanel;
    import javax.swing.SwingUtilities;
    
    /** @see https://stackoverflow.com/q/9625495/230513 */
    public class LayerDemo extends JFrame {
    
        private static final Dimension d = new Dimension(320, 240);
    
        public LayerDemo() {
            JLayeredPane layers = new JLayeredPane();
            layers.setPreferredSize(d);
    
            layers.add(new LayerPanel(1 * d.height / 8), 100);
            layers.add(new LayerPanel(2 * d.height / 8), 101);
            layers.add(new LayerPanel(3 * d.height / 8), 102);
    
            this.add(layers, BorderLayout.CENTER);
            this.setDefaultCloseOperation(EXIT_ON_CLOSE);
            this.pack();
            this.setLocationByPlatform(true);
        }
    
        private static class LayerPanel extends JPanel {
    
            private static final Random r = new Random();
            private int n;
            private Color color = new Color(r.nextInt());
    
            public LayerPanel(int n) {
                this.n = n;
                this.setOpaque(false);
                this.setBounds(n, n, d.width / 2, d.height / 2);
                this.addMouseListener(new MouseHandler(this));
            }
    
            @Override
            public void paintComponent(Graphics g) {
                super.paintComponent(g);
                Graphics2D g2d = (Graphics2D) g;
                g2d.setRenderingHint(
                    RenderingHints.KEY_ANTIALIASING,
                    RenderingHints.VALUE_ANTIALIAS_ON);
                g2d.setColor(color);
                g2d.fillRoundRect(0, 0, getWidth(), getHeight(), 16, 16);
                g2d.setColor(Color.black);
                g2d.drawString(String.valueOf(n), 5, getHeight() - 5);
            }
    
            private void update() {
                color = new Color(r.nextInt());
                repaint();
            }
        }
    
        private static class MouseHandler extends MouseAdapter {
    
            LayerPanel panel;
    
            MouseHandler(LayerPanel panel) {
                this.panel = panel;
            }
    
            @Override
            public void mouseClicked(MouseEvent e) {
                panel.update();
            }
        }
    
        public static void main(String[] args) {
            SwingUtilities.invokeLater(new Runnable() {
    
                @Override
                public void run() {
                    (new LayerDemo()).setVisible(true);
                }
            });
        }
    }