Jeremy
Jeremy

Reputation:

My GUI is frozen

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...

Upvotes: 0

Views: 3615

Answers (6)

Khalid Habib
Khalid Habib

Reputation: 1156

@Override
public void actionPerformed(ActionEvent e) {
     new Thread() {
        public void run() {
            //your code which runs on click event
        }
    }.start();

}

Upvotes: 0

Kent Boogaart
Kent Boogaart

Reputation: 178630

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.

Upvotes: 6

David Moles
David Moles

Reputation: 51053

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.)

Upvotes: 0

Mark
Mark

Reputation: 29119

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.

Upvotes: 3

David Z
David Z

Reputation: 131550

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.

Upvotes: 0

Bombe
Bombe

Reputation: 83847

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).

Upvotes: 0

Related Questions