JLayeredPane and painting
Asked Answered
V

1

6

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);
            }
        });
    }
}
Valenti answered 8/3, 2012 at 21:47 Comment(6)
I'm pretty sure you won't be able to get around this apart from maybe double buffering your layers if expensive to draw. It all comes down to RepaintManager which keeps track of "dirty" regions, because the layers share the same screen space a repaint of one will trigger a repaint of another... Good luck.Patch
So is there a way for a JPanel to cache what it should paint (of course what's painted should be static), and let repaint() to just paint the cache, instead of doing all those calculations again and again?Valenti
hehehe on resize too, with terrible flickering +1, there must be another issue, but are you able to use JLayer (since Java7) or JXLayer (for Java6)Butterfat
Ok, I found an answer to my question here leepoint.net/notes-java/GUI-lowlevel/graphics/43buffimage.htmlValenti
Yes, a BufferedImage is definitely the way to go.Holle
+1 for sscce.Giacopo
G
7

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://mcmap.net/q/1729365/-jlayeredpane-and-painting/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);
            }
        });
    }
}
Giacopo answered 9/3, 2012 at 6:32 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.