Search code examples
javaswinganimationthread-safetyevent-dispatch-thread

Display an image using EventDispatchThread vs without


So I'm trying to display an image(ball) which I'll eventually control with user input. For know, the image just gets displayed over intervals using thread's sleep method.

I've made 2 classes, one that extends JPanel and the other extends JFrame. The JPanel subclass looks like this:

 public class BallPanel extends JPanel {
    private Image ball;
    private int x,y;
    public BallPanel(){
        try {
            ball=ImageIO.read(new File("C:\\Users\\Owner\\Desktop\\ball.png"));
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        x=10;
        y=10;
        Thread thread = new Thread() {
            @Override
            public void run(){
                    loop();
        }
      };
        thread.start();
    }
    public void paintComponent(Graphics g){
        super.paintComponent(g);
        g.drawImage(ball,x,y,null);
    }
    public void loop(){
        while(true){
            repaint();
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }
}

In the loop method I call the sleep method to allow repaint to be called over intervals. Then, loop() is called in the constructor.

The JFrame subclass looks like this:

    public class BallFrame extends JFrame {

    public BallFrame(){
        setVisible(true);
        setSize(800,800);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setContentPane(new BallPanel());
    }
    public static void main(String args[]){
        //SwingUtilities.invokeLater(new Runnable() {
             //   @Override
              //  public void run() {
                    new BallFrame();
               // }
    //});

}
}

Now the interesting, or perhaps confusing thing, is that when I run the code as it is shown here, with the anonymous inner class commented out, the ball doesn't always appear. Sometimes I need to re-size the frame (i.e call repaint) before the ball is shown. However, when I call it through the even dispatch thread using the anonymous inner class the ball appears every time I run the code. What is the reason for this?


Solution

  • It has little to do with starting the UI from within the EDT or not (although you should cause that can cause lots of other weird and interesting issues) and more to do with the fact that you've called setVisible before you've established the contents of the UI.

    This is possibly an example of a race condition between the system trying to get the EDT up and running and the OS calls responding before it's established.

    In either case you SHOULD start the UI from within the EDT and call setVisible last.

    Swing can be lazy about updating the UI, this is actually a deliberate design choice as well as a good idea. You don't always want the UI updated after each and every change you make (like adding/removing components), so it hands over some of the control to the developer to decided when it's best to revalidate container hierarchy and request repaints

    I would also avoid using a Thread to update the state of the UI as this could cause dirty paints as Swing uses a passive rendering approach (painting when it feels it's required) and consider using a Swing Timer which updated from within the EDT OR use a BufferStrategy and employ a active rendering approach, which you can then control