Search code examples
javaswingdrawingpaintcomponentrepaint

Why does this this Swing bug occur when using repaint() and not with getParent().repaint()?


This question is based on a problem I had a while back with a simple Swing dice program. The original question I posted is here and has an accepted answer, but I'd like to know exactly what is happening, why the problem occurs, and why the solution works.

I managed to whittle down the original code to get to the core of the problem and it now looks very different:

  • I have two ColorPanels that each draw a coloured square
  • when you click on a panel the box should change colour in this order: start at black, then >red>green>blue>red>green>blue> etc
  • once a box has changed colour it should never be black again

However when I just call repaint() in the MouseListener, the program behaves very strangely:

  • I click on one panel and the square's colour changes
  • I then click on the other and it's square changes colour, but the first square also changes, back to black
  • you can see this behaviour in the gif below:

buggy program

If you use getParent().repaint() instead this behaviour goes away and the program behaves as expected:

enter image description here

  • The problem only seems to occur if the panels/squares start off 'overlapping'.
  • If you use a layout that stops this or don't set the size small then the problem does not seem to occur.
  • the problem doesn't happen every time which initially made me think that concurrency problems might be involved.
  • The code that I had problems with in my original question did not seem to cause problems for everybody and so my IDE, jdk etc might be relevant as well: Windows 7, Eclipse Kepler, jdk1.7.0_03

The code minus imports etc is as follows:

public class ColorPanelsWindow extends JFrame{

    static class ColorPanel extends JPanel {

        //color starts off black
        //once it is changed should never be 
        //black again
        private Color color = Color.BLACK;

        ColorPanel(){
            //add listener
            addMouseListener(new MouseAdapter(){
                @Override
                public void mousePressed(MouseEvent arg0) {
                    color = rotateColor();
                    repaint();
                    //using getParent().repaint() instead of repaint() solves the problem
                    //getParent().repaint();
                }
            });
        }
        //rotates the color black/blue > red > green > blue
        private Color rotateColor(){
            if (color==Color.BLACK || color == Color.BLUE)
                return Color.RED;
            if (color==Color.RED)
                return Color.GREEN;
            else return Color.BLUE;
        }

        @Override
        public void paintComponent(Graphics g){
            g.setColor(color);
            g.fillRect(0, 0, 100, 100);
        }
    }

    ColorPanelsWindow(){
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        setLayout(new GridLayout(1,0));
        add(new ColorPanel());
        add(new ColorPanel());
        //the size must be set so that the window is too small
        // and the two ColorPanels are overlapping
        setSize(40, 40);
//      setSize(300, 200);

        setVisible(true);
    }

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

            @Override
            public void run() {
                new ColorPanelsWindow();
            }

        });
    }
}

So my question is, what on earth is going on here?


Solution

  • but I'd like to know exactly what is happening,

    I see the same problems using JDK7u60 on Windows 7. Definitely seems like a bug to me.

    My best guess is that it is a problem with the double buffering.

    I added so debug code to the paintComponent() method.

    1) When you click on the right component only its paintComponent() method is called and the panel is painted the proper color.

    2) When you click on the left component only its paintComponent() method is called and the panel is painted the proper color, however the panel on the right reverts back to the black color, without invoking the paintComonent() method on the right panel. This leads me to believe that somehow an old buffer is being used (this would be the bug and I have no idea how to fix it).

    The reason that getParent().repaint() works is because this forces both components to be repainted no matter which panel you click on.