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?
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?
My understanding of the Swing Event Dispatcher Thread (EDT) is that its a dedicated thread where event handling code is executed. So, if my understanding is correct, then in the example below:
private class ButtonClickListener implements ActionListener{
public void actionPerformed(ActionEvent e) {
// START EDT
String command = e.getActionCommand();
if( command.equals( "OK" )) {
statusLabel.setText("Ok Button clicked.");
} else if( command.equals( "Submit" ) ) {
statusLabel.setText("Submit Button clicked.");
} else {
statusLabel.setText("Cancel Button clicked.");
}
// END EDT
}
}
All the code in between START EDT and END EDT is executing on the EDT, and any code outside of it is executing on the main application thread. Similarly, another example:
// OUTSIDE EDT
JFrame mainFrame = new JFrame("Java SWING Examples");
mainFrame.setSize(400,400);
mainFrame.setLayout(new GridLayout(3, 1));
mainFrame.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent windowEvent){
// START EDT
System.exit(0);
// END EDT
}
// BACK TO BEING OUTSIDE THE EDT
});
Again, only the System.exit(0) is executed inside the EDT.
So for starters, if my understanding of the "division of labor" between EDT and main app thread code execution is incorrect, please begin by correcting me!
Now then, I came across an article that emphasized the use of creating a new Thread from inside all this EDT code, which would make my first example above look like this:
public class LabelUpdater implements Runnable {
private JLabel statusLabel;
private ActionEvent actionEvent;
// ctor omitted here for brevity
#Override
public void run() {
String command = actionEvent.getActionCommand();
if (command.equals( "OK" )) {
statusLabel.setText("Ok Button clicked.");
} else if( command.equals( "Submit" ) ) {
statusLabel.setText("Submit Button clicked.");
} else {
statusLabel.setText("Cancel Button clicked.");
}
}
}
private class ButtonClickListener implements ActionListener{
public void actionPerformed(ActionEvent e) {
// START EDT
Thread thread = new Thread(new LabelUpdater(statusLabel, e));
thread.start();
// END EDT
}
}
My question: what advantage (or lack thereof) is there to this approach? Should I always code my EDT code this way, or is there a rubric one needs to follow as a guidelines for when to apply it? Thanks in advance!
The question is a bit broad and unspecific, but I'll try to address some of the points that you asked about. The entry point for further, own research is probably the Lesson: Concurrency in Swing, although it may indeed be hard to derive definite statements for specific cases from that.
First of all, there is an overarching rule in Swing - referred to as the Single Thread Rule:
Once a Swing component has been realized, all code that might affect or depend on the state of that component should be executed in the event-dispatching thread.
(Unfortunately, it is no longer stated so clearly in the tutorial)
Keeping that in mind, looking at your snippets:
// OUTSIDE EDT
JFrame mainFrame = new JFrame("Java SWING Examples");
...
This is often true, unfortunately - and unfortunately, even in some of the official Swing examples. But this may already cause problems. To be on the safe side, the GUI (including the main frame) should always be handled on the EDT, using SwingUtilities#invokeLater. The pattern is always the same then:
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> createAndShowGui());
}
private static void createAndShowGui() {
JFrame mainFrame = new JFrame("Java SWING Examples");
...
mainFrame.setVisible(true);
}
Regarding the second example that you showed, involving the LabelUpdater class: I'd be curious from which article you got this. I know, there is a lot of cr4p out there, but this example doesn't even remotely make sense...
public class LabelUpdater implements Runnable {
private JLabel statusLabel;
...
#Override
public void run() {
...
statusLabel.setText("Ok Button clicked.");
}
}
If this code (i.e. the run method) is executed in an new thread, then it obviously violates the single thread rule: The status of the the JLabel is modified from a thread that is not the event dispatch thread!
The main point of starting a new thread in an event handler (e.g. in an actionPerformed method of an ActionListener) is to prevent blocking the user interface. If you had some code like this
someButton.addActionListener(e -> {
doSomeComputationThatTakesFiveMinutes();
someLabel.setText("Finished");
});
then pressing the button would cause the EDT to be blocked for 5 minutes - i.e. the GUI would "freeze", and look like it hung up. In these cases (i.e. when you have long-running computations), you should do the work in an own thread.
The naive approach of doing this manually could (roughly) look like this:
someButton.addActionListener(e -> {
startBackgroundThread();
});
private void startBackgroundThread() {
Thread thread = new Thread(() -> {
doSomeComputationThatTakesFiveMinutes();
someLabel.setText("Finished"); // WARNING - see notes below!
});
thread.start();
}
Now, pressing the button would start a new thread, and the GUI would no longer block. But note the WARNING in the code: Now there's this problem again of the JLabel being modified by a thread that is not the event dispatch thread! So you'd have to pass this back to the EDT:
private void startBackgroundThread() {
Thread thread = new Thread(() -> {
doSomeComputationThatTakesFiveMinutes();
// Do this on the EDT again...
SwingUtilities.invokeLater(() -> {
someLabel.setText("Finished");
});
});
thread.start();
}
This may look clumsy and complicated, and as if you could have a hard time figuring out on which thread you currently are. And that's right. But for the common task of starting a long-running task, there is the SwingWorker class explained in the tutorial that makes this pattern somewhat simpler.
Shameless self-promotion: A while ago, I created a SwingTasks library, which is basically a "Swing Worker on steroids". It allows you to "wire up" methods like this...
SwingTaskExecutors.create(
() -> computeTheResult(),
result -> receiveTheResult(result)
).build().execute();
and takes care of showing a (modal) dialog if the execution takes too long, and offers some other convenience methods, e.g. for showing a progress bar in the dialog and so on. The samples are summarized at https://github.com/javagl/SwingTasks/tree/master/src/test/java/de/javagl/swing/tasks/samples
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.
I hava a GUI that has a button that need to be pressed periodically in the sense it should pause in between. But i am not able to do that. I have tried with Thread.sleep(). Below is the code.
protected Object doInBackground() throws Exception {
while(true){
btnSend.doClick();
try {
Thread.sleep(2000);
continue;
} catch (InterruptedException e1) {
}
}
return null;
}
Can anyone tell me where i am going wrong and how to solve it?
You shouldn't use a SwingWorker for this since you should not call doClick() off the event thread. If al you want to do is make this call intermittently, then simply use a Swing Timer for calls that need to be done intermittently and on the event thread.
int timerDelay = 2000;
new Timer(timerDelay, new ActionListener() {
public void actionPerformed(ActionEvent evt) {
btnSend.doClick();
}
}).start();
Edit
You ask in comment:
But i need to update another UI on that button click event. If i dont use swingworker the other UI will freeze. I have to use swingworker. Is it possible to do so?
You've got it wrong, I fear. The button click and GUI code should all be on the EDT. Any background non-GUI code that is generated by the button click's ActionListener should be done in a SwingWorker but not the click itself.
e.g.,
elsewhere
btnSend.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent evt) {
new SwingWorker<Void, Void>() {
public Void doInBackground() throws Exception {
//.....
return null;
}
}
}
});
If you need more specific advice regarding your situation, then you'll want to consider creating and posting a Minimal, Complete, and Verifiable Example Program where you condense your code into the smallest bit that still compiles and runs, has no outside dependencies (such as need to link to a database or images), has no extra code that's not relevant to your problem, but still demonstrates your problem.
So my program has multiple classes and after one of them has run, it'd like it so it appends the text area in the main class GUI with a 'finished' message
ta.append("Search Complete\n");
and this is the code that needs to complete
statCl.addActionListener(new ActionListener(){
public void actionPerformed (ActionEvent e) {
try {
ta.append("Searching...\n");
task.execute();
} catch (Exception IOE) {}
}
});
So it is in the class where task where actual code runs.
Any advice or help would be amazing, thanks.
If the task.execute() method doesn't start launch an operation in another thread, then the GUI will be freezed, and nothing will apear in the text area until the operation is finished. So you might just have
ta.append("Searching...\n");
task.execute();
ta.append("Finished");
If the operation is launched in a new thread, then this thread should append in the text area, but it should make sure this append is done in the event dispatch thread (EDT). Your code could thus look like this :
public class Task {
private JTextArea ta;
public Task(JTextArea ta) {
this.ta = ta;
}
public void execute() {
Thread t = new Thread(new Runnable() {
// perform the long operation
// at the end, update the text area, in the EDT
SwingUtilities.invokeLater(new Runnable() {
public void run() {
ta.append("finished");
}
});
}
t.start();
}
}
You might also look at SwingWorker, which is designed just for that (and other things like progress update). There is a code example in its class javadoc which does just what you're trying to do.
You should not be performing long-running task on EDT (event dispatching thread):
http://java.sun.com/products/jfc/tsc/articles/threads/threads1.html
Swing does all it's work on EDT, so you should not block EDT, e.g. run intensive tasks on it. Note: all event handlers are executed on EDT.
So there are two challenges:
Run intensive tasks in a background thread.
Update GUI, which must be done on EDT.
Use SwingWorker to solve this two issues.