views:

317

answers:

6

Hi all,

I have something I can't understand: my Swing GUI contains a 'play' and 'pause' button. I have also a static variable that defines 'ON' and 'OFF' states. (The main program generates the GUI). By cliking on 'play' I change the state of my static variable to 'ON' and I launch a time-consuming process in a thread that also modifies the GUI. As long as the static variable is 'ON' loops in the same process. Clicking on 'pause' would change the static variable to OFF. But by clicking on 'play' the GUI is freezing and consequently:

  1. The GUI doesn't update
  2. The process can't be 'paused' with my 'pause' button.

I have heard about EDT and SwingWorker but I you have a simple way to do it I take it.

Thank you for your help and forgive my bad english...

+6  A: 

The problem is that you're doing the intensive, time-consuming work on the same thread responsible for updating the GUI. SwingWorker allows you to move time-consuming tasks to a separate thread of execution, thereby leaving the UI thread to do its thing uninhibited.

However, it does add a further complication: affinity. Calling methods on UI components generally requires that you do so from the UI thread. Therefore, you need to use special functionality to get back to the UI thread from the worker thread. SwingWorker also gives you this ability.

I suggest you read through this documentation.

HTH, Kent

Kent Boogaart
A: 

You should not start long-running processes in Swing’s event handler because it will freeze your GUI, you know that now. :) Start it in a new thread. You only need to use a SwingWorker if you’re planning on manipulating the GUI from the worker thread (because Swing is not thread-safe).

Bombe
A: 

This is a pretty straightforward reason: while Java is working on your time-consuming process, it isn't able to update the GUI. Solution: run the time-consuming process in a separate thread. There are a bunch of ways to program that, and it would probably depend somewhat on how your program is written.

David Zaslavsky
+2  A: 

You need to read Concurrency in Swing to understand how the EDT and SwingWorkers operate.

All GUI updates are executed on the EDT so when you click a GUI component any method that this calls will be executed on the EDT. If this is a time consuming process then this will block the EDT from executing any futher GUI updates. Hence your GUI is freezing and you can't click the pause button.

You need to use SwingWorker to execute the time consuming process on another thread. The link I provided above details how to do this.

Mark
A: 

The event dispatch thread (EDT) is the only thread in which it's safe to read or update the GUI.

The pause button should be setting the on/off variable in the event dispatch thread.

The time-consuming operation, and the loop, should not be in the EDT. (The loop should also not be running continuously doing nothing but check the variable, or it can easily eat all your CPU. If it has nothing else to do it should check, and then call Thread.sleep() for some length of time (say 100ms).)

If you can prove that the on/off variable is being set to OFF, but that nonetheless it's always read as ON, it may be that the variable's value is not being copied from the EDT to the worker thread. Make it volatile, or synchronize access to it, or use an AtomicReference, or read it in the EDT using SwingUtilities.invokeAndWait().

SwingWorker probably is the simplest way to go, here. Implement your time-consuming operation, and the on/off check, in the doInBackground() method, and your GUI update in the done() method.

public enum State {
 RUNNING, STOPPED
}

public class ThreadSafeStateModel {
 private State state = State.STOPPED;

 public synchronized void stop() {
  state = State.STOPPED;
 }

 public synchronized void start() {
  state = State.RUNNING;
 }

 public boolean isRunning() {
  return state == State.RUNNING;
 }
}

public class ExpensiveProcessWorker extends SwingWorker<Void, Void> {

 private final ThreadSafeStateModel model;

 public ExpensiveProcessWorker(ThreadSafeStateModel model) {
  this.model = model;
 }

 @Override // Runs in background
 protected Void doInBackground() throws Exception { 
  while (model.isRunning()) {
   // do one iteration of something expensive
  }
  return null;
 }

 @Override // Runs in event dispatch thread
 protected void done() { 
  // Update the GUI
 }
}

public class StopButton extends JButton {
 public StopButton(final ThreadSafeStateModel model) {
  super(new AbstractAction("Stop") {
   @Override
   public void actionPerformed(ActionEvent e) {
    model.stop();
   }
  });
 }
}

public class StartButton extends JButton {
 public StartButton(final ThreadSafeStateModel model) {
  super(new AbstractAction("Start") {
   @Override
   public void actionPerformed(ActionEvent e) {
    model.start();
    new ExpensiveProcessWorker(model).execute();
   }
  });
 }
}

(A lot could be done to clean this up depending on the real application, but you get the idea.)

David Moles
Thank you David all is ok and runs perfectly. But just a thing: I didn't need to use the done() method to refresh the GUI. I succeed to do it in the doInBackground() method.Have you got an idea about that?
Hey, Jeremy -- in general updating the UI in `doInBackground()` will often work but you can't depend on it. The UI is always painted in the event dispatch thread, and UI models are always read in the event dispatch thread, so it's safer to only write to UI models in the event dispatch thread. For instance, if the number of rows in a `TableModel` changes outside the event dispatch thread, there's a good chance that when the table's painted there will be `IndexOutOfBoundsException`s because the table and the table model disagree about the number of rows.
David Moles
I understand...a little bit more now I have tried to add an other kind of GUI update and it freezed. To resolve this problem I used the done() method to update the GUI and create an other swingWorker with the same initial data. It's ok now! Thank you very much!
A: 

Thank you all for your very useful answers. I try it and I keep you posted!