Let's say i have a listener attached to a button. When i press this button, actionPerformed is called and i set a label as visible. Then the calculate() method runs(which has some really long calculations inside it and it takes time). Then i wanna print the results with the show() method.
Thing is that i know for a fact that the label will be set as visible after all the code inside actionPerformed will be executed.
So my question is : How should i set the calculate method to run on background? Threads? SwingTimer? SwingWorker? I haven't found an ideal way yet.
class ButtonListener implements ActionListener{
#Override
public void actionPerformed(ActionEvent e) {
calculateLbl.setVisible(true);
calculate();
show();
}
}
Your problem is one of Swing concurrency: When calculate() is called on the Swing event thread, the long-running code hampers the event thread, preventing it from painting to the JLabel. The solution is to run calculate in a background thread, and then be notified when it is done. When notification occurs, call show(). A SwingWorker would work great for this since it comes with its own notification mechanism.
e.g.,
#Override
public void actionPerformed(ActionEvent e) {
calculateLbl.setVisible(true);
new SwingWorker<Void, Void>() {
public Void doInBackground() throws Exception{
calculate(); // this is run in a background thread
// take care that calculate makes no Swing calls
return null;
}
protected void done() {
show(); // this is run on the Swing event thread
}
}.execute();
}
Caveat: code not tested/compiled/nor run.
A problem with the above code is that it does not handle any exceptions that might be thrown within the calculate method, and a cleaner better way to do this is to create a SwingWorker variable, attach a PropertyChangeListener to it, and when its SwingWorker.StateValue is SwingWorker.StateValue.DONE, call get() on the SwingWorker and handle any possible exceptions there.
Related
Related to my previous question: Call repaint from another class in Java?
I'm new to Java and I've had a look at some tutorials on SwingWorker. Yet, I'm unsure how to implement it with the example code I gave in the previous question.
Can anyone please explain how to use SwingWorker with regards to my code snippet and/or point me towards a decent tutorial? I have looked but I'm not sure I understand yet.
Generally, SwingWorker is used to perform long-running tasks in Swing.
Running long-running tasks on the Event Dispatch Thread (EDT) can cause the GUI to lock up, so one of the things which were done is to use SwingUtilities.invokeLater and invokeAndWait which keeps the GUI responsive by which prioritizing the other AWT events before running the desired task (in the form of a Runnable).
However, the problem with SwingUtilities is that it didn't allow returning data from the the executed Runnable to the original method. This is what SwingWorker was designed to address.
The Java Tutorial has a section on SwingWorker.
Here's an example where a SwingWorker is used to execute a time-consuming task on a separate thread, and displays a message box a second later with the answer.
First off, a class extending SwingWorker will be made:
class AnswerWorker extends SwingWorker<Integer, Integer>
{
protected Integer doInBackground() throws Exception
{
// Do a time-consuming task.
Thread.sleep(1000);
return 42;
}
protected void done()
{
try
{
JOptionPane.showMessageDialog(f, get());
}
catch (Exception e)
{
e.printStackTrace();
}
}
}
The return type of the doInBackground and get methods are specified as the first type of the SwingWorker, and the second type is the type used to return for the publish and process methods, which are not used in this example.
Then, in order to invoke the SwingWorker, the execute method is called. In this example, we'll hook an ActionListener to a JButton to execute the AnswerWorker:
JButton b = new JButton("Answer!");
b.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e)
{
new AnswerWorker().execute();
}
});
The above button can be added to a JFrame, and clicked on to get a message box a second later. The following can be used to initialize the GUI for a Swing application:
private void makeGUI()
{
final JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.getContentPane().setLayout(new FlowLayout());
// include: "class AnswerWorker" code here.
// include: "JButton" b code here.
f.getContentPane().add(b);
f.getContentPane().add(new JButton("Nothing"));
f.pack();
f.setVisible(true);
}
Once the application is run, there will be two buttons. One labeled "Answer!" and another "Nothing". When one clicks on the "Answer!" button, nothing will happen at first, but clicking on the "Nothing" button will work and demonstrate that the GUI is responsive.
And, one second later, the result of the AnswerWorker will appear in the message box.
Agree:
Running long-running tasks on the Event Dispatch Thread (EDT) can cause the GUI to lock up.
Do not agree:
so one of the things which were done is to use SwingUtilities.invokeLater and invokeAndWait which keeps the GUI responsive..
invokeLater still runs the code on the EDT, and can freeze your UI!! Try this:
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
try {
Thread.sleep(100000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
At least I, cannot move my mouse once I click the button which triggers the actionPerformed with the above code. Am I missing something?
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:
The GUI doesn't update
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...
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.
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.
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).
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.
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.)
#Override
public void actionPerformed(ActionEvent e) {
new Thread() {
public void run() {
//your code which runs on click event
}
}.start();
}
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:
The GUI doesn't update
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...
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.
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.
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).
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.
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.)
#Override
public void actionPerformed(ActionEvent e) {
new Thread() {
public void run() {
//your code which runs on click event
}
}.start();
}
I'm building a Swing application and one part of the functionality should be to process and output some text visually and audibly (using Mary TTS). I need some advice on the best way for the GUI and text processing classes to communicate.
The GUI class is a subclass of JPanel. Within that I have a class implementing Runnable, called LineProcesser, which prepares the text to be dispatched to an audio player. I'm using a thread executor to keep this off the EDT (that may not be the best way but it seems to achieve the result I'm after).
My intention is for LineProcessor to run through all the text and update a JTextArea at the end of each line. Additionally it will need to halt and wait for user input at certain points. After the user input has been completed the GUI class should tell it to resume processing.
The following code illustrates what I currently have:
public class MyPanel extends JPanel {
ExecutorService lineExecutor = Executors.newSingleThreadExecutor();
Runnable lineProcessor = new LineProcessor();
public class LineProcessor implements Runnable {
private int currentLineNo = 0;
public LineProcessor() {
// ...
}
#Override
public void run() {
// call getText();
// call playAudio();
currentLineNo++;
}
}
}
private JButton statusLbl = new JLabel();
private JButton mainControlBtn = new JButton();
private void mainControlBtnActionPerformed(ActionEvent evt) {
if (mainControlBtn.getText().equals("Start")) {
lineExecutor.submit(lineProcessor);
mainControlBtn.setText("Running");
}
}
}
How can LineProcessor notify GUI components that they need to change and how can it be paused and restarted from within the GUI? I'm confused as to whether I need a Swing Worker, property/event listeners or something else? The examples I've read sort of make sense but I can't see how I can apply them to the code I have here.
All you need to do is wrap any Swing calls in a Runnable, and queue it on the EDT via SwingUtilities.invokeLater(myRunnable);. That's it. No need for a SwingWorker.
e.g.,
public class LineProcessor implements Runnable {
private int currentLineNo = 0;
Runnable LineProcessor = new LineProcessor(); // won't this cause infinite recursion?
public LineProcessor() {
// ...
}
#Override
public void run() {
// call getText();
// call playAudio();
currentLineNo++;
SwingUtilities.invokeLater(new Runnable() {
public void run() {
// *** Swing code can go here ***
}
});
}
}
You will have to use both SwingWorker and Event methodology.
Place your long running code in Swing Worker.
Create new property change Event, listener, manager
In your SwingWorker, when the change event occurs, call PropertyChangeManager to notify all the liseners.
All GUI components which wants to be notified with the event should register themselves with the PropertyChangeManager.
Your PropertyChangeManager will call customProperyChange method of the PropertyChangeListener and will pass the properyChangeEvent
What you are looking for is a SwingWorker. This class allows to perform the work on a worker thread, having periodical updates on the EDT, and in the end update the EDT as well.
Several examples are available on SO and in the Swing tutorials. Just a few links
The 'How to use progress bars tutorial', which happens to update a text area as well
The 'Concurrency in Swing' tutorial, a must-read if you start with SwingWorker
The SwingWorker javadoc
Reporting progress can be done with the publish method, these results will be passed to the process method in which you can update the UI. At the end, the done method is called allowing you to perform some final UI updates.
For the pause/restart functionality ... you can use an invokeAndWait in the doInBackground method with a blocking method call (for example showing a JOptionPane asking for user input). But if you start using invokeAndWait in the doInBackground it might be overkill to use the SwingWorker and you can simply opt for the approach #Hovercraft Full Of Eels suggested
Related to my previous question: Call repaint from another class in Java?
I'm new to Java and I've had a look at some tutorials on SwingWorker. Yet, I'm unsure how to implement it with the example code I gave in the previous question.
Can anyone please explain how to use SwingWorker with regards to my code snippet and/or point me towards a decent tutorial? I have looked but I'm not sure I understand yet.
Generally, SwingWorker is used to perform long-running tasks in Swing.
Running long-running tasks on the Event Dispatch Thread (EDT) can cause the GUI to lock up, so one of the things which were done is to use SwingUtilities.invokeLater and invokeAndWait which keeps the GUI responsive by which prioritizing the other AWT events before running the desired task (in the form of a Runnable).
However, the problem with SwingUtilities is that it didn't allow returning data from the the executed Runnable to the original method. This is what SwingWorker was designed to address.
The Java Tutorial has a section on SwingWorker.
Here's an example where a SwingWorker is used to execute a time-consuming task on a separate thread, and displays a message box a second later with the answer.
First off, a class extending SwingWorker will be made:
class AnswerWorker extends SwingWorker<Integer, Integer>
{
protected Integer doInBackground() throws Exception
{
// Do a time-consuming task.
Thread.sleep(1000);
return 42;
}
protected void done()
{
try
{
JOptionPane.showMessageDialog(f, get());
}
catch (Exception e)
{
e.printStackTrace();
}
}
}
The return type of the doInBackground and get methods are specified as the first type of the SwingWorker, and the second type is the type used to return for the publish and process methods, which are not used in this example.
Then, in order to invoke the SwingWorker, the execute method is called. In this example, we'll hook an ActionListener to a JButton to execute the AnswerWorker:
JButton b = new JButton("Answer!");
b.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e)
{
new AnswerWorker().execute();
}
});
The above button can be added to a JFrame, and clicked on to get a message box a second later. The following can be used to initialize the GUI for a Swing application:
private void makeGUI()
{
final JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.getContentPane().setLayout(new FlowLayout());
// include: "class AnswerWorker" code here.
// include: "JButton" b code here.
f.getContentPane().add(b);
f.getContentPane().add(new JButton("Nothing"));
f.pack();
f.setVisible(true);
}
Once the application is run, there will be two buttons. One labeled "Answer!" and another "Nothing". When one clicks on the "Answer!" button, nothing will happen at first, but clicking on the "Nothing" button will work and demonstrate that the GUI is responsive.
And, one second later, the result of the AnswerWorker will appear in the message box.
Agree:
Running long-running tasks on the Event Dispatch Thread (EDT) can cause the GUI to lock up.
Do not agree:
so one of the things which were done is to use SwingUtilities.invokeLater and invokeAndWait which keeps the GUI responsive..
invokeLater still runs the code on the EDT, and can freeze your UI!! Try this:
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
try {
Thread.sleep(100000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
At least I, cannot move my mouse once I click the button which triggers the actionPerformed with the above code. Am I missing something?