Search code examples
javaswingactionlistener

Java Swing: Activate an animation with an Action event


I am trying to write a simple GUI in java.switch which activates an animation when a button is pressed. The animation works when I remove the button and just let the animation run directly, but when I put the animation code in an ActionListener class which runs when the button is pressed, then it does not work anymore. What is the reason for that?

Code, working animation without button:

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.concurrent.TimeUnit;

class SimpleGUI {
    
    private JFrame frame;
    private JPanel panel;
    private int xPos = 70;
    private int yPos = 70;

    class DrawPanel extends JPanel {
    public void paintComponent(Graphics g) {
        g.setColor(Color.white);
        g.fillRect(0, 0, this.getWidth(), this.getHeight());
        g.setColor(Color.green);
        g.fillOval(xPos, yPos, 40, 40);
    }
    }

    public void go() {
    frame = new JFrame();
    panel = new DrawPanel();
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.getContentPane().add(BorderLayout.CENTER, panel);
    //frame.getContentPane().add(BorderLayout.SOUTH, button);
    frame.setSize(300,300);
    frame.setVisible(true);
    for(int i = 0; i < 100; i++) {
        xPos++;
        yPos++;
        panel.repaint();
        try {
        TimeUnit.MILLISECONDS.sleep(50);
        } catch(Exception e) {
        e.printStackTrace();
        }
    }
    }
}

class GUITest {
    public static void main(String[] args) {
    SimpleGUI gui = new SimpleGUI();
    gui.go();
    }
}

Code, broken animation with button:

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.concurrent.TimeUnit;

class SimpleGUI {
    
    private JFrame frame;
    private JPanel panel;
    private int xPos = 70;
    private int yPos = 70;

    class DrawPanel extends JPanel {
    public void paintComponent(Graphics g) {
        g.setColor(Color.white);
        g.fillRect(0, 0, this.getWidth(), this.getHeight());
        g.setColor(Color.green);
        g.fillOval(xPos, yPos, 40, 40);
    }
    }

     class AnimationListener implements ActionListener {
    public void actionPerformed(ActionEvent event) {
        for(int i = 0; i < 100; i++) {
        xPos++;
        yPos++;
        panel.repaint();
            try {
            TimeUnit.MILLISECONDS.sleep(50);
        } catch(Exception e) {
            e.printStackTrace();
        }
        }
    }
     }

    public void go() {
    frame = new JFrame();
    panel = new DrawPanel();
    JButton button = new JButton("Start Animation");
    button.addActionListener(event -> button.setText("Animation starts"));
    button.addActionListener(new AnimationListener());
    
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.getContentPane().add(BorderLayout.CENTER, panel);
    frame.getContentPane().add(BorderLayout.SOUTH, button);
    frame.setSize(300,300);
    frame.setVisible(true);
    }
}



class GUITest {
    public static void main(String[] args) {
    SimpleGUI gui = new SimpleGUI();
    gui.go();
    }
}

I were expecting that the code in the ActionListener class is executed completely when the button is pressed. I also tried removing the for loop in the ActionListener class so that the circle moves a single step whenever the button is pressed. This works, and I can move the circle all 100 steps by pressing the button repeatedly.


Solution

  • You've got a threading problem.

    Working: You start the application on the main thread. It creates the UI which spaws the Event Dispatching Thread to update the UI. In parallel your application runs the go() on the main thread, and it seems to work. Although there is still a design flaw.

    Not working: The go() method is started from a button press, which itself is invoked on the Event Dispatching Thread. Since the go() method does not exit, the thread is kept busy and has no time to update the UI.

    Solution:

    • Use multiple threads.
    • To prevent peculiarities, learn to handle concurrency in Swing.

    There is a nice tutorial out there: https://docs.oracle.com/javase/tutorial/uiswing/concurrency/index.html