I have several JPanel
s 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.
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.