2

Is there any way to synchronize Event Dispatch Thread with another thread created by swing.Timer, SwingWorker or new Thread()? I want EDT to wait for another thread does it's work (moving object on screen for example) and to do some task immediately after this work is done. Is it possible? I have to use another thread because if I try to change object's coordinates in EDT I don't see movement on screen.

Rafa Viotti
  • 9,998
  • 4
  • 42
  • 62
Alexander
  • 51
  • 5

1 Answers1

5

You don't want to synchronize on the EDT or have it join other threads as this will likely freeze your program. Probably your best bet is to use a listener type of structure to notify the Swing code that the background thread is done, something that can be done for instance by adding a PropertyChangeListener to a SwingWorker and listening for the "state" property to change to SwingWorker.StateValue.DONE. If you do use this, be sure to call get() on your SwingWorker within your PropertyChangeListener so as to catch all exceptions that might have been thrown from within the background thread.

For example


Edit
You state in comment:

But I'll try to explain. User presses the button. I want some JPanel object to continuously move to another place on screen as a result of button press. This movement will take about 0.4 seconds and I really don't want any button to respond during this time. Movement is provided by coordinates change + Thread.sleep(5). Seems it's impossible to realize this movement in Event Dispatch Thread. Am I right?

  1. Yes it is quite possible to do the movement on the EDT if you use a Swing Timer. A Swing Timer uses a background thread behind the scenes to repeatedly call its ActionListener's actionPerformed method with a delay. The key with its use is that all code in its actionPerformed method is called on the EDT.
  2. If you want to de-activate the button, then set the button as disabled 1(or better, it's Action) by calling setEnabled(false). Undo this once the animation has completed.

For example:

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;

import javax.swing.*;

public class MoveBall extends JPanel {
   private static final int PREF_W = 800;
   private static final int PREF_H = PREF_W;
   private static final int BALL_W = 40;
   private static final int BALL_H = BALL_W;
   private static final Color BALL_COLOR = Color.red;
   public static final int TIMER_DELAY = 20;
   private int ballX = BALL_W / 2;
   private int ballY = BALL_H / 2;
   private BufferedImage ballImg;
   private ButtonAction buttonAction = new ButtonAction("Start Animation");
   private Timer timer;

   public MoveBall() {
      ballImg = new BufferedImage(BALL_W, BALL_H, BufferedImage.TYPE_INT_ARGB);
      Graphics2D g2 = ballImg.createGraphics();
      g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
            RenderingHints.VALUE_ANTIALIAS_ON);
      g2.setColor(BALL_COLOR);
      g2.fillOval(1, 1, BALL_W - 2, BALL_H - 2);
      g2.dispose();

      add(new JButton(buttonAction));
   }

   @Override
   protected void paintComponent(Graphics g) {
      super.paintComponent(g);
      if (ballImg != null) {
         int x = ballX - BALL_W / 2;
         int y = ballY - BALL_H / 2;
         g.drawImage(ballImg, x, y, null);
      }
   }

   @Override
   public Dimension getPreferredSize() {
      if (isPreferredSizeSet()) {
         return super.getPreferredSize();
      }
      return new Dimension(PREF_W, PREF_H);
   }

   private class ButtonAction extends AbstractAction {
      public ButtonAction(String name) {
         super(name);
         int mnemonic = (int)name.charAt(0);
         putValue(MNEMONIC_KEY, mnemonic);
      }

      @Override
      public void actionPerformed(ActionEvent e) {
         ballX = BALL_W / 2;
         ballY = BALL_H / 2;
         repaint();
         setEnabled(false);
         new Timer(TIMER_DELAY, new TimerListener()).start();
      }
   }

   private class TimerListener implements ActionListener {
      @Override
      public void actionPerformed(ActionEvent e) {
         if (ballX + BALL_W / 2 >= getWidth()) {
            stopBall(e);
         } else if (ballY + BALL_H / 2 >= getHeight()) {
            stopBall(e);
         } else {
            ballX++;
            ballY++;
         }
         repaint();
      }

      private void stopBall(ActionEvent e) {
         buttonAction.setEnabled(true);
         ((Timer) e.getSource()).stop();
      }
   }

   private static void createAndShowGui() {
      MoveBall mainPanel = new MoveBall();

      JFrame frame = new JFrame("MoveBall");
      frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
      frame.getContentPane().add(mainPanel);
      frame.pack();
      frame.setLocationByPlatform(true);
      frame.setVisible(true);
   }

   public static void main(String[] args) {
      SwingUtilities.invokeLater(new Runnable() {
         public void run() {
            createAndShowGui();
         }
      });
   }
}
Community
  • 1
  • 1
Hovercraft Full Of Eels
  • 283,665
  • 25
  • 256
  • 373
  • 3
    Definitely the way to go. With a `SwingWorker` the `done()` method gets called anyway at the end of processing - that makes it very easy to invoke a callback there. – Boris the Spider Feb 08 '15 at 22:26
  • @BoristheSpider: true, but if you do that, the SwingWorker must have some knowledge of the calling code since it must then call a method of the calling code to notify it of completion, increasing coupling. For this reason I usually prefer to use a PropertyChangeListener. – Hovercraft Full Of Eels Feb 08 '15 at 22:27
  • Fair point. Using a listener does mean that the coupling is one-directional. Either way beats synchronising on the EDT though... – Boris the Spider Feb 08 '15 at 22:28
  • @BoristheSpider: Yep, agree 100%. My answer didn't touch on the subject of SwingWorker stability, but some have had problems using it, especially counting on it to complete correctly. I've not yet seen that problem, but I've also not pushed it to its limits either. – Hovercraft Full Of Eels Feb 08 '15 at 22:30
  • Thanks a lot. But what if I really want to **freeze my program** untill the work in secondary thread is done? You see I use another thread only to diplay object's movement on screen. Frankly I'd prefer to refuse to use another thread if I can achieve it the other way. – Alexander Feb 08 '15 at 23:18
  • 1
    @Alexander: It's fine to freeze an animation or halt a behavior by changing state, but you never want to freeze the Swing event thread ever. This will frustrate the user to no end. As for your second part, I'm not sure what you're asking. – Hovercraft Full Of Eels Feb 08 '15 at 23:20
  • @Hovercraft Full Of Eels: Sorry, I'm novice in Java. But I'll try to explain. User presses the button. I want some JPanel object to continuously move to another place on screen as a result of button press. This movement will take about 0.4 seconds and I really don't want any button to respond during this time. Movement is provided by coordinates change + Thread.sleep(5). Seems it's impossible to realize this movement in Event Dispatch Thread. Am I right? – Alexander Feb 09 '15 at 01:10