Search code examples
javaswingjpanelpaintcomponent

paintComponent called multiple times with overlapping components


I have several JPanels layered on top of each other inside a parent JPanel. When I invoke repaint() on one of them, all of them repaint as many times as there are layers. This grows quadratic! For 3 panels I see 3 redraws in each, which is 9 redraws. With 5 panels there are 25 redraws.

I found out that for overlapping components, I should override isOptimizedDrawingEnabled() in the parent and return false. Or use a JLayeredPane as the parent. I tried both, and the results were the same.

What is causing this?

How can I stop my panels from repainting multiple times?

Edit: SSCCE/MCVE as follows:

public class JPanelTest extends JPanel {
    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                new JPanelTest();
            }
        });
    }

    public JPanelTest() {
        super(null);
        setLayout(new OverlayLayout(this));

        JFrame f = new JFrame(getClass().getSimpleName());
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        f.setSize(600, 600);

        final Model model = new Model();

        f.getContentPane().add(this);
        this.add(new MyPanel(model));
        this.add(new MyPanel(model));
        this.add(new MyPanel(model));
        this.add(new MyPanel(model));
        this.add(new MyPanel(model));

        f.setLocationRelativeTo(null);
        f.setVisible(true);

        addMouseListener(new MouseAdapter() {
            public void mouseClicked(MouseEvent ex) {
                model.setColor(new Color((int)ex.getWhen()));
            }
        });
    }

    @Override
    public boolean isOptimizedDrawingEnabled() {
        return false;
    }
}

// My own containers/listeners/events are larger, so this is shorter for SSCCE/MCVE.
class Model extends PropertyChangeSupport {
    Color color = Color.BLACK;

    public Model() {
        super(Color.BLACK);
    }

    public void setColor(Color color) {
        this.color = color;
        firePropertyChange("color", null /* meh */, color);
    }
}

class MyPanel extends JPanel implements PropertyChangeListener {
    static int  instanceCounter = 0;
    int         repaintCounter  = 0;
    final int   instance;

    private Model model;

    public MyPanel(Model model) {
        super(null);
        instance = ++instanceCounter;
        setOpaque(false);

        this.model = model;
        model.addPropertyChangeListener(this);
    }

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);

        g.setColor(model.color);
        g.drawString("panel " + instance + ", repaint " + ++repaintCounter, 1, instance * 15);
    }

    @Override
    public void propertyChange(PropertyChangeEvent evt) {
        System.out.println("panel " + instance + ": Value is now " + evt.getNewValue());
        repaint();
    }
}

Some extra notes: I noticed that for the panels to each repaint multiple times, I have to call repaint() multiple times. However, when all panels are side-by-side (not overlapping), they are all repainted ONCE.


Solution

  • What is causing this?

    I'm guessing the cause is because all the panels are non-opaque.

    When painting a non-opaque component the RepaintManager must find the first opaque parent and then paint the parent and all the children. This happens 5 times so you get 25 redraws.

    In your simple example you could change the code in your propertyChanged(...) method:

    getParent().repaint();
    

    Now 5 repaint requests will be made on the parent container instead of the individual panels. The RepaintManager will combine these requests in to a single request and then the panel and its 5 children will be painted once.